a73x

internal/agent/reconcile/managerhelpers_test.go

Ref:   Size: 1.8 KiB   History

package reconcile

// Test-only introspection for the per-VM worker manager. Neither runs in
// production — the agent never counts its workers, and it never waits on them
// (not blocking on workers is the whole point of this layer). They live in the
// test build so the production package has no unreachable methods, while tests
// can observe a tick's work before asserting on it.

// count reports the number of live workers (one per VM this host is tracking).
func (m *manager) count() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.workers)
}

// waitIdle blocks until every worker has consumed its pending assignment and
// finished the resulting pass, so a test can assert on a tick's work.
func (m *manager) waitIdle() {
	for _, w := range m.snapshot() {
		w.mu.Lock()
		// !w.stopped is an escape, not a nicety: a worker stopped while an
		// assignment was still pending returns from run without ever clearing
		// pending, and this would otherwise wait on it forever.
		for (w.pending != nil || w.busy) && !w.stopped {
			w.cond.Wait()
		}
		w.mu.Unlock()
	}
}

// Stop shuts down every per-VM worker. It signals and returns without waiting: a
// worker mid-pass exits when that pass ends (bounded by VMTimeout).
//
// TERMINAL: an Engine cannot be restarted, and after Stop, Step dispatches
// nothing and reports empty actual state — which the control plane reads as every
// VM on this host having vanished. Production therefore never calls it
// (cmd/eitri-agent lets the process exit and the OS reclaim the goroutines); it
// exists only so a test's cleanup can tear workers down between cases.
func (e *Engine) Stop() {
	m := e.manager()
	m.mu.Lock()
	defer m.mu.Unlock()
	m.stopped = true
	for id, w := range m.workers {
		delete(m.workers, id)
		w.stop()
	}
}