NAME
    Data::Stack::Shared - Shared-memory LIFO stack for Linux

SYNOPSIS
        use Data::Stack::Shared;

        my $stk = Data::Stack::Shared::Int->new(undef, 100);
        $stk->push(42);
        $stk->push(99);
        say $stk->pop;       # 99 (LIFO)
        say $stk->peek;      # 42
        say $stk->size;      # 1

        # blocking with timeout
        $stk->push_wait(42, 5.0);
        my $val = $stk->pop_wait(5.0);

        # string variant
        my $ss = Data::Stack::Shared::Str->new(undef, 50, 256);
        $ss->push("hello");
        say $ss->pop;

        # anonymous / memfd / file-backed
        my $s = Data::Stack::Shared::Int->new('/tmp/stk.shm', 100);
        $s = Data::Stack::Shared::Int->new(undef, 100);
        $s = Data::Stack::Shared::Int->new_memfd("my_stk", 100);
        my $fd = $s->memfd;
        $s = Data::Stack::Shared::Int->new_from_fd($fd);

DESCRIPTION
    LIFO stack in shared memory. CAS-based position handout on an atomic top
    index, paired with a per-slot publication state machine (see
    "Concurrency"). Futex blocking when empty or full.

    Linux-only. Requires 64-bit Perl.

  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 "t-1" via the "top" CAS therefore always
    observes the matching pusher's transition to "filled" before reading the
    value. "peek" is a seqlock-style read: it retries if the slot
    transitions during the read and returns false if the top changes
    concurrently beyond the retry budget.

    "drain" is safe under concurrent "push"/"pop". It bounds the per-slot
    wait to ~2s and, on timeout, force-resets a slot still in the "writing"
    state to "empty" (with generation bumped) so a pusher that crashed
    between its position CAS and publish cannot wedge drain forever. Because
    the slot control word does not encode owner PID, this recovery cannot
    distinguish a crashed pusher from one stalled longer than 2s; if a live
    pusher is falsely reclaimed, its later publish observes the bumped
    generation and is silently dropped (the value is lost -- equivalent to a
    crash). A legitimate publish-in-flight delay is many orders of magnitude
    shorter than the threshold under normal load.

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

  Variants
    "Data::Stack::Shared::Int" - int64_t values
    "Data::Stack::Shared::Str" - fixed-length strings

METHODS
  Push / Pop
        my $ok  = $stk->push($val);               # non-blocking
        $ok     = $stk->push_wait($val);           # blocking (infinite)
        $ok     = $stk->push_wait($val, $timeout); # blocking with timeout

        my $val = $stk->pop;                       # non-blocking, undef if empty
        $val    = $stk->pop_wait;                  # blocking (infinite)
        $val    = $stk->pop_wait($timeout);        # blocking with timeout

        $val    = $stk->peek;                      # read top without removing

  Status
        my $n   = $stk->size;
        my $cap = $stk->capacity;
        my $ok  = $stk->is_empty;
        my $ok  = $stk->is_full;
        $stk->clear;                               # empty (NOT concurrency-safe)
        my $n = $stk->drain;                       # empty (concurrency-safe, returns count)

  Common
        my $p  = $stk->path;
        my $fd = $stk->memfd;
        $stk->sync;
        $stk->unlink;
        my $s  = $stk->stats;

  eventfd
        my $fd = $stk->eventfd;
        $stk->eventfd_set($fd);
        my $fd = $stk->fileno;
        $stk->notify;
        my $n  = $stk->eventfd_consume;

STATS
    stats() returns: "size", "capacity", "pushes", "pops", "waits",
    "timeouts", "mmap_size".

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 only when the file is created (an
    existing file 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):

        Int push + pop          6.4M/s
        Int push (fill) + pop   6.4M/s
        Int peek               13.0M/s
        Str push + pop (48B)    4.7M/s

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

        Int push + pop          5.0M/s aggregate

SEE ALSO
    Data::Deque::Shared - double-ended queue (deque)

    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.

