exlab_wizard.orchestrator.quiescence_poller#
Operator-free, per-file quiescence-driven NAS sync poller.
Operator-free per-file NAS sync design (2026-05-21). The
QuiescenceSyncPoller is the single auto-sync trigger for every
run pending NAS sync – orchestrator-staged runs and runs acquired
directly on nas-mode equipment. It supersedes the sentinel/manifest
StagingWatcher and its five-state ingest.json machine.
Each sweep (poll_once):
Discover runs in both roots: * stage-mode – run-leaf directories under
config.orchestrator.staging_root;nas-mode – run-leaf directories under each
nas-mode equipment’slocal_roottree (<local_root>/<equipment_id>/<project>/{Runs, TestRuns}/<Run_*>).
Per-file quiescence. The poller keeps an in-memory snapshot across sweeps: for each file, its
(st_size, st_mtime_ns)signature and the wall-clock time the signature was first observed. A file is quiet once that signature has been observed unchanged for at leastconfig.sync.quiescence_minutes. Files matching anyconfig.sync.ignore_globsglob and the.exlab-wizard/cache dir are skipped. Eligibility is measured from the poller’s own observations across sweeps – not the absolute age ofmtime– because transports (rsync -t,rclone) preserve the sourcemtime.Per-file eligibility. A quiet file is eligible when its current
(st_size, st_mtime_ns)signature differs from thesynced_signaturerecorded for it in the run’ssync_state.json(a file with no record, or a record carrying a stale signature, is eligible; a file matching its recorded signature has already synced at its current state and is skipped).Enqueue. Each discovered run with at least one eligible file is enqueued via
nas_sync.enqueue(run_path, files=[...])carrying the run-relative paths of the eligible files.enqueueitself owns the re-queue / no-op decision (a terminal job with a fresh subset is re-armed; an active job is a no-op), so the poller no longer needs the coarse “skip run with a job” guard.
The poller is safe to cancel at any await point: it carries no on-disk state of its own (the snapshot is purely in-memory and is rebuilt by re-observing the filesystem on the next sweep).
Classes
|
One file's observation record carried forward across sweeps. |
|
The NAS-sync surface the poller and the Phase 3 staging code use. |
|
Polls every run pending NAS sync and enqueues runs with eligible files. |
- class exlab_wizard.orchestrator.quiescence_poller.FileSnapshot(signature, first_seen_monotonic)[source]#
Bases:
objectOne file’s observation record carried forward across sweeps.
signature– the(st_size, st_mtime_ns)last observed.first_seen_monotonic– thetime.monotonic()value at which that exact signature was first observed. Reset whenever the signature changes; the settle window is measured from it.
- class exlab_wizard.orchestrator.quiescence_poller.NASSyncLike(*args, **kwargs)[source]#
Bases:
ProtocolThe NAS-sync surface the poller and the Phase 3 staging code use.
enqueueis what the poller itself calls; thestagingrouter andui/mountrun-status code additionally consultstatus/get_by_run_path/list_all. They are declared here so the protocol documents the full surface thatdeps.nas_syncmust satisfy. Tests pass in-memory stubs that record the calls.
- class exlab_wizard.orchestrator.quiescence_poller.QuiescenceSyncPoller(*, config, nas_sync, sync_state_writer)[source]#
Bases:
objectPolls every run pending NAS sync and enqueues runs with eligible files.
Constructor dependencies are a
Config, aNASSyncClient-shaped sync client, and aSyncStateWriterused (read-only) to skip files already synced at their current(st_size, st_mtime_ns)signature.The start/stop/loop lifecycle mirrors the retired
StagingWatcher;poll_onceis exposed so tests can drive the poller synchronously.- Parameters:
config (
Config)nas_sync (
NASSyncLike)sync_state_writer (
SyncStateWriter)
- apply_config(config)[source]#
Swap the cached config in place so a live settings save applies.
poll_oncere-readsquiescence_minutes, the equipment list, the staging root, and the ignore globs fromself._configevery sweep, and_loop()re-readspoll_interval_secondseach iteration – so reassigning the config here makes every sync setting take effect on the next sweep without a tray relaunch.
- async poll_once(*, now_monotonic=None)[source]#
Run one discovery + quiescence + enqueue sweep.
now_monotonicis injectable so tests can drive the settle window with a controllable clock; production callers leave itNoneand the poller readstime.monotonic().Returns the list of run paths enqueued on this sweep (in discovery order) so tests can assert exactly which runs fired.