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.

