Repository navigation
Description
Summary
stop-if-idle closes namespace admission before counting protected work and keeps it closed until it reopens admission after a busy decision. When its activity.lock try-lock succeeds, it also holds that lock exclusively for the rest of the decision.
A view operation accepted before the closure can reach an admission check inside that window. Its job then fails terminally with busy/namespace-admission-closed. ViewManager::execute does not defer that reason, so the receipt becomes failed. This happens even though the same probe counted the operation as protected work and returned stopping:false.
SHARED_WORKTREE_INDEXES.md documents the closure ("stop-if-idle first closes admission, then accounts for … A busy decision reopens admission"). It does not say that a probe ending busy can fail work that was already admitted.
Environment
- tgrep v1.2.5 on Windows. The idle/admission code is unchanged since v1.2.4:
tgrep-core/src/managed/maintenance.rsandlifetime.rsare identical between the two tags. - One managed daemon shared by two Copilot runtime processes. One process supervises the daemon; the other attaches a worktree of the same repository.
- Test-fixture policy:
workers1,work_slots2,idle_timeout_ms1000,admission_retry_ms100,operation_timeout_ms30000. Production uses a much longer idle timeout, which makes the window rarer but does not remove it. - CPU saturated by concurrent test processes. Each client RPC took 1.1–3.2 s, compared with 250–370 ms unloaded.
Timeline
Times are client-observed seconds, relative to the start of the run.
| t | Event |
|---|---|
| 24.05 | Supervising process's views.attach accepted; it occupies the single worker until about 37–40 |
| 34.69 | Peer's views.attach accepted and queued behind it |
| 44.3, 49.3 | Peer's attach observed preparing |
| 48.04 → 50.26 | Supervising process's stop-if-idle round. Result: stopping:false; leases 2, owners 2, operations 3, queued_jobs 3 |
| ~50.4 | Peer's attach job no longer running |
| 53.37 | Peer's receipt failed: busy/namespace-admission-closed |
| 59.6 | Client resubmits the attach with a new token; accepted |
| 61.6 | Client's attach deadline (started ~29.1) expires; that session uses non-indexed search for its lifetime |
Other rounds in the same run returned namespace-maintenance-active (31.9, 40.97) or stopping:false without failing anything.
Code path (v1.2.5)
-
In
tgrep-cli/src/serve/shared/managed.rs, the"stop-if-idle"handler sets the process admission gate to false for the whole call. During that time the accept loop answers every new connection withdaemon-admission-closed, including receipt polls for accepted operations, and the maintenance tick skips. The handler then runsviews.drain_released()and callsNamespace::stop_if_idle_with_token. -
In
tgrep-core/src/managed/maintenance.rs,stop_if_idle_innercommitsadmission='closed'and counts leases, owners, reservations and operations. It then always attemptstry_lock_exclusive(activity.lock), even when the counts already show protected work. It runsdiscard_idle_verificationand only then commitsadmission='open'. -
Two checks return
busy/namespace-admission-closedduring that interval:work::ensure_admission, while the catalog hasadmission!='open';ActivityGuard::acquire, whose shared lock is refused while the probe holdsactivity.lockexclusively.
We have not determined which of the two the peer's job hit.
-
In
tgrep-core/src/managed/views.rs,ViewManager::executedefers onlyview-work-active,generation-already-materializing,view-attachment-preparing,object-retirement-in-progressandcatalog-checkpoint-readers-activethroughdefer_operation. Any other error, includingnamespace-admission-closed, is recorded as the operation's terminal result.
Admission closes before the count runs, so the same probe counts every operation accepted before the closure (operations > 0). The probe therefore cannot commit a stop while such an operation exists, and failing the operation's job gains nothing.
Expected
- A probe that finds protected work does not fail admitted work. Operations accepted before the closure, and their jobs, keep running or wait for the decision. They are never failed with
namespace-admission-closed. - Ideally, a probe that can already see protected work returns
stopping:falsewithout closing either gate or takingactivity.lockexclusively. Busy daemons would then never expose the window. - During a genuine closure, new requests still get the documented retryable
busyrejection before acceptance. They are never accepted and then failed. - Only a committed stop ends in-flight work, with a reason distinct from a transient closure.
Impact
A session whose attach fails this way falls back to non-indexed search for its lifetime. A retry with a new token cannot recover when the first attempt consumed most of the client's operation deadline.
The Copilot runtime no longer sends stop-if-idle while its own sessions are live, which removes the trigger above. Three cases remain:
- a supervising process with no live sessions racing another process's attach;
- orphan-maintenance probes against a daemon that is serving other clients;
- any other client that sends
stop-if-idle.
Possible fix (suggestion)
- Busy fast path in
stop_if_idle_innerand the CLI handler's process gate. Check the cheap counts first, and when any protected work is visible, record the non-stopping decision without closing admission or lockingactivity.lock. Returning "not stopping" from a slightly stale positive observation is always safe. - Defer instead of failing. In
ViewManager::execute, treat a transientnamespace-admission-closedas deferrable for operations accepted before the closure, viadefer_operation. This covers the remaining window, where the pre-check saw no work but an operation was accepted just before closure: its job is delayed instead of failed. Requeued work must resume once admission reopens, and a committed stop must still end it. - Deterministic regression tests with the existing
IdleAdmissionClosedfault point andmanaged-test-hooks:- Pause a probe after closure while an accepted attach's job runs. Assert that the probe returns busy, the attach completes, and admission reopens.
- A race test: an attach is either admitted and completes while the daemon stays, or is rejected before acceptance while the daemon stops.
Evidence, including merged daemon and client timelines and receipts, is retained and can be shared on request.
Activity
Keep admitted work running when stop-if-idle finds the namespace busy…