Repository navigation
Make daemon startup reliable: validated config, atomic lockfile, waiting up - #92
Merged
Merged
Conversation
…ing up - Config (#54): PyroLabConfiguration rejects dangling cross-references (service daemon/nameservers, daemon nameservers, autolaunch entries), reporting every problem at once. `config update` and `info` print them readably; `rename` refuses to write a config it would invalidate. - Startup (#54, #56): pyrolabd.main() validates the config before starting anything, publishes the lockfile as soon as the daemon can answer, then autolaunches in a background thread with each entry isolated. A startup failure is written to STARTUP_ERROR_FILE. - Lockfile (#56): written atomically (temp file + os.replace), read tolerantly, and judged stale by whether its PID is running (new utils.pid_is_running, using ctypes on Windows) rather than by a failed connection. - `pyrolab up` waits until the daemon responds (--timeout, default 30s) and otherwise reports why it exited; it refuses to start a second daemon while one is running, even an unresponsive one. - ProcessManager methods share a reentrant lock, now that autolaunch, the checkup timer, and Pyro request threads run concurrently. - Importing pyrolab.api no longer fails on an invalid user config, one with no nameservers, or an unresolvable "public" host; this would otherwise have locked users out of every CLI command. Closes #54 Closes #56 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Makes starting the background daemon reliable. Closes #54 and #56. A follow-up PR on the process manager (#50, #51, #52, #53, #59) builds on this one and should be merged after it.
Configuration cross-references (#54)
PyroLabConfigurationnow rejects a config where any reference points to something undefined. That covers each service'sdaemonandnameservers, each daemon'snameservers, and everyautolaunchentry. A service with nodaemon:setting refers to one calleddefault, and the error says so.describe_config_error().pyrolab config updaterejects an invalid file, shows the problems, says the installed config wasn't changed, and exits 1. Before, it either installed a config that later killed the daemon, or printed a pydantic traceback.pyrolab inforeports an invalid installed config the same way.pyrolab renamenow checks the result before saving. It refuses to rename something other entries still refer to, because that would write an invalid config. Updating those references automatically ispyrolab renamesilently orphans every reference to the renamed entity #67, which is still open.Startup (#54, #56)
pyrolabd.main():psshows entities appearing as they start.STARTUP_ERROR_FILEforupto show. The cleanup infinallyremoves the lockfile only if this process wrote it.Lockfile (#56)
os.replacemoves it into place. Readers see either no lockfile or a complete one.read_lockfile()returnsNonefor a missing or unreadable lockfile instead of raising aValidationError.utils.pid_is_running(). This replaces "the connection was refused, so delete it", which wrongly treated a busy daemon as dead. On Windows the check usesctypes, becauseos.killthere kills the process instead of checking it. No new dependency.pyrolab upcommands run at the same instant could both pass the "already running" check. Before, the lockfile was created up front with an exclusive create, but then left empty while the daemon started.os.replacegives up that exclusivity in exchange for atomic writes, which is the trade we chose.pyrolab upwaitsupwaits until the new daemon responds, 30 seconds by default (--timeout). If the daemon exits first,upprints why: for example, "invalid configuration in …: autolaunch refers to daemon 'typo', which is not defined". If it times out,upsays so and points topsand the logs.upwon't start another daemon while one is running, including one that's running but not responding. Before, an unresponsive daemon was treated as dead: its lockfile was deleted and a second daemon started next to it.Thread safety
ProcessManageris now used concurrently by Pyro's request threads, the checkup timer, and the background autolaunch. The methods that read or change its process tables hold a shared reentrant lock. Without it,psrunning during autolaunch could fail with "dictionary changed size during iteration".Found while testing: importing
pyrolab.apicould lock users outImporting
pyrolab.apireads the user config to set default nameserver settings. Once validation was strict, an invalid installed config made that import raise. Every CLI command would then crash, includingconfig update, the command needed to fix the file, and so would any client script. That import step is now best-effort: problems are logged as a warning. This also fixes two older crashes in the same code: a config with no nameservers (next()on an empty dict), and apublichost that can't be resolved on an isolated network (#80).Checks run
pid_is_runningfor this process, an exited process, and invalid PIDs. CI runs that on Windows too.uppath: success, stale lockfile, already running, running but unresponsive, startup error, unexplained exit, timeout;main()run in a thread over a real Pyro connection: it publishes the lockfile, autolaunches in the background, shuts down, and cleans up;main()with an invalid config, and when another daemon is running;pyrolab.apiwith invalid, unparseable and nameserver-less configs.master, the behavior tests fail: stale and partial lockfiles, config update with dangling references, renames that orphan references,infowith an invalid config, and the api import.config updaterejected the bad file;upreturned in 0.3 s and the services appeared inpsa few seconds later;upwas refused;upandinfoboth explained the problem, andconfig updatefollowed byuprecovered.🤖 Generated with Claude Code