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.