Repository navigation
refactor(daemon): Part of #3736: T2-S5 background owners on native - #3981
Conversation
…-background-owners
…-background-owners
…-background-owners # Conflicts: # crates/tracedecay/src/daemon/tests/ownership.rs
|
| )) { | ||
| log_daemon_event( | ||
| "retirement_reaper", | ||
| &[ | ||
| ("owner", format!("{:?}", owner.owner.project_id)), | ||
| ("outcome", "runtime_unavailable".to_owned()), | ||
| ("error", error), | ||
| ], | ||
| ); | ||
| return; |
There was a problem hiding this comment.
🔴 Rejected reaper strands project automation
When NativeTask::spawn refuses a reaper, the retired scheduler remains registered as Retiring without a task. Later retirement attempts cannot clear it, so project automation cannot restart.
Learn more
Retirement first moves the scheduler task out of its registry entry and marks that entry Retiring in retire_matching_automation_scheduler. Normally, the reaper joins the task and runs the cleanup future, which removes that entry. If native spawn refuses, the finalizer signals termination but never runs cleanup. The entry retains Retiring with task: None; later retirement attempts cannot create another reaper, and scheduler reconciliation returns Retiring.
Example: A project's automation loop starts retiring as the native runtime stops accepting tasks. Reaper spawn fails; the retirement waiter completes, but the project's next automation reconcile still reports Retiring indefinitely.
Recommended fix: Make spawn refusal run or schedule the cleanup after the retired task has actually settled, retaining ownership until then. Avoid publishing termination as completed before the scheduler tombstone is removed; validate a retry can start automation after a refused reaper spawn.
Was this helpful? React with 👍 or 👎 to provide feedback.
Refs #3736
T2-S5 of the Tokio to Asupersync cutover: the daemon's background owners. This branch is based on master after #3964 (T2-S4) and #3883 (nightly toolchain), and merges master up to a3028ce.
What changed
A spawn moved to
NativeTaskonly when its future reaches no Tokio runtime API. The trace compared each future's callees against the production Tokio inventory of the workspace: everytokio::spawn,JoinSet, Tokio timer,tokio::net,tokio::fs,Handleandnative_runtime::tokio_handlesite outside test code.scheduler.rsautomation loop (start_automation_scheduler)tracedecay-automation-runtime(blocking work through nativespawn_blocking),tracedecay_daemon_service::automation_effect(retained request context and memory owner reads), and the registered Codex prompt runner, which is synchronous. None of these has a Tokio site. Its timers were already native.branch_admin.rsretirement reaperbranch_admin.rsmanual branch publicationJoinSetVecof abort-on-dropNativeTasks)pr_autotrack, and the code-index scheduler mount. None of these has a Tokio site.maintenance.rsresident-memory samplerspawn_blocking.maintenance.rspublication due-request tasktokio::sync::broadcastreceiver. Both work on any executor.maintenance.rsretention looptracedecay-maintenance, the code-index scheduler registry, store telemetry and branch-store GC (execute_branch_admin_in_layout, thenpr_autotrackretirement). None of these has a Tokio site.pr_autotrack/runtime.rsloopproject_retirement.rsretirement tracker (:223)JoinHandleworks on any executor.project_retirement.rsspawn_and_track(:97) and fallible retirement (:142)project_server_lifecycle::retire_project_servers.remote_recovery_lifecycle.rs:259project_server_lifecycle::retire_project_servers_now.hook_v2_replay_consumer.rsconsumeradmit_hook_v2_envelope. That callsadmit_registered_hook_orchestration, which needs the Tokio handle attokio_handle("daemon-service.hook_orchestration.admit"). The consumer's timer was already native.spool_opener.rsopenerproject_open_admission.rs:635. It has no timer.Behavior notes:
AutomationSchedulerReconcileOutcome::OwnerUnavailableand logsscheduler_start outcome=runtime_unavailable.pr_autotrack::spawn_with_administrationreturnsErr, and bootstrap logs it and runs without the loop.maintenance_task_unavailablewith the owner name.branch_tracking_failederror.retirement_reaper outcome=runtime_unavailable.PrAutotrackTaskstill aborts its loop on drop. A dropped manual-publication owner still aborts its tasks, as the droppedJoinSetdid, and a cancelled shutdown join aborts the publications it had not joined. Both are proven by new tests with mutation checks (see Tests).ProjectServerRetirement::_taskandMaintenanceReaperHandle::_taskare gone: dropping aJoinHandleonly detached the task, so holding the handle did nothing.track_aborted_retirement_taskpassed the automation loop. It is replaced bytrack_aborted_native_retirement_task.Waiting on project_server_lifecycle.rs
This PR does not read or edit
project_server_lifecycle.rs. Three seams stay on Tokio until that file's timers move (scope commit 3b):ProjectServerRetirementAdmission::spawn_and_track(project_retirement.rs:97). Its future callsretire_project_servers.spawn_fallible_retirement(project_retirement.rs:142). Its capacity-retirement future callsretire_project_servers.retire_runtime_work(remote_recovery_lifecycle.rs:259). Its future callsretire_project_servers_now.track_retirement_taskkeeps itstokio::task::JoinHandleparameter, so callers in that file compile unchanged.Tests
Each off-Tokio test runs inside
native_runtime::block_on, or after a Tokio setup on a plain thread, and assertsHandle::try_current().is_err(). Each test readstest_support::native_runtime_hazards()before and after the run and asserts that it did not change. That helper sumstokio_runtime_missing_countover thetokio_handlesites and addsexact_sql::native_thread_writer_waits. This PR adds the four remainingtokio_handlesites to the sum:work_attempt.spawn_for_worktree,host_admission.bootstrap,shutdown.runnerandshutdown_coordination.runner.Red-first: each test below was run on master with the test adapted to master's signatures. Each failed:
On this branch, these tests pass:
pr_autotrack::tests::spawned_loop_is_cancellable_and_joinable_without_a_tokio_runtime(rewritten from the Tokio variant).pr_autotrack::tests::reconcile_activates_a_pr_head_without_a_tokio_runtime: the tick's reconcile checks out PR 11 and mounts its worktree off Tokio.maintenance::tests::the_coordinator_runs_and_shuts_down_without_a_tokio_runtime.maintenance::tests::a_full_tick_collects_an_unmounted_registered_project_without_a_tokio_runtime(the full-tick test, moved off Tokio).tests::scheduler_config::the_automation_loop_starts_and_exits_without_a_tokio_runtime: a reconcile with no Tokio runtime returnsStarted. The loop reads its disabled configuration and records the exit decision at the existing exit barrier.tests::scheduler_shutdown::automation_retirement_reaps_without_a_tokio_runtime.tests::scheduler_shutdown::manual_branch_publication_runs_and_joins_without_a_tokio_runtime.branch_admin::project_retirement::tests::a_tokio_retirement_is_tracked_and_settled_without_a_tokio_runtime.branch_admin::project_retirement::tests::an_aborted_native_owner_settles_its_retirement_clean.Abort-on-drop proofs, with mutation checks. The checks emptied
PrAutotrackTask::dropandManualBranchPublication::drop. Both tests then failed:dropping_the_pr_autotrack_owner_aborts_its_parked_loop:the dropped owner aborts its loop: Elapsed(()).dropping_the_administration_aborts_an_owned_manual_branch_publication:the dropped owner aborts its publication: Elapsed(()).Rewritten tests:
NativeTask. A noncooperative owner blocks a worker of its own one-worker native runtime until released (spawn_noncooperative_native_test_task), so the process runtime is never short a worker.NativeTaskProbereports when the task's future was dropped. It replaces the TokioAbortHandle::is_finishedchecks.cancel_stops_the_automation_loop_while_the_scheduler_map_is_heldnow waits for the loop's own end instead of pollingis_finishedfor 2 s. In one full-suite run the 2 s poll failed under load. The loop's tick is 60 s, so the 20 s hang guard still fails when the cancel does not reach the loop. For a mutation check, the stop request and wake were removed fromcancel_automation_schedulers. The test then failed witha cancel issued while the scheduler map was held must still end the loop.NativeTasks.The scheduler tick also ran off Tokio during the first run of the loop test: the project-open loop ticked memory curator, session reflector and skill writer on the native runtime with no panic.
Gates
These gates ran on the final merge (066a946):
bazel build --config=clippy //crates/tracedecay/...: Build completed successfully.bazel build //:rustfmt_checkand//crates/tracedecay:rustfmt_check_2024: Build completed successfully.bazel test //crates/tracedecay:unit_test: PASSED, 881 passed.bazel test //crates/tracedecay:daemon_suite: PASSED, 75 passed.These gates ran on f9f5212, before the last two master merges. This PR's production code has not changed since:
//crates/tracedecay:mcp_suite: PASSED, 661 passed.//crates/tracedecay:session_suite: PASSED, 418 passed.Two failures did not reproduce:
twelve_project_journey_retires_idle_owners_without_empty_graphsfailed once in a fullunit_testrun withreopening a retired project must restore its Hook V2 replay consumer. It passed in 3 isolated reruns and in the next full run.shutdown_hook_v2_replay_consumerremoves the registration by data root without checking which consumer owns it. A late retirement can therefore remove a newer consumer's registration. This PR does not change that code.daemon_suiterejected_token_reads_typed_refusal_and_daemon_keeps_servingfailed once withwrite handshake: BrokenPipe. The test client pipelines its writes after the daemon has refused and closed the socket. It passed on rerun.