Repository navigation
Per-user data directories, one process-safe log, no import side effects - #95
Merged
Merged
Conversation
- Files (#66): configuration, logs, runtime state, nameserver data, and cache live in per-user platformdirs directories (new dependency platformdirs), or all under PYROLAB_DATA_DIR, instead of inside the installed package. Importing creates nothing; directories are made where files are written. The first command or daemon start after upgrading copies the old config and nameserver databases over, once. `pyrolab --data` lists every location. - One log (#64, #25): the background daemon is the only writer of a single rotating JSON-lines log (5 MB x 5). Child processes send their records over a pipe each; a shared queue (loguru's enqueue=True or QueueHandler) deadlocks every process's logging when a child is killed mid-write, which stopping hung processes does. - Library hygiene (#63): importing pyrolab adds only a NullHandler; it no longer configures the root logger, sys.excepthook, or warning filters. The CLI (new run() entry point) and daemon set those for themselves. - Reading logs (#71, #36): new `pyrolab status` (is the daemon running, and what went wrong since it started) and `pyrolab logs show`; `logs export` and `logs clean` rewritten for the JSON-lines log, which can't crash on a malformed entry. - get_ip (#80): falls back to the hostname's address, then 127.0.0.1, with a warning, instead of failing without a route outside. Docs explain choosing `host` for clients on multi-homed machines. Closes #25 Closes #36 Closes #63 Closes #64 Closes #66 Closes #71 Closes #80 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, a spawned child receives its end of the log pipe by taking over a handle the parent duplicated for it. A child killed while still starting never does, so the parent keeps an open write end, the pipe never reports closed, and the collector thread (and handle) leaked. The Windows 3.14 CI job caught it in the kill test. collect_from_pipe now takes an `alive` check (the manager passes runner.is_alive): it keeps reading while data remains, so a crashed child's last records still arrive, and stops once the child is dead and the pipe is drained. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_collector_reads_everything_before_stopping sent 50 records into the pipe before starting the reader. Linux buffers that; on Windows (before Python 3.14) the sends block with no reader, so the test hung until the CI timeout, which reported exactly where. The collector now runs while the records are sent, and the child "dies" only afterwards. 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.
Moves PyroLab's files out of the installed package and replaces per-process log files with one log that's safe across processes. Importing
pyrolabno longer has side effects. Closes #25, #36, #63, #64, #66, #71, #80. Targetsmaster; it doesn't depend on any other PR.Where files live (#66)
platformdirs, the new dependency (>=3.0; it has no dependencies of its own). On Windows that's%LOCALAPPDATA%\pyrolab; on Linux,~/.config/pyrolaband~/.local/state/pyrolab. SettingPYROLAB_DATA_DIRputs everything in one folder instead.pyrolab --datalists every location.pip install --upgradeno longer wipes anyone's config, and a read-only install no longer fails on import.pyrolabcommand or daemon start after upgrading copies the oldpyrolab/data/localconfig and nameserver databases to the new locations, and says so. It never overwrites anything, and leaves the originals in place. A marker file stops it repeating, so a config removed withconfig resetisn't brought back. Logs and runtime state aren't copied.One log, safe across processes (#64, #25)
pyrolab.logthat rotates at 5 MB and keeps 5 old files. Before, each process wrote its ownpyrolab_<pid>.log(166 in one checkout), rotating at 30 KB.logs.log_to_pipein the child,logs.collect_from_pipein the daemon).enqueue=Truelooked attractive. They work fine until a child is killed partway through writing a log record. The killed child dies holding the shared queue's lock, and from then on every other process blocks the moment it logs, including the background daemon. Make process lifecycle robust: escalating stop, restart backoff, non-fatal registration #93 kills hung children by design, so this would happen eventually. In my tests, both deadlocked in the first round of 20 underspawnandfork, while the pipe-per-child design survived all 20.test_logs.pynow checks this with real processes.Importing
pyrolabchanges nothing global (#63)NullHandlerto thepyrolablogger. It no longer adds a handler to the root logger, replacessys.excepthook, or changes warning filters.pyrolabcommand, through a newcli:runentry point, and the daemon set these up for themselves. CLI commands print log messages only whenPYROLAB_LOGLEVELis set. Client scripts that want Pyro's remote tracebacks now opt in themselves; the docs show how.Reading the log (#71, #36)
pyrolab status(new, Getting errors from the logs shortcut #36's request): whether the daemon is running, and the warnings and errors since it started, one line each. It points tologs showfor full tracebacks.pyrolab logs show(new): the log since the daemon last started, with--level,--allfor earlier runs, and--lines.pyrolab logs export FILE: text by default, or--json, across rotated files oldest first. Lines it can't read are counted and skipped instead of crashing the command (pyrolab logs exportcrashes on any log entry without a parseable timestamp #71).pyrolab logs clean: refuses while the daemon is running, since the daemon is writing the log.get_ip()(#80)127.0.0.1. It logs a warning that nameshost: publicas the setting to change.get_ip()exists forhost: public. It picks the address a nameserver or daemon listens on and publishes in its URIs, so it has to be an address clients can reach. Clients connect to the PyroLab server, never to instruments, so the issue's "instrument subnet" point didn't apply. For a server on several networks, the docs now say to sethostto the address on the clients' network.Also
envvars.rstbecame "Files, Logs, and Environment Variables".configuration.rstgained a section on choosinghost, andclients.rsta note on remote tracebacks.try_itr(two copies), the commented-outcondense_logs, and an unused nameserver constant.data_dirfixture is now autouse, so no test can touch real per-user folders or copy a developer's real old files.Found by Windows CI
collect_from_pipe(alive=...)). Only the Windows 3.14 job caught it.Checks run
forkand Unix-socket variants). pre-commit passes on every file.HOMEand theXDG_*folders pointed at an empty directory. Nothing was written there.excepthook, warning filters and created folders.masterfails every one of those checks.spawnandfork:uv buildplustwine check --strictpass. The wheel requiresplatformdirs>=3.0,pyrolabpoints atpyrolab.cli:run, and no runtime files are bundled.PYROLAB_DATA_DIRset to an empty folder):upautolaunched a healthy daemon and one whose service crashes on import, and records from all of them landed in the singlepyrolab.log, with the crash tracebacks from the child processes;status,logs showandlogs exportworked;pyrolab.logandpyrolabd_output.log;downandlogs cleanleft nothing behind.🤖 Generated with Claude Code