A lock-free circular recorder of the raw process image, one record per RT cycle.
More...
|
| | ProcessDataRing ()=default |
| |
| | ~ProcessDataRing () |
| |
| | ProcessDataRing (const ProcessDataRing &)=delete |
| |
| ProcessDataRing & | operator= (const ProcessDataRing &)=delete |
| |
| void | allocate (uint32_t inputCap, uint32_t outputCap, size_t capacityCycles) |
| | (Re)allocates the ring for an image of inputCap / outputCap bytes per direction and capacity cycles, discarding any prior recording (sequence restarts at 0).
|
| |
| void | clear () |
| | Releases the storage and resets to the unallocated state. Idempotent.
|
| |
| bool | allocated () const |
| | Whether allocate ran with a non-zero capacity.
|
| |
| void | write (uint64_t timestampNs, int wkc, std::span< const uint8_t > inputs, std::span< const uint8_t > outputs) |
| | Records one cycle. RT, wait-free, single-writer. No-op until allocate.
|
| |
| uint64_t | head () const |
| | The producer's next sequence number; head()-1 is the newest recorded cycle, and head()==0 means nothing is recorded yet.
|
| |
| size_t | capacity () const |
| | Number of cycles the ring can hold.
|
| |
| uint64_t | oldestValidSeq () const |
| | The oldest sequence number still in the ring: max(0, head - capacity). A cursor below this was lapped (its data overwritten) and must resync to this value.
|
| |
| bool | readRecord (uint64_t seq, Record &out) const |
| | Copies the record for seq into out. Lock-free; retries are the caller's job.
|
| |
A lock-free circular recorder of the raw process image, one record per RT cycle.
The RT loop appends one record every cycle via write() and never blocks: a record is the whole raw input and output IOmap for that cycle plus its timestamp and working counter. The ring overwrites its oldest record on wrap, so it holds the most recent capacity() cycles — a flight-data recorder, not a drain queue. Any number of non-RT readers consume it through independent read cursors (a read index, not a tail): a reader never frees a slot and never gates the producer, so the RT loop overwrites unconditionally and may lap a slow reader. That is allowed and detected, not a bug — a lapped reader sees readRecord return false and resyncs from oldestValidSeq().
It is the single RT-written source for both the live monitoring stream (each monitoring holds a cursor and ships every record in [cursor, head)) and point reads of the freshest value (read head()-1) — one write per cycle, and one record holds both directions from the same cycle.
Sequencing: each record carries an absolute cycle sequence number; slot = seq % capacity. head() is the producer's monotonic next-seq (it never wraps). The per-slot sequence word is stored after the payload with release ordering — its publication is what makes a record readable — and a reader re-checks it after copying to detect a write that raced the copy (a per-slot sequence re-check). The RT write() is wait-free: a few memcpy plus two release stores.
Single writer (the RT loop), many concurrent readers. Not copyable or movable.
- Warning
- That guarantee covers
write() only, and is scoped to it deliberately: allocate and clear are a third kind of writer — they release the storage, so they cannot be made safe by any reader-side sequence check and are not covered by the lock-free protocol. The owner must exclude readers around them (DeviceManager does it with processDataMutex_: the control-plane callers hold it exclusively, every ring reader takes it shared). A reader that skips that lock is a use-after-free waiting for a re-map, not a torn read.