Detect superseded shmem segments via per-creation segment_uid - #193
Merged
Conversation
isOwnerAlive() relied solely on owner_pid to reject stale NATIVE CLIENT
segments. That fails when a persistent service owns the shared memory:
a long-running process (e.g. Orion) keeps the same PID as it tears down
and recreates a device's segments under the same name across sessions, so
owner_pid always reads "alive" even for a segment that belongs to a closed
session. A CLIENT holding the old mapping cannot tell it has been superseded.
Add a per-creation segment_uid to NativeConfigBuffer, stamped by the
STANDALONE owner at creation. isOwnerAlive() now applies two checks:
- Supersession: compare the uid in our mapped buffer against the uid of
whatever the segment name resolves to now (readCurrentSegmentUid()
re-opens + maps the current segment). Mismatch or missing name => stale.
- Crash-orphan: probe owner_pid, catching an owner that died without
unlinking (uid unchanged in that case).
Both are needed: pid catches crash-orphan (uid unchanged), uid catches
supersession (pid unchanged). A zero uid or pid skips that check for
backward compatibility with pre-liveness segments.
segment_uid is a portable token (steady_clock ^ pid ^ process-local
counter), not an inode: macOS returns st_ino == 0 for POSIX shm fds, so
the inode is unusable as a segment identity there.
Note: isOwnerAlive() is a one-shot check at attach. A client superseded
mid-stream won't notice until something re-checks; periodic polling belongs
in the client (pycbsdk/sdk_session) and is intentionally out of scope here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The initial supersession check read the segment uid live from the mapped buffer. That breaks on Windows: a named mapping persists while any handle is open, and CreateFileMapping on an existing name returns the SAME object, so a recreating owner reuses the same memory and overwrites segment_uid in place. A live-vs-current comparison then always matches itself, and the CI OwnerLivenessTest.ClientDetectsUnlinkedSegment / ClientDetectsSupersededSegment cases failed on windows-x64. Capture the uid once at attach (Impl::attached_segment_uid) and compare the snapshot against the current named segment's uid. This detects supersession on both platforms: - POSIX: the name resolves to a new inode/object (uid N2 != snapshot N1). - Windows: the reused object's uid is overwritten to N2 != snapshot N1. The pure "owner closed but never recreated, client still holds the mapping" case is only detectable on POSIX (shm_unlink removes the name immediately); on Windows the name and memory stay intact, which is correct behavior, so ClientDetectsUnlinkedSegment is now guarded #ifndef _WIN32. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cboulay
force-pushed
the
fix/cerelink-shmem-segment-uid-supersession
branch
from
August 3, 2026 19:12
06c55ce to
78cfffd
Compare
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.
Problem
ShmemSession::isOwnerAlive()rejected stale NATIVE CLIENT segments using onlyowner_pid. That check is blind when a persistent service owns the shared memory.Concretely, Orion (
orion/src/device/CereLink.cpp) is one long-lived process. Itsdisconnect()→m_session.reset()cleanly destroys the STANDALONEShmemSession(runningshm_unlink), and eachconnect()recreates the device's segments under the same name. Across every start/stop cycleowner_pidstays equal to Orion's PID, soisOwnerAlive()always reports "alive" — even for a segment that belongs to a session that was closed and superseded. A CLIENT holding the old mapping has no way to tell it is stale.Fix
Add a per-creation
segment_uidtoNativeConfigBuffer, stamped by the STANDALONE owner at creation.isOwnerAlive()now applies two checks:segment_uidmismatch (new)owner_piddead (existing)Both are needed — the PID catches the crash-orphan case (uid unchanged), the uid catches supersession (pid unchanged). A zero
segment_uidorowner_pidskips that check for backward compatibility with pre-liveness segments.readCurrentSegmentUid()re-opens the segment name and reads the uid of whatever it resolves to now, comparing against the uid in our mapped buffer.Why a token, not an inode
The natural identity would be the segment inode, but macOS returns
st_ino == 0for POSIX shm fds (verified), so inode is unusable there.segment_uidis a portable token:steady_clockstamp ^ pid ^ process-local atomic counter (never 0), stored in the buffer and read back by mapping the re-opened segment on both platforms.Out of scope
isOwnerAlive()is a one-shot check at attach (sdk_session.cpp~814). A client that attaches to the current segment and is superseded while streaming won't notice until something re-checks. That periodic liveness poll belongs in the client (pycbsdk/sdk_session) and is intentionally deferred — tracked in a separate issue.Tests
New
OwnerLivenessTestcases:StandaloneWritesSegmentUid,ClientDetectsUnlinkedSegment,ClientDetectsSupersededSegment.cbshm_tests: 104/104 pass; full project builds clean.🤖 Generated with Claude Code