NAME
    Data::Heap::Shared - Shared-memory binary min-heap (priority queue) for
    Linux

SYNOPSIS
        use Data::Heap::Shared;

        my $heap = Data::Heap::Shared->new(undef, 1000);
        $heap->push(3, 300);   # priority=3, value=300
        $heap->push(1, 100);
        $heap->push(2, 200);

        my ($pri, $val) = $heap->pop;   # (1, 100) -- lowest priority first
        my ($pri, $val) = $heap->peek;  # (2, 200) -- without removing

        # blocking pop
        my ($pri, $val) = $heap->pop_wait(5.0);

DESCRIPTION
    Binary min-heap in shared memory. Elements are "(priority, value)"
    integer pairs. Lowest priority pops first.

    Mutex-protected push/pop with sift-up/sift-down. PID-based stale mutex
    recovery. Futex blocking when empty.

    Crash safety: if a process dies while holding the heap mutex (mid-push
    or mid-pop), the mutex is recovered via PID detection, but the heap data
    may be in an inconsistent state (partially sifted). Callers should
    "clear" and rebuild if crash recovery is triggered in a critical
    application.

    Linux-only. Requires 64-bit Perl.

CONSTRUCTORS
  new
        my $heap = Data::Heap::Shared->new($path, $capacity);
        my $heap = Data::Heap::Shared->new($path, $capacity, $mode);
        my $heap = Data::Heap::Shared->new(undef, $capacity);

    Create or attach a heap. $capacity is the maximum number of elements. If
    $path is a defined filename, the heap is backed by that file (created if
    absent, attached if present). If $path is "undef", an anonymous mapping
    is used -- it has no backing file but is "MAP_SHARED", so it is
    inherited across "fork" and shared with child processes (an unrelated
    process simply cannot attach it).

    The optional $mode is an octal permission mask applied only when the
    backing file is created; it defaults to 0600 (owner-only). See
    "SECURITY".

    Croaks on error (bad capacity, permission denied, header mismatch,
    etc.).

  new_memfd
        my $heap = Data::Heap::Shared->new_memfd($name, $capacity);

    Create an anonymous heap backed by a Linux "memfd". $name is a label for
    debugging (as shown in "/proc"). The underlying file descriptor can be
    retrieved with "memfd" and passed to another process (e.g. over a unix
    socket or by inheritance) which attaches with "new_from_fd". Croaks on
    error.

  new_from_fd
        my $heap = Data::Heap::Shared->new_from_fd($fd);

    Attach to an existing heap given an open file descriptor for its backing
    store (typically obtained from "memfd" in another process). The header
    is validated on attach. Croaks on error.

METHODS
  push
        my $ok = $heap->push($priority, $value);

    Insert a "($priority, $value)" integer pair. Returns true on success, or
    false if the heap is full (see "is_full"). Wakes one blocked "pop_wait"
    waiter.

  pop
        my ($pri, $val) = $heap->pop;

    Remove and return the lowest-priority element as a "($priority, $value)"
    pair. Returns the empty list if the heap is empty.

  pop_wait
        my ($pri, $val) = $heap->pop_wait;         # block forever
        my ($pri, $val) = $heap->pop_wait($secs);  # block up to $secs
        my ($pri, $val) = $heap->pop_wait(0);      # non-blocking

    Like "pop", but blocks (via futex) until an element is available. With
    no argument (or a negative timeout) it blocks indefinitely. A timeout of
    0 polls without blocking. A positive fractional $secs bounds the wait;
    on timeout the empty list is returned.

  peek
        my ($pri, $val) = $heap->peek;

    Return the lowest-priority element without removing it. Returns the
    empty list if the heap is empty.

  size
        my $n = $heap->size;

    Current number of elements.

  capacity
        my $cap = $heap->capacity;

    Maximum number of elements (fixed at creation).

  is_empty
        my $bool = $heap->is_empty;

    True if "size == 0".

  is_full
        my $bool = $heap->is_full;

    True if "size >= capacity".

  clear
        $heap->clear;

    Remove all elements (resets size to zero).

  path
        my $p = $heap->path;

    The backing file path, or "undef" for anonymous / memfd heaps.

  memfd
        my $fd = $heap->memfd;

    The backing file descriptor: the "memfd" of a "new_memfd" heap, or the
    dup'd fd of a "new_from_fd" heap. Such an fd can be shared with another
    process which attaches via "new_from_fd". Returns -1 for file-backed and
    anonymous heaps, which keep no shareable descriptor.

  sync
        $heap->sync;

    Flush the mapping to the backing file with "msync". Croaks on error. No
    effect for anonymous heaps.

  unlink
        $heap->unlink;
        Data::Heap::Shared->unlink($path);

    Remove the backing file from the filesystem. Called as an instance
    method it unlinks the heap's own path; called as a class method it
    unlinks the given $path. Croaks for anonymous / memfd heaps (no path) or
    on unlink failure. The mapping stays valid until all handles are
    destroyed.

  stats
        my $stats = $heap->stats;

    Return a hashref with the keys "size", "capacity", "pushes", "pops",
    "waits", "timeouts", "recoveries", and "mmap_size". The counters are
    cumulative across all processes sharing the heap.

EVENTFD NOTIFICATION
    An optional "eventfd" lets an event loop (e.g. EV, AnyEvent, IO::Async)
    wake when the heap is written, instead of blocking in "pop_wait".

  eventfd
        my $fd = $heap->eventfd;

    Create (or return) an eventfd associated with this handle and return its
    file descriptor. Watch it for readability; readiness means "notify" was
    called. Croaks on error.

  eventfd_set
        $heap->eventfd_set($fd);

    Use a caller-supplied file descriptor for notification instead of one
    created by "eventfd". Any previously-owned eventfd is closed. The heap
    takes ownership of $fd: it is closed on the next "eventfd_set" and when
    the heap is destroyed, so pass a dup(2) of the descriptor if you need to
    keep using your own copy.

  fileno
        my $fd = $heap->fileno;

    The current notification file descriptor, or -1 if none is set.

  notify
        $heap->notify;

    Signal the notification fd (write to the eventfd), making it readable.
    Returns true if a notification was delivered.

  eventfd_consume
        my $count = $heap->eventfd_consume;

    Read and clear the eventfd counter, returning the accumulated count, or
    "undef" if there was nothing to read.

BENCHMARKS
    Single-process (500K ops, x86_64 Linux, Perl 5.40):

        push (sequential)       5.3M/s
        pop (drain)             2.5M/s
        push+pop (interleaved)  2.5M/s
        peek                    4.9M/s

    Multi-process (4 workers, 100K ops each, cap=64):

        push+pop                3.1M/s aggregate

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.

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

    Data::Deque::Shared - double-ended queue

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

