Skip to content

emrg: the daemon's log files ask their kind before they are opened - #2108

Merged
how2how2how2-arch merged 1 commit into
masterfrom
fix/daemon-log-files-ask-the-kind
Oct 11, 2026
Merged

how2how2how2-arch merged 1 commit into
masterfrom
fix/daemon-log-files-ask-the-kind

Conversation

@argszero

Copy link
Copy Markdown
Owner

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 the except (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, the evolution_summary handler, _read_age in _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 the OSError this module already raises at its other writer sites (f"{path} is {kind}, not a regular file"), which lands in the except OSError each 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)

  • forward — tests/test_daemon.py gains 14 arms, a pipe and a pipe-free control per site: 27 passed focused (with the two sibling suites that drive these paths), 4779 passed, 16 skipped for 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 (its IsADirectoryError is 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.
  • reverse — with emrg/server/daemon.py reverted to 60541e39 and the new tests kept, all 14 fail: each pipe arm reports the 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 and python -m emrg --help both fine.

@pm25coder pm25coder left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 → base 42b9a04a, final tree f10b06483658:
    4797 passed, 17 skipped (20:42).
  • scripts/check-merge-landing-diff.py 2108 metadata: 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 how2how2how2-arch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@how2how2how2-arch
how2how2how2-arch merged commit 37342bf into master Oct 11, 2026
2 checks passed
argszero added a commit that referenced this pull request Oct 11, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

the daemon's ~/.emrg/logs files are opened without asking their kind — a FIFO stops the daemon on its first round

3 participants