Expand description
SharedSemaphore - cross-process counting semaphore.
Three MMF files compose the primitive:
<base>.count.bin- SharedAtomicU32: available permits.<base>.wakeup.bin- SharedAtomicU64: monotonic generation bumped on everyreleaseto wake waiters.<base>.waiters.bin- SharedAtomicU32: count of currently- waiting acquirers. Releasers consult it to skip the wakeup bump when there are no waiters (saves an atomic store on the uncontended path).
§Protocol
acquire:
- Load
count. If > 0, try CAS to decrement; on success, return. - On 0 (or CAS lost), increment
waiters, snapshotwakeup, re-checkcount, then yield/sleep until eithercount > 0ORwakeupadvances. Loop back to 1.
release:
count.fetch_add(1, AcqRel).- If
waiters.load(Acquire) > 0,wakeup.fetch_add(1, Release)to wake at least one waiter.
try_acquire: a single CAS pass; never spins, never sleeps.
§Why no real wait queue ring?
Linux futex semantics are “wake N waiters”; an acquirer just needs to know “something changed.” A generation counter gives that exactly. Adding a ring of waiter PIDs only helps if you need strict FIFO fairness, which most cross-process resource limiters do NOT. The generation-counter design is simpler, has zero allocation, and matches the semantics of every modern OS semaphore primitive (which all coalesce identical wakeups internally).
§Permit RAII
acquire / try_acquire return a Permit guard tied to the
semaphore. Dropping the permit releases the count. For cross-
thread / cross-process ownership (e.g., handing a permit to a
background task), use the standalone release API:
mem::forget(permit) then sem.release() from the new owner.
Structs§
- Permit
- RAII permit guard. Dropping releases one permit back to the
semaphore. Use
mem::forget(permit)+sem.release()to transfer ownership. - Shared
Semaphore