Skip to content

Fix the seqlock's writer and reader barriers to match tyndall - #2

Merged
johannesschrimpf merged 2 commits into
mainfrom
js/seqlock-memory-ordering
Sep 4, 2026
Merged

johannesschrimpf merged 2 commits into
mainfrom
js/seqlock-memory-ordering

Conversation

@johannesschrimpf

@johannesschrimpf johannesschrimpf commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #1, which was the layout. This is the ordering.

Two of tyndall's four seqlock barriers were missing:

  • Writer. The odd seq increment was Release, which orders what comes before it, not the payload stores after it. tyndall issues smp_wmb() there. Now AcqRel.
  • Reader. There was no barrier between the entry read and the second seq load, so the read can drift past the validation. tyndall issues smp_rmb() there. Now fence(Acquire).

Either one lets a reader accept a torn record on ARM, silently. be-node-drone is about to be the writer on 28 topics at 100 Hz.

On aarch64 the fix is one instruction each:

before after
writer's first increment ldaddl ldaddal
reader, before the second load nothing dmb ishld

dmb ishld is what tyndall's smp_rmb() expands to on that target.

The new stress test is not the proof of this; the code generation is. With the writer's barrier reverted the test still passes on an Apple M-series host, and it could never fail on x86. It is there to catch gross regressions, and its limits are written at the test.

Second commit makes cargo clippy --all-targets -- -D warnings pass, which it did not on main. Mechanical: format arguments inlined, the example's 3.14 becomes PI, and the virtual workspace states resolver = "2", which its edition-2021 members already imply.

Version to 0.3.1.

🤖 Generated with Claude Code

johannesschrimpf and others added 2 commits September 4, 2026 13:03
Two of tyndall's four seqlock barriers were missing here. Both are silent
when they go wrong: a torn record reaching a consumer, not a crash.

The writer stored the odd sequence number with a Release increment.
Release orders what comes *before* the increment, which is the previous
write, and does nothing about the payload stores that follow it. On a
weakly ordered machine those stores may become visible first, and a
reader that samples seq either side of them sees the same even number
over a half-written entry and accepts it. tyndall issues smp_wmb() there;
AcqRel is the equivalent, and the acquire half is what keeps the payload
below the increment.

The reader loaded the second sequence number with Acquire, which orders
what comes *after* the load, not the entry read before it. Without a
barrier the entry read may drift past the validation and pick up bytes
from the next write. tyndall issues smp_rmb() there; fence(Acquire) is
the equivalent.

On aarch64 the difference is visible in one instruction each: the
writer's increment becomes ldaddal instead of ldaddl, and the reader
gains a dmb ishld, which is literally what tyndall's smp_rmb() expands
to on that target.

This matters now because be-node-drone is about to become the writer on
28 topics at 100 Hz on ARM, feeding C++ readers.

Adds a stress test with one writer and three readers over a payload wide
enough to tear. Its limits are documented at the site: with the writer's
barrier reverted it still passes on an Apple M-series host, so the
evidence for the fix is the code generation and the test is there for
gross regressions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`cargo clippy --all-targets -- -D warnings` failed on main and would have
kept failing after the barrier fix, which makes the lint useless as a
gate. All of it is mechanical:

* `uninlined_format_args` in the shm-name builder, the open log line, and
  the two examples: the arguments move into the format string;
* `approx_constant` on the example writer's `3.14`, which is now
  `std::f32::consts::PI` — it was standing in for pi anyway;
* the virtual workspace left `resolver` unset while every member is
  edition 2021, so cargo warned on every invocation. Stating `resolver =
  "2"` is what the members already imply.

No behaviour changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@johannesschrimpf
johannesschrimpf merged commit d8708d2 into main Sep 4, 2026
@johannesschrimpf
johannesschrimpf deleted the js/seqlock-memory-ordering branch September 4, 2026 12:10
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.

2 participants