NAME Data::RingBuffer::Shared - Shared-memory fixed-size ring buffer for Linux SYNOPSIS use Data::RingBuffer::Shared; my $ring = Data::RingBuffer::Shared::Int->new(undef, 100); $ring->write(42); $ring->write(99); say $ring->latest; # 99 (most recent) say $ring->latest(1); # 42 (previous) say $ring->size; # 2 # overwrites oldest when full -- never blocks $ring->write($_) for 1..200; say $ring->size; # 100 (capacity) say $ring->latest; # 200 # read by sequence number my $seq = $ring->write(777); say $ring->read_seq($seq); # 777 # wait for new data my $cnt = $ring->count; $ring->wait_for($cnt, 5.0); # F64 variant my $f = Data::RingBuffer::Shared::F64->new(undef, 1000); $f->write(3.14); # dump entire ring as list (oldest first) my @vals = $ring->to_list; DESCRIPTION Fixed-size circular buffer in shared memory. Writes overwrite the oldest entry when the buffer is full -- writes never block or fail. Readers access data by relative position (0=latest) or absolute sequence number. Unlike Data::Queue::Shared (consumed on read, blocks when full) and Data::PubSub::Shared (subscription tracking), RingBuffer is a simple overwriting window with no consumer state. Useful for metrics rings, sensor data, rolling windows, debug traces. Linux-only. Requires 64-bit Perl. Variants "Data::RingBuffer::Shared::Int" - int64_t values "Data::RingBuffer::Shared::F64" - double values METHODS Constructors $r = Data::RingBuffer::Shared::Int->new($path, $capacity); $r = Data::RingBuffer::Shared::Int->new(undef, $capacity); $r = Data::RingBuffer::Shared::Int->new_memfd($name, $cap); $r = Data::RingBuffer::Shared::Int->new_from_fd($fd); The descriptor you pass to "new_from_fd" is duplicated ("F_DUPFD_CLOEXEC"), so it stays yours to close and closing it does not disturb the buffer. Write my $seq = $ring->write($value); # returns sequence number Always succeeds. Overwrites oldest when full. Read my $val = $ring->latest; # most recent (undef if empty) my $val = $ring->latest($n); # nth most recent (0=latest) my $val = $ring->read_seq($s); # by sequence (undef if overwritten) my @all = $ring->to_list; # entire ring, oldest first Status $ring->size; # entries in buffer (max = capacity) $ring->capacity; $ring->head; # next write position (monotonic) $ring->count; # total writes (for wait_for) Waiting my $ok = $ring->wait_for($expected_count); # block until count changes my $ok = $ring->wait_for($expected_count, $timeout); Returns 1 if new data arrived (count != expected), 0 on timeout. Lifecycle $ring->clear; # reset head/count (NOT concurrency-safe) $ring->sync; $ring->unlink; $ring->path; $ring->memfd; $ring->stats; eventfd $ring->eventfd; $ring->eventfd_set($fd); $ring->fileno; $ring->notify; $ring->eventfd_consume; BENCHMARKS Single-process (1M ops, x86_64 Linux, Perl 5.40, cap=1000): Int write 11.7M/s Int latest 11.1M/s Int read_seq 10.4M/s F64 write 8.8M/s F64 latest 12.0M/s STATS stats() returns a hashref: "size", "capacity", "head", "count", "writes", "overwrites", "mmap_size". CONCURRENCY AND CRASH SAFETY The ring is lock-free: there are no mutexes or rwlocks. A writer claims a unique, monotonically increasing position with a single atomic increment of the head counter, so multiple writer and reader processes may share one buffer concurrently. Writes always succeed and never block; when the buffer is full the oldest slot is overwritten. Each slot carries a publication sequence used as a seqlock. A reader loads the sequence, copies the value, then re-checks the sequence; if a write was in progress or the slot has since been overwritten, the read is retried and ultimately reports "no value" ("latest" and "read_seq" return "undef"). A reader therefore never observes a torn or half-written value. If a writer crashes mid-write, its slot is left marked in-progress. Readers skip that slot (returning "undef") rather than block, and after a short recovery timeout a later writer reclaims the slot and overwrites the partial data. Because there are no locks, a crash can never orphan one and wedge other processes; the buffer as a whole stays usable. "wait_for" blocks on a futex over an internal wake counter, so a crashed waiter cannot stall writers either. "clear" is not concurrency-safe: call it only when no other process is writing to the buffer. An interrupted create is recovered too. A creator killed after the backing file is sized but before its header is committed leaves a full-size, all-zero file. "new" re-initializes such a file automatically, but only when it is exactly the size the requested geometry needs, is owned by your effective uid, and is still entirely zero -- a file holding data is never re-initialized. If the creator got as far as writing part of the header, the file cannot be told apart from a corrupt one and "new" croaks with "incomplete ring file left by an interrupted create; remove it and retry". A file left behind by an interrupted create never held data, so removing it is safe -- but a file whose header was corrupted after the fact reaches the same croak, so confirm it is an abandoned create before deleting anything you care about. SECURITY Backing files are created with mode 0600 (owner-only) by default, so only the creating user can open and attach them. To share a backing file across users, pass an explicit octal file mode such as 0660 as the last argument to "new"; the mode is applied when the file is created, and when a file left behind by an interrupted create is re-initialized (see "CONCURRENCY AND CRASH SAFETY"); a file already in use keeps its own permissions. The file is opened with "O_NOFOLLOW", so a symlink planted at the path is refused, and created with "O_EXCL"; the on-disk header is validated when the file is attached. Any process you grant write access to a shared mapping is trusted not to corrupt its contents while other processes are using it. SEE ALSO Data::Queue::Shared - FIFO queue (consumed on read) Data::PubSub::Shared - publish-subscribe ring (subscription tracking) Data::Buffer::Shared - typed shared array Data::BitSet::Shared - shared bitset Data::Pool::Shared - fixed-size object pool Data::Stack::Shared - LIFO stack Data::Deque::Shared - double-ended queue Data::Log::Shared - append-only log Data::Heap::Shared - priority queue Data::Graph::Shared - directed weighted graph Data::Sync::Shared - synchronization primitives Data::HashMap::Shared - concurrent hash table Data::ReqRep::Shared - request-reply AUTHOR vividsnow LICENSE This is free software; you can redistribute it and/or modify it under the same terms as Perl itself.