fix(worker): cooperative shutdown via recv_timeout + atexit/R_unload (#103)#199
Open
fix(worker): cooperative shutdown via recv_timeout + atexit/R_unload (#103)#199
Conversation
Collaborator
Author
|
We should consider depending on the rust crates ctor/dtor instead of relying on atexit. Althought this is a perfectly acceptable approach as is. |
…103) The worker thread was blocked on `mpsc::recv()` forever: the static `JOB_TX: OnceLock<SyncSender<AnyJob>>` was never dropped, so `recv` never saw `Disconnected`, and `OnceLock` has no `take()` to let us signal otherwise. On process exit the worker stayed parked in a blocked syscall — harmless on Linux/macOS because `_exit` terminates threads, but a latent hang risk on Windows under `system2` pipe capture. Fix, defense-in-depth: - Replace the blocking `while let Ok(job) = job_rx.recv()` loop with a `recv_timeout(250ms)` loop that polls a new `WORKER_SHOULD_STOP: AtomicBool`. Idle workers wake at most 4 times per second; queued jobs dispatch immediately (timeout only fires when nothing is in flight). - Add `miniextendr_runtime_shutdown()` — public, idempotent — that flips the flag. Without the `worker-thread` feature it's a no-op. - Call sites: - `miniextendr_runtime_init` registers a libc `atexit` hook, so graceful R session shutdown (`q()`) wakes the worker even when `.onUnload` doesn't fire. atexit can be flaky on Windows but is additive — it only helps, never hurts. - `miniextendr_init!` now also generates `R_unload_<pkg>` which R calls on `detach(unload=TRUE)` / `dyn.unload()`, calling `miniextendr_runtime_shutdown()`. Re-entry guard and all existing worker tests still green. Capacity-0 rendezvous semantics on the job channel are unchanged, so in-flight behavior is identical to before. Keeps OS thread cleanup implicit: we don't track a `JoinHandle` or `thread::join` — on process exit the OS reaps the thread; on package unload the thread exits within ≤250 ms of the shutdown signal. Closes #103. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
e118f20 to
911bc44
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #103.
Problem
The worker thread (`worker-thread` feature) blocks forever on `mpsc::recv()`. The static `JOB_TX: OnceLock<SyncSender>` is never dropped, and `OnceLock` has no `take()` method, so `recv` never sees `Disconnected`. On process exit the worker stays pinned in a blocked syscall — harmless on Linux/macOS where `_exit` reaps threads, but a latent hang risk on Windows under `system2` pipe capture.
Fix (defense-in-depth)
Test plan
Notes
Generated with Claude Code