Repository navigation
[WSLC] (1/2) Run the state-aware daemon's exec off the single worker thread - #1407
Soham Das (SohamDas2021) wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Shutdown can discard queued non-exec commands and lose a completed exec’s real result.
Review effort: Balanced
Findings: 1
What changed in this PR
Moves WSLc state-aware exec execution off the apartment worker while preserving container-handle safety.
Changes:
- Runs execs on dedicated MTA threads.
- Parks same-container lifecycle work until exec completion.
- Adds shutdown handling, tests, and updated documentation.
| File | Description |
|---|---|
session_manager.rs |
Adds threaded exec lifecycle and parking. |
control_server.rs |
Updates admission documentation. |
wslc-state-aware.md |
Documents concurrency and ordering changes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
a4c6692 to
400ed19
Compare
400ed19 to
88f25ff
Compare
88f25ff to
415f934
Compare
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The new unsafe cross-thread FFI handle sharing and teardown lifetime guarantees warrant final human review despite comprehensive tests.
Review effort: Balanced
Findings: None
Resolved since last review (1)
415f934 to
e753739
Compare
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
e753739 to
7ce1c8e
Compare
Gudge (MGudgin)
left a comment
There was a problem hiding this comment.
Review summary
Requesting changes for the one High finding: after a client disconnects, its handler releases the sole exec permit while the newly off-worker run can continue. Another exec can then start on a different container, contrary to the intended one-active-exec limit. Please bind capacity to confirmed run termination, handle premature client exit, and test that sequence. The other findings below are non-blocking individually.
What I checked: The PR base 13d899c is the merge-base of head 7ce1c8e, and the local three-file diff matches gh pr diff after line-ending normalization. The module grows from 25 to 42 #[test]/#[tokio::test] markers. Same-container parking, finished-run replies, and a WSL2-only same-container deprovision test are present. The permit acquisition and handler ownership in control_server.rs are unchanged; it is the new off-worker exec path that removes the old serialization backstop. This was a source-level review; I did not rerun the WSL2 suites.
Claim-only finding (non-blocking)
Medium (performance) — Same-container waiters can exhaust the client reserve. claim_mismatch: the PR description says other work proceeds immediately, but the new parked same-container requests keep their handlers waiting. With one attached exec and eight such waiters, all nine pre-existing client permits are occupied, so another sandbox's request (or cancellation) cannot get a handler until capacity frees. This qualifies the claim under saturation; it is not a new unbounded queue or a merge condition. Consider bounding parked waiters, reserving a cancellation lane, or qualifying the description.
Verified pre-existing — not attributed to this PR
- The pipe-handler-scoped exec permit existed in the base; the High finding is attributed to the PR only because moving exec off the worker makes its early release capable of admitting another active run.
- The global client capacity and the transition-lock teardown order predate the PR. I am not treating the previously suggested lock-held shutdown delay or pull-on-worker-panic concern as a new regression.
The accompanying inline comments contain the remaining verified findings, including the two safety-test gaps and a non-blocking documentation clarification.
| // MTA threads: both processes started within 0.5ms of each other and overlapped | ||
| // for 5.1100s of a 5.1177s wall clock, both exited 0, and each process's output | ||
| // arrived only on its own capture buffer and live sink. | ||
| unsafe impl Send for ExecJob {} |
There was a problem hiding this comment.
Medium (test coverage) — Test exec overlapping another container's real SDK lifecycle.
Attribution: newly_exposed_by_change — this PR moves exec to an MTA thread while the worker can concurrently provision/start/stop/deprovision another container against the shared WSLc session.
The comment describes a manual two-exec measurement. The committed ignored WSL2 test deprovision_during_an_exec_waits_for_the_run checks only same-container parking, not the cross-container SDK calls made live by this PR. Admission is sent before the run thread starts, so an admission reply alone does not prove an overlap occurred.
Fix: Add an ignored WSL2 test that waits for a ready signal from exec on A, completes B's lifecycle before A exits, then verifies A's output and exit status. Multi-exec admission can remain deferred.
There was a problem hiding this comment.
Agreed - deferring to PR 2, which builds the concurrency E2E harness this needs anyway.
| worker: &mpsc::UnboundedSender<WorkerCommand>, | ||
| ) { | ||
| for work in self.retire_exec(sandbox_id, report) { | ||
| self.dispatch(work, worker); |
There was a problem hiding this comment.
Low (test coverage) — Multiple parked commands' replay order lacks a test.
Attribution: introduced_by_change — this new loop redispatches every command parked behind the finished run, but the existing tests park one command at a time.
For a parked [Exec, Stop], the next Exec must take the container claim and the Stop must re-park behind it, rather than stop the newly running process.
Fix: Exercise that sequence through a controllable exec runner or in an ignored WSL2 test; assert the Stop remains pending until the second Exec finishes.
There was a problem hiding this comment.
Agreed - deferring to PR 2; needs the injectable exec runner that work introduces.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
7ce1c8e to
e41d278
Compare
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Cancellation, permit lifetime, and panic cleanup contain unresolved concurrency and resource-safety defects.
2 open findings
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
| let delivered = write_exec_result(&mut pipe, session.exec(config).await).await; | ||
|
|
||
| if delivered.is_err() { |
| /// | ||
| /// Reached only on an unwind, where [`Worker::shutdown`]'s drain never ran. | ||
| fn abandon_if_execs_running(mut self, in_flight: usize) { | ||
| if in_flight > 0 { |
Gudge (MGudgin)
left a comment
There was a problem hiding this comment.
Meant to approve #1396 :-(


📖 Description
The WSLc daemon ran every SDK call on one apartment-affine worker thread, and
execheld that thread for the entire run. While one container was executing, nothing else could be serviced — provisioning, starting, stopping or deprovisioning any other sandbox waited for it to finish.Exec now runs on a thread of its own and reports back when it completes, so the worker stays responsive. A command naming a container whose run is still in flight waits for that run, because deleting the container would free a handle the run is using. Everything else proceeds immediately.
Nothing changes for a caller except ordering. The daemon still admits one exec at a time, an unknown or unstarted sandbox still fails before admission, and a timeout or cancellation still kills the process rather than the container. The one observable difference: commands are no longer globally ordered across sandboxes, so work finishing on one container no longer implies anything about another.
This is 1 of 2 changes needed to resolve the work item. It deliberately keeps the single-exec admission limit, so the new concurrency is not yet reachable by callers. The second raises that bound, reports a busy container to the caller, and proves the resulting concurrency end to end.
🔗 References
docs/wsl/wslc-state-aware.mdupdated: the daemon no longer runs all SDK calls on the worker, and the serialized-exec limitation is replaced with what actually remains.🔍 Validation
cargo test --workspace— 4861 passed, 0 failedcargo clippy --workspace --all-targets -- -D warnings,cargo fmt --all -- --check— clean#[ignore]daemon tests, bothdaemon_ipcpipe tests, and the full E2E suite — one-shot 35/35, state-aware 83/83✅ Checklist
Cargo.lock, thedependency-feed-checkcheck passes (see docs/pull-requests.md)📋 Issue Type
Microsoft Reviewers: Open in CodeFlow