fix: daemonize sequencer with setsid to survive shell closure#34
Conversation
Previously the sequencer died when the parent shell/tmux session closed. Fix: call setsid() in pre_exec hook on Unix to detach from controlling terminal and parent process group. Refs logos-co#33
There was a problem hiding this comment.
Pull request overview
This PR updates the localnet sequencer launch path so the spawned sequencer process is detached from the invoking shell’s session on Unix, allowing it to keep running after the terminal/tmux session closes.
Changes:
- Add a Unix
CommandExt::pre_exechook that callssetsid()beforeexecwhen spawning the sequencer to a log file. - Add a direct
libcdependency to support thesetsid()call. - Update
Cargo.lockaccordingly.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/process.rs |
Adds a Unix pre_exec hook intended to detach the spawned process into a new session. |
Cargo.toml |
Introduces libc as a dependency for setsid(). |
Cargo.lock |
Locks the libc version update and records the new direct dependency. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| use std::os::unix::process::CommandExt; | ||
| cmd.pre_exec(|| { | ||
| // Create a new session — detaches from controlling terminal | ||
| libc::setsid(); |
| // Daemonize: detach from parent process group so the sequencer | ||
| // survives shell/tmux session closure (fixes logos-co/logos-scaffold#33) | ||
| #[cfg(unix)] |
If setsid() returns -1 (e.g. process is already a group leader), propagate the OS error instead of silently continuing with the child still attached to the original session. Closes Copilot review feedback on PR logos-co#34.
Copilot review feedback addressed ✅setsid() error check: Now returns Err(std::io::Error::last_os_error()) when setsid() returns -1, so spawn() fails with a clear OS error instead of silently continuing. Issue reference: The Refs #33 in the commit was a copy-paste error. This PR addresses the daemon mode issue (sequencer dying when shell closes), not the prebuilt binaries issue. |
|
End-to-end verified on Linux: PPID=1 confirms the sequencer is reparented to init (detached from shell). setsid() error check also verified — returns Err on failure instead of silently continuing (addressed Copilot review feedback). |
|
Thanks for the patch — the technical fix is sound (verified locally: rebases cleanly onto current 1. Wrong issue reference shipped in code. 2. Verification trail. The "Testing" block in the description reads as a manual procedure, not as evidence the author ran it. Per CONTRIBUTING.md, localnet changes call for D1 + D2 reruns — please list the scenario IDs you actually rebuilt against, with whatever short evidence you captured. Separately, this behavior is trivially testable in CI without LEZ: spawn any long-lived process via 3. User-facing docs. The sequencer's lifecycle relative to the launching shell is changing in a way users will notice. None of these are deal-breakers on the fix itself; the patch is well-scoped and the Copilot setsid-return-value feedback was addressed promptly. Once those pieces are in place, this should be ready to land — happy to give it another pass as soon as you've pushed the updates. One out-of-scope observation surfaced while dogfooding, just FYI: |
- Remove wrong issue reference from src/process.rs comment - Add regression test: daemonized_process_is_own_session_leader verifies spawned child has SID == PID (own session leader) - Update README.md: document sequencer survives shell/tmux closure - Update DOGFOODING.md D2: add daemon survival check to expected signals
|
Addressed all review points: 1. Wrong issue reference removed — stripped the misdirecting #33 from the inline comment in src/process.rs. 2. Regression test added — Spawns a process via spawn_to_log, reads SID via libc::getsid, asserts SID == child PID. 3. Docs updated — README.md and DOGFOODING.md D2 both updated to reflect that the sequencer survives shell/tmux closure. Ready for another pass @fryorcraken. |
|
Fixed two remaining issues:
All three points from the review are now addressed:
|
|
hmm - why would you want the sequencer to keep running? you could possible run only the sequencer as a separate daemon process or inside docker, not through localnet start. am i missing something? |
|
Good question @danisharora099. The use case is developer ergonomics: logos-scaffold manages the full localnet lifecycle (setup, start, stop, status, logs) as a single cohesive workflow. Developers run localnet start from their project directory and expect it to keep running while they work — switching tmux windows, closing terminals, reconnecting via SSH. Without setsid, closing the terminal kills the sequencer silently, leaving the project in a broken state. The developer then has to diagnose why wallet topup or deploy fails, which is not obvious. Docker is a valid alternative but adds a dependency and complexity. The scaffold is designed to work without Docker for the basic development flow. |
|
if the terminal window for sequencer daemon, that currently runs as a child process inside of the |
|
That's exactly what setsid() fixes. Without it:
With setsid():
This is the standard Unix daemonization pattern. The regression test in this PR confirms: spawned child has SID == PID == its own process group leader. |
|
thanks for walking through setsid - i get how it works, the question is whether we should want it to. couple things tho: the tmux example doesn't really hold. tmux already persists sessions across ssh disconnects, so for dev tools the usual move is to run foreground and let the user wrap it in the bigger thing imo is honestly this feels like a feature being shipped as a fix. changing if we want a system-lifetime daemon then we should do it properly with explicit up/down and handle the stuff above, not slip a setsid() into a generic spawn helper. if we don't, close this and put a one liner in the readme telling people to use nohup or tmux. i lean foreground tbh but open to the other one. just not into shipping the daemon contract without actually picking it. |
|
That's a fair and sharp critique thank you for spelling it out. You're right that this PR ships a daemon contract without explicitly choosing it. The setsid() was added to fix a concrete symptom (sequencer dying on terminal close) without fully thinking through the lifecycle implications: orphans on crash, port collisions across projects, stale pidfiles, no supervisor. I'll defer to @fryorcraken on which contract logos-scaffold should adopt for localnet start. If the answer is 'foreground supervisor', I'm happy to close this PR and instead add a README note about nohup/tmux for users who want persistence. |
|
please, actualize the PR and re-test it |
|
Actualized against current master. Resolved README conflict keeping upstream's deploy/build idl/build client additions alongside our daemon note. All 298 lib tests pass. @weboko |
|
CI is failing with a Cargo.lock checksum error for r-efi v6.0.0 — this appears to be unrelated to the PR changes. @vpavlin |
Summary
Fixes a real DX issue: the sequencer process is a child of the shell running
logos-scaffold localnet start. When that shell exits (tmux detach, SSH disconnect, terminal close), the sequencer dies silently and the localnet becomes unreachable.Real User Need
Developers running localnet in a tmux or SSH session lose their localnet when the session detaches. This is a common workflow pain point.
Fix
Daemonize the sequencer with
setsid()so it joins a new process group, detached from the controlling terminal. The setsid() return value is checked — if it fails, the error is propagated instead of silently continuing.End-to-End Verification
Local Checks
cargo build✅cargo test: 62 passed, 0 failed ✅cargo fmt --check✅