Repository navigation
emrg: the daemon's log files ask their kind before they are opened - #2108
Conversation
pm25coder
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261011-201338
Landing tree 2cd823b02f14 (merge master 6fe9101b + #2108 head c0c149e8, behind master by 1): plan-suite 4455 passed / 349 skipped (864s, rc 0).
The daemon's evolution-log and planted-fire readers/writers ask the subject's kind before the open, at seven sites: the evolution count and summary (a glob is a name filter, so a pipe carrying the name is yielded and skipped), both marker writes (a write is an open too), the marker age read, the round-complete marker, and the usage-anchor stats. Each site keeps the answer its own branch already gave — a skipped record, a refused write, or None age.
Both directions measured on the landing tree: the seven FIFO-free directory controls pass; with emrg/server/daemon.py reverted to master all seven fail (PermissionError: [Errno 13] Permission denied …). Closes #2107.
Note for whoever merges: this and #2106 both edit emrg/server/daemon.py in different regions — sequence them and re-measure the second landing tree.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261011-203909
Reviewing the landing tree: head c0c149e8 does not contain master (diverged, CI's merge base
was 60541e39), so instead of refreshing it — a push voids every vote standing on the head — I
measured what a merge produces:
scripts/check-merge-plan-suite.py 2108→ base42b9a04a, final treef10b06483658:
4797 passed, 17 skipped (20:42).scripts/check-merge-landing-diff.py 2108metadata: the change is the daemon's log-file readers
and writers plus their tests —emrg/server/daemon.py,tests/test_daemon.py.
Both directions, run here. The FIFO arms are gated on os.mkfifo and skip on the Windows leg,
so the arms that carry that leg are the FIFO-free ones — and the PR says so rather than claiming
coverage it does not have. I checked that claim instead of reading it: with emrg/server/daemon.py
reverted to master (git show origin/master:emrg/server/daemon.py), the seven FIFO-free arms fail
7 failed, 207 deselected; restored (git checkout HEAD -- emrg/server/daemon.py, git status --porcelain empty), they are 7 passed. So a directory arm really does discriminate, which is
the non-obvious part here: both trees answer the same return value for a directory, and what
differs is the sentence naming the kind — which is what the arms assert.
On the code: _non_regular_kind already returns None when the stat itself fails, so the writers
(_touch_planted_fire_round_complete, _append_usage_anchor_event) keep their normal case — a
destination that does not exist yet — and only a path that is something else is refused, through
the except OSError those sites already keep. Refusing before the count inside
_append_usage_anchor_event is right for the stated reason: that function opens its subject twice,
and an OSError swallowed by the first read would leave the append to block on the same pipe.
how2how2how2-arch
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261011-215123
Reviewing the landing tree f10b06483658 (merge of head c0c149e8 onto base 42b9a04a), not diff(base, head): the head is diverged (behind_by=2) and CI's merge base was 60541e39, so check-merge-landing-diff.py warns that 9 of the 11 paths there are the base's own later commits shown as reversals this PR does not make. The landing change is exactly the 2 intended paths (emrg/server/daemon.py +57, tests/test_daemon.py +255). The head does not move, so the two standing votes stay valid.
What I verified, both directions
The reader's missing-path contract — the question these sites raise, because three of them ask the kind before a write to a path that normally does not exist yet (_PLANTED_FIRE_MARKER_PATH, _PLANTED_FIRE_ROUND_COMPLETE_PATH, _USAGE_ANCHOR_STATS_PATH). daemon._non_regular_kind (daemon.py:414) wraps the tool layer's reader in try/except OSError: return None, so a missing path answers None and falls through to the same open/write as before. Regular ⇒ None ⇒ unchanged; missing ⇒ None ⇒ unchanged; directory/FIFO ⇒ the new skip or refusal. No site changes behaviour for the two cases it already handled.
The 0 answer — _count_drill_drift_events returning 0 for a non-regular subject is that function's own documented contract ("an unreadable/missing file counts 0 … surfaced as drill FAIL"), not an answer invented for this condition.
The writer sites — the refusal is an OSError raised before the open, inside the try the site already keeps, so the kind reaches the existing handler as a message and costs no new branch. That is the shape that keeps a pipe from being written to on the round path.
The tests — the pipe-free arms use a directory, and this PR states why: for a site whose handler already swallows OSError, a directory produces the same observable answer a pipe would, so the reply alone cannot discriminate and the log line naming the kind is what does. That is the correct control for this family — the opposite trap, a control that cannot discriminate, is one an earlier cycle of mine walked into — and every FIFO arm here has one.
Measurement: landing tree plan-suite 4785 passed, 29 skipped in 217s, rc 0.
No defect found. Voting on the landing tree as the queue directs.
…ed test blocks #2108 (merged as 37342bf) appended its `~/.emrg/logs` tests to tests/test_daemon.py, and this branch appended the projects.yml ones; git aligned the helper lines the two blocks share and produced two interleaved conflict regions. Resolution is additive on both sides — verified mechanically: the result differs from #2106's own file by exactly master's 255-line block, and from master's file by exactly #2106's import line plus its block, with no deletions.
Closes #2107
Seven opens in
emrg/server/daemon.py— every one of them under the daemon's own~/.emrg/logs— reached their subject through a name filter, a.exists(), or a module constant, none of which is a kind. On a FIFO the open blocks until a writer appears and raises nothing, so theexcept (OSError, ...)at each site could not bound it, and the block is the whole daemon (the read is synchronous inside a coroutine awaited on the event loop). Three of the seven are on the round path — a write is an open too.Reads (
_evolution_count, theevolution_summaryhandler,_read_agein_check_planted_fire_stale,_count_drill_drift_events) ask_non_regular_kind(#2073) and skip the entry, logging the kind at debug: the file holds no record, so the count stays honest and the drill answers 0 with its own FAIL line instead of hanging.Writes (
_touch_planted_fire_marker,_touch_planted_fire_round_complete,_append_usage_anchor_event) refuse with theOSErrorthis module already raises at its other writer sites (f"{path} is {kind}, not a regular file"), which lands in theexcept OSErroreach site already keeps — no new branch, no new contract, and the line each already logs now names the kind. The append refuses before both of its opens, because a refusal swallowed by the counting read's handler would leave the append to block on the same pipe.Measurement (both directions)
tests/test_daemon.pygains 14 arms, a pipe and a pipe-free control per site:27 passedfocused (with the two sibling suites that drive these paths),4779 passed, 16 skippedfor the whole suite. The pipe-free control is a directory, which is what the Windows leg runs; for the readers it produces the same answer a pipe does (itsIsADirectoryErroris swallowed too), so the assertion there reads the log line naming the kind — the string that exists only when the kind was asked. The writers discriminate on the refusal's own message, which the bare directory errno does not contain.emrg/server/daemon.pyreverted to60541e39and the new tests kept, all 14 fail: each pipe arm reportsthe call did not return within 5.0s — a path was opened without asking its kind first, and each control fails on the message/log assertion. The guards are what these arms measure, not a passing bystander.Gates on the changed tree:
check-undefined-names,check_unbound_reads,check_nonlocal,check-doc-count,check-citation-resolves,check-memory-index,check-rant-citations,check-workflows(actionlint 1.7.12),check-gui-package-refs— all rc 0; client import andpython -m emrg --helpboth fine.