FIFO Shared Memory
Warning
Experimental Feature — The FIFO shared memory mode is experimental and is not enabled by default. All shared memory segments default to a FIFO depth of 1 (standard single-frame behaviour).
When FIFO mode is enabled, the new C, C++, and Python FIFO APIs must be used to read and write
data. Using legacy API calls (e.g., accessing image->array.V directly, or calling the old
get_data() / get_frame() without understanding FIFO semantics) will produce unexpected
behaviour because the underlying memory layout has changed.
Windows is not yet fully tested. The FIFO extension has only been fully tested on Linux and macOS. Windows code which relies on FIFO shared memory should work, but you should be aware that unexpected behaviour is possible.
Overview
The standard DAO shared memory model stores exactly one frame per shared memory segment. The FIFO
extension adds a circular buffer of N independent frames (segments) inside a single shared memory
object. Each segment has its own copy of IMAGE_METADATA, so timestamps, counters, and the
write flag are tracked independently.
A writer advances a shared fifo_last_written index every time a new frame is committed.
Each reader keeps its own private read-tail (fifo_last_read / fifo_last_read_cnt0) that
lives in the local IMAGE struct and is therefore never visible to other processes. Readers can:
consume frames one-by-one in order (FIFO / sequential mode),
always jump to the newest available frame (latest-only / “newest” mode),
read arbitrary historical segments by index.
Note
Because the read-tail is local to each IMAGE struct, multiple independent readers can each
maintain their own position without interfering with each other or with the writer.
Memory Layout
A FIFO SHM object allocates contiguous memory for all N segments in one go:
[ IMAGE_METADATA[0] | IMAGE_METADATA[1] | ... | IMAGE_METADATA[N-1] ]
[ data segment [0] | data segment [1] | ... | data segment [N-1] ]
IMAGE_METADATA[0].fifo_sizestores the depth N and is the authoritative value for all processes.IMAGE_METADATA[0].fifo_last_writtenis the index of the segment most recently committed by the writer.Each
IMAGE_METADATA[i]holds the counters and flags for segment i.
For a depth-1 FIFO the layout is identical to the legacy format, so existing code that was compiled against the new headers will continue to work as before, as long as it only interacts with depth-1 SHMs. To use SHMs with FIFOs deeper than 1 segment, the code must be updated to use the new FIFO-aware functions.
Return Codes
The FIFO API introduces two new return codes in addition to the existing DAO_SUCCESS,
DAO_ERROR, and DAO_TIMEOUT:
Constant |
Value |
Meaning |
|---|---|---|
|
|
The reader’s tail was lapped by the writer. The tail has been reset to the newest segment. The data pointer returned is still valid but some frames were skipped. |
|
|
No new segment is available yet (the reader is already at the head of the FIFO). |
Creating a FIFO Shared Memory Segment
C API
Use daoShmImageCreate_FIFO instead of daoShmImageCreate:
#include "dao.h"
IMAGE image;
uint32_t size[2] = {256, 256};
uint32_t fifo_depth = 8; // keep the last 8 frames
// Create a FIFO shared memory segment with depth 8
daoShmImageCreate_FIFO(&image, "/tmp/mydata.im.shm",
2, // naxis
size,
_DATATYPE_FLOAT,
1, // shared = 1 (POSIX shared memory)
0, // no keywords
fifo_depth);
Note
daoShmImageCreate is a thin wrapper around daoShmImageCreate_FIFO with fifo_size = 1
and continues to work as before.
C++ API
Pass the optional depth argument to the Dao::Shm constructor:
#include "daoShm.hpp"
// 256×256 float32 SHM with 8-frame FIFO depth
Dao::Shm<float> shm("/tmp/mydata.im.shm", {256, 256}, nullptr, /*depth=*/8);
Python API
Pass the depth keyword argument when creating a shm object:
import numpy as np
from daoShm import shm
data = np.zeros((256, 256), dtype=np.float32)
# Create SHM with FIFO depth of 8 frames
shm_obj = shm("/tmp/mydata.im.shm", data=data, depth=8)
Writing Data
All existing write functions (daoShmImage2Shm, set_frame(), set_data() etc.) have been
updated to advance the FIFO automatically — no API change is required on the writer side. Each
call internally increments fifo_last_written and writes into the next circular slot.
Reading Data
C API
Function |
Description |
|---|---|
|
Returns the most recently written segment. Equivalent to the old |
|
Advances the reader’s tail by one and returns the next unread segment. Returns |
|
Blocks (using the counter semaphore) until a new segment is written, then returns. Call |
|
Returns a pointer to a specific FIFO slot by index (wraps modulo |
|
Checks whether the segment last read by this tail has since been overwritten. Returns |
|
Resets the reader’s tail to the newest segment. Useful after an overwrite or on initialisation. |
Sequential reading example (C):
#include "dao.h"
IMAGE image;
daoShmShm2Img("/tmp/mydata.im.shm", &image); // open existing SHM
void *segment_ptr = NULL;
uint32_t segment_idx = 0;
uint64_t segment_cnt0 = 0;
while (1) {
// Block until the next frame arrives
daoShmWaitForNextSegment(&image);
int_fast8_t status = daoShmGetNextSegment(&image, &segment_ptr,
&segment_idx, &segment_cnt0);
if (status == DAO_OVERWRITE) {
fprintf(stderr, "Warning: frame(s) were overwritten – reader is too slow\n");
}
float *data = (float *)segment_ptr;
// process data …
}
Latest-only reading example (C):
daoShmGetNewestSegment(&image, &segment_ptr, &segment_idx, &segment_cnt0);
float *data = (float *)segment_ptr;
C++ API
Method |
Description |
|---|---|
|
Returns the newest segment. Existing synchronisation options (semaphore, counter spin) still work. |
|
Advances the tail and returns the next unread segment. |
|
As above but also fills |
|
Returns a pointer to an arbitrary FIFO slot. |
|
Returns |
|
Returns |
|
Returns a pointer to |
Sequential reading example (C++):
#include "daoShm.hpp"
Dao::Shm<float> shm("/tmp/mydata.im.shm"); // open existing SHM
while (true) {
int_fast8_t status;
float *data = shm.get_next_frame(/*wait=*/true, status);
if (status == DAO_OVERWRITE) {
std::cerr << "Warning: frame(s) overwritten – reader is too slow\n";
}
// process data …
}
Python API
Method |
Description |
|---|---|
|
Returns the newest segment as a NumPy array. |
|
Advances the tail and returns the next unread segment. Returns |
|
Returns the segment at the given FIFO index as a NumPy array. |
|
Returns the last num_items segments stacked along axis 0 as a single NumPy array. |
|
Returns metadata for the newest segment ( |
Sequential reading example (Python):
from daoShm import shm
shm_obj = shm("/tmp/mydata.im.shm") # open existing SHM
while True:
status, data = shm_obj.get_data_next(wait=True)
if status == shm.DAO_OVERWRITE:
print("Warning: frame(s) overwritten – reader is too slow")
elif status == shm.DAO_NOTREADY:
continue # no new frame yet
# process data (NumPy array) …
Reading recent history (Python):
# Retrieve the last 4 frames as a (4, 256, 256) NumPy array
history = shm_obj.get_history(num_items=4)
Overwrite Detection
Because the FIFO is a circular buffer, a slow reader can be lapped by a fast writer. There are two mechanisms to detect this:
Inline with
get_data_next/get_next_frame/daoShmGetNextSegment: The return value isDAO_OVERWRITEwhenever the tail was behind the writer by more than one full FIFO cycle. The tail is automatically reset to the newest segment.Explicit check via
check_segment_overwrite/daoShmCheckSegmentOverwrite: Checks whether thewriteflag orcnt0of the last-read slot has changed since the read. Useful if you copy data out of shared memory and then want to validate the copy.
To avoid overwrite events, increase the FIFO depth or reduce the time between reader calls.
FIFO Depth Considerations
The primary motivation for using a FIFO depth greater than 1 is retaining a history of frames in shared memory. Two typical use-cases drive the choice of depth:
- Telemetry and diagnostics
A telemetry or logging process can read back the last N frames at any time using
get_history()/daoShmGetArbitrarySegment. This gives a rolling window of recent data without requiring a separate ring-buffer or file-write on every cycle. The depth should cover at least the longest expected gap between telemetry reads.- Algorithms that depend on previous results
Many adaptive optics algorithms (predictive controllers, integrators, temporal filters) need access to one or more past frames at each iteration. Rather than maintaining separate history buffers inside the component, the FIFO provides this history directly in shared memory, accessible to any process that holds an
IMAGEhandle. Choose a depth equal to the number of past frames the algorithm requires, plus a margin for scheduling jitter.
The FIFO depth multiplies the shared memory allocation:
Total size ≈ N × (sizeof(IMAGE_METADATA) + nelement × sizeof(element_type))
+ semaphores + keywords
Typical guidance:
Depth 1 — identical to standard (non-FIFO) behaviour. No overhead.
Depth 2–8 — sufficient for algorithms requiring a small number of past frames (e.g., a first- or second-order temporal filter).
Depth 10–100 — suitable for telemetry windows, longer predictive filters, or components that process data in bursts rather than frame-by-frame.
Depth > 100 — feasible for large telemetry histories; bear in mind that the entire buffer lives in RAM. For very long histories consider whether a dedicated file-based ring buffer is more appropriate.
Thread Safety
The writer side (
daoShmImage2Shmand equivalents) is safe to call from a single writer thread. Concurrent writers are not supported.Multiple readers are safe because each reader’s tail is stored in its own local
IMAGEstruct. Readers never modify shared memory.The
writeflag in each segment’sIMAGE_METADATAprovides a lightweight consistency indicator but is not a full memory barrier. UsedaoShmCheckSegmentOverwriteif strict data integrity is required.
Backwards Compatibility
All existing code that creates SHM with daoShmImageCreate (depth 1) will continue to compile
and run correctly — the function now delegates to daoShmImageCreate_FIFO with fifo_size = 1.
Code that loads an existing SHM (daoShmShm2Img) reads fifo_size from the metadata and
adjusts all internal offsets automatically, so readers compiled with the new headers can open both
depth-1 and depth-N segments transparently.
Warning
SHM segments created with the old library (before the FIFO branch was merged) have
fifo_size = 0 in memory, which the new loader interprets incorrectly. Delete and recreate
any existing shared memory files after upgrading to the FIFO-enabled build.