Add: check the mailbox wire constants against the C++ enums at import - #1765
Conversation
The mailbox state word and the preparation-disposition word are a cross-process contract: a parent writes them and its forked child reads them through shared memory. Both sides declared the values independently -- MailboxState and MailboxPreparationDisposition in worker_manager.h, and thirteen plus two hand written module constants in simpler/worker.py -- with nothing tying the two declarations together. That combination has no failure signal. The declarations are in different languages, so a divergence is not a compile error; the resulting value is a legal state, just a different one, so it is not corruption either. A parent storing 9 for TASK_LAUNCHED against a child reading 19 produces a child that waits on a state its parent never publishes. It surfaces as a hang or a misrouted frame, at a distance from the edit that caused it. The binding now exports both enums as name-to-value tables, next to the four mailbox constants worker.py already imports from it for exactly this reason. simpler.worker keeps its own literals -- they are read on the mailbox polling path, and an attribute lookup per poll is not worth paying -- and checks them against the native tables once at import. The check reports every disagreement in one message rather than the first, since a renumbering usually moves several values at once, and separately rejects an enumerator the native side has and Python does not: that is a state a child can legitimately publish and this module would not recognise. Writing the check surfaced one such gap immediately. MailboxPreparationDisposition has a NONE member that Python never declared, because the parent writes it when it claims a frame and no child ever publishes it. Python is right to reject NONE as a *published* disposition, so the constant is now declared with that distinction stated rather than the check being loosened to ignore it. Renumbering is what this cannot allow, and nothing here renumbers anything: the change is additive on the C++ side and a check plus one new constant on the Python side. Tests pin both directions -- that every declared value matches the native table, and that the guard actually rejects a divergence rather than passing it through. The rejection cases restore the value they perturb so the rest of the suite is unaffected. Verification: the guard was confirmed against a real divergence, not a simulated one -- renumbering TASK_LAUNCHED from 9 to 19 in worker_manager.h and rebuilding made `import simpler.worker` fail with both values named; the enum was then restored and rebuilt. 1311 Python unit tests, 92/92 C++ unit tests (ctest -LE requires_hardware), pyright and clang-format clean. Refs #1764. This is the drift-guard half of that issue; the MailboxState enum split remains open there.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe native bindings now export mailbox enum values. Python re-exports these tables and validates its mailbox constants during worker import. Unit tests cover matching values, renumbered constants, and undeclared enumerators. ChangesMailbox wire validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
The mailbox state word and preparation-disposition word are a cross-process contract — a parent writes them, its forked child reads them through shared memory. Both sides declared the values independently, with nothing tying the two declarations together:
MailboxState(13 values) andMailboxPreparationDispositioninworker_manager.hsimpler/worker.pyThat combination has no failure signal. The declarations are in different languages, so a divergence is not a compile error. The resulting value is a legal state, just a different one, so it is not corruption either. A parent storing
9forTASK_LAUNCHEDagainst a child reading19produces a child waiting on a state its parent never publishes — a hang or misrouted frame, far from the edit that caused it.This is the drift-guard half of #1764. The
MailboxStateenum split stays open there.Approach
The binding now exports both enums as name→value tables, right next to the four mailbox constants
worker.pyalready imports from it for exactly this reason (worker_bind.h:838-841). The pattern isn't new — it just didn't cover the enums.simpler.workerkeeps its literals — they're read on the mailbox polling path and an attribute lookup per poll isn't worth paying — and checks them against the native tables once at import. It reports every disagreement in one message rather than the first, since a renumbering usually moves several values at once, and separately rejects an enumerator the native side has that Python doesn't: that's a state a child can legitimately publish and this module wouldn't recognise.Nothing is renumbered. The change is additive on the C++ side, and a check plus one new constant on the Python side.
What writing the check immediately found
MailboxPreparationDisposition::NONEexists in C++ but was never declared in Python — the parent writes it when claiming a frame, and no child ever publishes it. Python is right to rejectNONEas a published disposition (worker.py:2848), so I declared the constant with that distinction stated rather than loosening the check to ignore it.Testing
ctest -LE requires_hardware)The guard was verified against a real divergence, not a simulated one. I renumbered
TASK_LAUNCHEDfrom 9 to 19 inworker_manager.h, rebuilt, and confirmedimport simpler.workerfails with both values named:The enum was then restored and rebuilt. Tests pin both directions — that every declared value matches, and that the guard rejects a divergence rather than passing it through — with the perturbation cases restoring what they touch so the rest of the suite is unaffected.
Onboard sweeps were not run: no runtime behavior changes, and the added code executes once at import.