NAME
    Data::Deque::Shared - Shared-memory double-ended queue for Linux

SYNOPSIS
        use Data::Deque::Shared;
        use feature 'say';

        my $dq = Data::Deque::Shared::Int->new(undef, 100);
        $dq->push_back(1);
        $dq->push_back(2);
        $dq->push_front(0);
        say $dq->pop_front;   # 0
        say $dq->pop_back;    # 2

        # String variant (fixed max_len per entry)
        my $sq = Data::Deque::Shared::Str->new(undef, 100, 64);
        $sq->push_back("hello");
        say $sq->pop_front;   # hello

        # blocking with timeout
        $dq->push_back_wait(42, 5.0);
        my $v = $dq->pop_front_wait(5.0);

        # file-backed / memfd
        $dq = Data::Deque::Shared::Int->new('/tmp/dq.shm', 100);
        $dq = Data::Deque::Shared::Int->new_memfd("my_dq", 100);
        my $fd = $dq->memfd;
        $dq = Data::Deque::Shared::Int->new_from_fd($fd);

DESCRIPTION
    Double-ended queue (deque) in shared memory. Ring buffer with CAS-based
    push/pop at both ends. Futex blocking when empty or full.

    Linux-only. Requires 64-bit Perl. Capacity must be <= 2^31.

    Used only as a FIFO ("push_back" + "pop_front"), it's effectively a
    fixed-slot lock-free string queue: 1.3x-4.7x faster than
    Data::Queue::Shared::Str under multi-producer contention, at the cost of
    per-slot fixed memory ("capacity x max_len").

  Concurrency
    Push and pop are safe under multi-producer / multi-consumer workloads.
    Each slot carries a 64-bit control word (state + generation) that acts as
    a publication gate: a pusher atomically transitions the slot through
    "empty -" writing -> filled>, and a popper transitions it through "filled
    -" reading -> empty> with the generation bumped on completion. A consumer
    that claims position "n" via the head/tail CAS therefore always observes
    the publication transition of the corresponding push before reading the
    value.

    "drain" is safe under concurrent "push"/"pop": it spin-waits on any slot
    whose pusher is mid-publish, then releases each slot through the state
    machine. If a pusher crashes after winning its position CAS but before
    publishing the value (i.e. anywhere in the cursor-CAS to publish window),
    drain waits ~2 seconds and then force-recovers the slot via a generation
    bump (counted in "stats->{recoveries}"). A stalled- but-live pusher whose
    slot was force-recovered will silently drop its late publish rather than
    resurrect the slot as FILLED.

  Compatibility
    File format bumped to v2 in this release (per-slot control array added for
    MPMC safety). Opening a v1 file (magic "DEQ1") created by
    Data::Deque::Shared "<= 0.02" will croak on header validation. Re-create
    the deque with the new version; anonymous and memfd-backed usage is
    unaffected.

METHODS
  Constructors
    There are two concrete subclasses; the base class "Data::Deque::Shared" is
    not instantiated directly.

    "Data::Deque::Shared::Int" stores 64-bit signed integers:

        my $dq = Data::Deque::Shared::Int->new($path, $capacity, $mode);
        my $dq = Data::Deque::Shared::Int->new_memfd($name, $capacity);
        my $dq = Data::Deque::Shared::Int->new_from_fd($fd);

    "Data::Deque::Shared::Str" stores byte/UTF-8 strings in fixed-size slots:

        my $sq = Data::Deque::Shared::Str->new($path, $capacity, $max_len, $mode);
        my $sq = Data::Deque::Shared::Str->new_memfd($name, $capacity, $max_len);
        my $sq = Data::Deque::Shared::Str->new_from_fd($fd);

    For "new", $path may be "undef" for an anonymous (private) mapping, or a
    filesystem path for a file-backed mapping shared across processes.
    $capacity is the number of slots (must be > 0 and <= 2^31); it is rounded
    up to the next power of two, and "capacity"/"stats" report that rounded
    value. For the Str variant, $max_len is the maximum stored byte length per
    entry (must be > 0 and < 2 GiB); longer values are truncated. $mode is an
    optional octal file permission mode applied only when a backing file is
    created (default 0600); see "SECURITY".

    "new_memfd" creates an anonymous "memfd" sealed mapping named $name;
    retrieve its descriptor with "memfd" and re-attach in another process
    (after passing the fd across, e.g. via "SCM_RIGHTS") with "new_from_fd".

    All constructors croak on failure. The descriptor you pass is duplicated
    ("F_DUPFD_CLOEXEC"), so it stays yours to close and closing it does not
    disturb the handle.

  Push / Pop
        $dq->push_back($val);          $dq->push_front($val);
        $dq->push_back_wait($val, $t); $dq->push_front_wait($val, $t);
        my $v = $dq->pop_front;        my $v = $dq->pop_back;
        my $v = $dq->pop_front_wait($t); my $v = $dq->pop_back_wait($t);

    The non-blocking "push_back" / "push_front" return true on success and
    false if the deque is full. "pop_front" / "pop_back" return the value, or
    "undef" if the deque is empty.

    The *_wait variants block until the operation can proceed or the optional
    timeout $t (fractional seconds; omitted or negative means wait forever)
    elapses. "push_*_wait" return true on success, false on timeout;
    "pop_*_wait" return the value, or "undef" on timeout.

  Status
        $dq->size;  $dq->capacity;  $dq->is_empty;  $dq->is_full;
        $dq->clear;    # NOT concurrency-safe
        my $n = $dq->drain;  # concurrency-safe, returns count drained
        $dq->stats;    # {size, capacity, pushes, pops, waits, timeouts, recoveries, mmap_size}

  Common
        $dq->path;  $dq->memfd;  $dq->sync;  $dq->unlink;

  eventfd
        $dq->eventfd;  $dq->notify;  $dq->eventfd_consume;
        $dq->eventfd_set($fd);  $dq->fileno;

STATS
    stats() returns: "size", "capacity", "pushes", "pops", "waits",
    "timeouts", "recoveries", "mmap_size". "recoveries" counts slots that
    drain force-skipped because a pusher crashed (or stalled > 2s) between
    winning the cursor CAS and publishing the value.

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 "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.

BENCHMARKS
    Single-process (1M ops, x86_64 Linux, Perl 5.40):

        push_back + pop_front (FIFO)    6.5M/s
        push_back + pop_back (LIFO)     6.3M/s
        push_front + pop_front (LIFO)   6.4M/s
        push_front + pop_back (FIFO)    6.5M/s

    Multi-process (8 workers, 200K ops each):

        cap=16     5.7M/s aggregate
        cap=64     5.9M/s aggregate
        cap=256    5.8M/s aggregate

CRASH SAFETY
    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 deque 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.

SEE ALSO
    Data::Stack::Shared - LIFO stack

    Data::Queue::Shared - FIFO queue

    Data::ReqRep::Shared - request-reply

    Data::Pool::Shared - fixed-size object pool

    Data::Log::Shared - append-only log (WAL)

    Data::Buffer::Shared - typed shared array

    Data::Sync::Shared - synchronization primitives

    Data::HashMap::Shared - concurrent hash table

    Data::PubSub::Shared - publish-subscribe ring

    Data::Heap::Shared - priority queue

    Data::Graph::Shared - directed weighted graph

    Data::BitSet::Shared - shared bitset (lock-free per-bit ops)

    Data::RingBuffer::Shared - fixed-size overwriting ring buffer

AUTHOR
    vividsnow

LICENSE
    This is free software; you can redistribute it and/or modify it under the
    same terms as Perl itself.

