NAME
    Data::Sync::Shared - Shared-memory synchronization primitives for Linux

SYNOPSIS
        use Data::Sync::Shared;

        # Semaphore -- bounded counter for resource limiting
        my $sem = Data::Sync::Shared::Semaphore->new('/tmp/sem.shm', 4);
        $sem->acquire;            # block until available
        $sem->acquire(1.5);       # with timeout
        $sem->try_acquire;        # non-blocking
        $sem->acquire_n(3);       # acquire N permits atomically
        $sem->release;
        $sem->release(2);         # release N permits
        my $n = $sem->drain;      # acquire all, return count
        {
            my $g = $sem->acquire_guard;   # auto-release on scope exit
        }

        # Barrier -- N processes rendezvous
        my $bar = Data::Sync::Shared::Barrier->new('/tmp/bar.shm', 3);
        my $leader = $bar->wait;       # block until all 3 arrive
        my $leader = $bar->wait(5.0);  # with timeout (-1=timeout)

        # RWLock -- reader-writer lock
        my $rw = Data::Sync::Shared::RWLock->new('/tmp/rw.shm');
        $rw->rdlock;  $rw->rdunlock;
        $rw->wrlock;  $rw->wrunlock;
        $rw->try_rdlock;  $rw->try_wrlock;
        $rw->downgrade;                # wrlock -> rdlock atomically
        {
            my $g = $rw->wrlock_guard;  # auto-release on scope exit
        }

        # Condvar -- condition variable with built-in mutex
        my $cv = Data::Sync::Shared::Condvar->new('/tmp/cv.shm');
        $cv->lock;
        $cv->try_lock;       # non-blocking
        $cv->wait;           # atomically unlock + wait + re-lock
        $cv->wait(2.0);      # with timeout
        $cv->signal;         # wake one waiter
        $cv->broadcast;      # wake all waiters
        $cv->wait_while(sub { !$ready }, 5.0);  # predicate loop
        $cv->unlock;

        # Once -- one-time initialization gate
        my $once = Data::Sync::Shared::Once->new('/tmp/once.shm');
        if ($once->enter) {          # or enter($timeout)
            do_init();
            $once->done;
        }

        # All primitives support anonymous (fork-inherited) mode:
        my $sem = Data::Sync::Shared::Semaphore->new(undef, 4);

        # And memfd mode (fd-passable):
        my $sem = Data::Sync::Shared::Semaphore->new_memfd("my_sem", 4);
        my $fd = $sem->memfd;

DESCRIPTION
    Data::Sync::Shared provides five cross-process synchronization
    primitives stored in file-backed shared memory (mmap(MAP_SHARED)), using
    Linux futex for efficient blocking.

    Linux-only. Requires 64-bit Perl.

  Primitives
    Data::Sync::Shared::Semaphore - bounded counter
        CAS-based counting semaphore. "acquire" decrements (blocks at 0),
        "release" increments (capped at max). Useful for cross-process
        resource limiting (connection pools, worker slots).

    Data::Sync::Shared::Barrier - rendezvous point
        N processes call "wait"; all block until the last one arrives, then
        all proceed. Returns true for one "leader" process. Generation
        counter tracks how many times the barrier has tripped.

    Data::Sync::Shared::RWLock - reader-writer lock
        Multiple concurrent readers or one exclusive writer. Readers use
        "rdlock"/"rdunlock", writers use "wrlock"/"wrunlock". Non-blocking
        "try_rdlock"/"try_wrlock" variants available.

    Data::Sync::Shared::Condvar - condition variable
        Includes a built-in mutex. "lock"/"unlock" protect the predicate.
        "wait" atomically releases the mutex and sleeps; on wakeup it
        re-acquires the mutex. "signal" wakes one waiter, "broadcast" wakes
        all.

    Data::Sync::Shared::Once - one-time init gate
        "enter" returns true for exactly one process (the initializer); all
        others block until "done" is called. If the initializer dies,
        waiters detect the stale PID and a new initializer is elected.

  Features
    *   File-backed mmap for cross-process sharing

    *   Futex-based blocking (no busy-spin, no pthread)

    *   PID-based stale lock recovery (dead process detection)

    *   Anonymous and memfd modes

    *   Timeouts on all blocking operations (except Condvar "lock", which
        provides only a non-blocking "try_lock")

    *   eventfd integration for event-loop wakeup

  Crash Safety
    All primitives encode the holder's PID in the lock word. If a process
    dies while holding a lock, other processes detect the stale lock within
    2 seconds via "kill(pid, 0)" and automatically recover.

    Reader-slot exhaustion (slotless readers): dead-process recovery
    attributes a crashed lock holder's contribution through its reader-slot.
    The slot table holds 1024 entries (one per concurrent reader process).
    If more than that many reader processes share one mapping at once, a
    reader that cannot claim a slot proceeds "slotless" -- it still takes
    the read lock but leaves no per-process record. If such a slotless
    reader is then killed while holding the read lock, its share of the lock
    cannot be attributed to a dead process, so writer recovery cannot
    reclaim it and writers may block until the mapping is recreated.
    Reaching this needs more than 1024 concurrent reader processes on one
    mapping plus a crash in the brief read-lock window; the dead-process
    slot reclaim keeps the table from filling with stale entries, so in
    practice it is very unlikely.

  Security
    Backing files are created securely: the path is opened with
    O_CREAT|O_EXCL|O_NOFOLLOW (a pre-existing symlink at the path is
    rejected, and an existing regular file is attached rather than
    truncated) and the new file is created mode 0600 (owner-only) by
    default, so a segment is not world-writable unless you opt in. To share
    a file with a peer group, pass an explicit octal mode as the last
    argument to new():

        my $sem = Data::Sync::Shared::Semaphore->new($path, $max, $initial, 0660);
        my $rw  = Data::Sync::Shared::RWLock->new($path, 0660);

    The mode is applied exactly via "fchmod" after an "O_EXCL" create, so
    the caller's umask does not narrow it. Offsets read back from an
    attached segment are bounds-checked before use, so a poisoned
    reader-slot offset cannot steer a pointer outside the mapping.

  Guard Objects
    All locking primitives provide scope-based guards that auto-release on
    scope exit (including exceptions):

        {
            my $g = $rw->rdlock_guard;
            # ... read operations ...
        }  # rdunlock called automatically

        {
            my $g = $sem->acquire_guard(3);  # acquire 3 permits
            # ... use resource ...
        }  # release(3) called automatically

        {
            my $g = $cv->lock_guard;
            $cv->wait_while(sub { !$ready }, 5.0);
        }  # unlock called automatically

PRIMITIVES
  Data::Sync::Shared::Semaphore
   Constructors
        my $sem = Data::Sync::Shared::Semaphore->new($path, $max);
        my $sem = Data::Sync::Shared::Semaphore->new($path, $max, $initial);
        my $sem = Data::Sync::Shared::Semaphore->new(undef, $max);
        my $sem = Data::Sync::Shared::Semaphore->new_memfd($name, $max);
        my $sem = Data::Sync::Shared::Semaphore->new_memfd($name, $max, $initial);
        my $sem = Data::Sync::Shared::Semaphore->new_from_fd($fd);

    $max is the maximum permit count. $initial defaults to $max (fully
    available); set to 0 to start with no available permits.

   Operations
        my $ok  = $sem->acquire;              # block until available (infinite)
        my $ok  = $sem->acquire($timeout);    # block with timeout (seconds)
        my $ok  = $sem->try_acquire;          # non-blocking, false if unavailable
        my $ok  = $sem->acquire_n($n);        # acquire N permits atomically
        my $ok  = $sem->acquire_n($n, $timeout);
        my $ok  = $sem->try_acquire_n($n);    # non-blocking N-permit acquire
        $sem->release;                        # release one permit
        $sem->release($n);                    # release N permits (clamped at max)
        my $n   = $sem->drain;               # acquire all available, return count
        my $val = $sem->value;                # current available count
        my $max = $sem->max;                  # maximum permits

   Guard
        my $g = $sem->acquire_guard;          # acquire 1, release on scope exit
        my $g = $sem->acquire_guard($n);      # acquire N
        my $g = $sem->acquire_guard($n, $timeout);  # with timeout, undef on fail

  Data::Sync::Shared::Barrier
   Constructors
        my $bar = Data::Sync::Shared::Barrier->new($path, $parties);
        my $bar = Data::Sync::Shared::Barrier->new(undef, $parties);
        my $bar = Data::Sync::Shared::Barrier->new_memfd($name, $parties);
        my $bar = Data::Sync::Shared::Barrier->new_from_fd($fd);

    $parties must be >= 2.

   Operations
        my $r = $bar->wait;           # block until all parties arrive
        my $r = $bar->wait($timeout); # with timeout

    Returns: 1 = leader (last to arrive), 0 = non-leader, -1 = timeout. A
    zero timeout (wait(0)) is a non-blocking probe: it returns -1 if the
    party would have to block, without breaking the barrier. On a positive
    timeout that actually elapses the barrier is permanently broken: "wait"
    returns -1 for the timing-out party and all other waiters, and every
    subsequent "wait" also returns -1 until "reset" is called. This matches
    POSIX "pthread_barrier_t" broken semantics.

        my $gen = $bar->generation;    # how many times barrier has tripped
        my $n   = $bar->arrived;       # currently arrived count
        my $n   = $bar->parties;       # party count
        $bar->is_broken;               # true if timeout broke the barrier
        $bar->reset;                   # clear broken state + bump generation

  Data::Sync::Shared::RWLock
   Constructors
        my $rw = Data::Sync::Shared::RWLock->new($path);
        my $rw = Data::Sync::Shared::RWLock->new(undef);
        my $rw = Data::Sync::Shared::RWLock->new_memfd($name);
        my $rw = Data::Sync::Shared::RWLock->new_from_fd($fd);

   Operations
        $rw->rdlock;                   # block until read lock acquired
        $rw->rdlock($timeout);         # with timeout (croaks on timeout)
        $rw->wrlock;                   # block until write lock acquired
        $rw->wrlock($timeout);         # with timeout (croaks on timeout)
        my $ok = $rw->try_rdlock;      # non-blocking
        my $ok = $rw->try_wrlock;      # non-blocking
        my $ok = $rw->rdlock_timed($timeout);  # returns false on timeout
        my $ok = $rw->wrlock_timed($timeout);  # returns false on timeout
        $rw->rdunlock;
        $rw->wrunlock;
        $rw->downgrade;                # convert wrlock to rdlock atomically

   Guards
        my $g = $rw->rdlock_guard;             # rdunlock on scope exit
        my $g = $rw->rdlock_guard($timeout);   # with timeout (croaks on fail)
        my $g = $rw->wrlock_guard;
        my $g = $rw->wrlock_guard($timeout);

  Data::Sync::Shared::Condvar
   Constructors
        my $cv = Data::Sync::Shared::Condvar->new($path);
        my $cv = Data::Sync::Shared::Condvar->new(undef);
        my $cv = Data::Sync::Shared::Condvar->new_memfd($name);
        my $cv = Data::Sync::Shared::Condvar->new_from_fd($fd);

   Operations
        $cv->lock;                     # acquire built-in mutex
        $cv->unlock;                   # release built-in mutex
        my $ok = $cv->try_lock;        # non-blocking

        my $ok = $cv->wait;            # unlock, wait for signal, re-lock
        my $ok = $cv->wait($timeout);  # with timeout
        $cv->signal;                   # wake one waiter
        $cv->broadcast;                # wake all waiters

        my $ok = $cv->wait_while(\&pred);           # loop until pred returns false
        my $ok = $cv->wait_while(\&pred, $timeout); # with timeout

    "wait" must be called while holding the mutex. Returns 1 on
    signal/broadcast, 0 on timeout. The mutex is always re-acquired before
    "wait" returns.

    "wait_while" calls "wait" in a loop until the predicate coderef returns
    false. Returns 1 if predicate became false, 0 on timeout.

   Guard
        my $g = $cv->lock_guard;       # unlock on scope exit

  Data::Sync::Shared::Once
   Constructors
        my $once = Data::Sync::Shared::Once->new($path);
        my $once = Data::Sync::Shared::Once->new(undef);
        my $once = Data::Sync::Shared::Once->new_memfd($name);
        my $once = Data::Sync::Shared::Once->new_from_fd($fd);

   Operations
        my $init = $once->enter;             # try + wait, infinite
        my $init = $once->enter($timeout);   # with timeout
        $once->done;                         # mark initialization complete
        my $ok  = $once->is_done;            # check without blocking
        $once->reset;                        # reset to uninitialized state

    "enter" returns true for exactly one process (the initializer). All
    others block until "done" is called, then return false. With a $timeout,
    a waiter that times out before initialization completes also returns
    false: a false return means "not the initializer", not necessarily
    "initialization complete", so use "is_done" to tell a finished gate from
    a timed-out wait. If the initializer dies, stale PID detection elects a
    new one.

  Common Methods
    All primitives support:

        my $p  = $obj->path;           # backing file path (undef if anon)
        my $fd = $obj->memfd;          # memfd fd (-1 if file-backed/anon)
        $obj->sync;                    # msync -- flush to disk
        $obj->unlink;                  # remove backing file
        Class->unlink($path);          # class method form
        my $s  = $obj->stats;          # diagnostic hashref

    Stats keys vary by type. All counters are approximate under concurrency.

    Semaphore: "value", "max", "waiters", "mmap_size", "acquires",
    "releases", "waits", "timeouts", "recoveries".

    Barrier: "parties", "arrived", "generation", "waiters", "mmap_size",
    "waits", "releases", "timeouts".

    RWLock: "state" ("unlocked", "read_locked", "write_locked"), "readers",
    "waiters", "mmap_size", "acquires", "releases", "recoveries".

    Condvar: "waiters", "signals", "mmap_size", "acquires", "releases",
    "waits", "timeouts", "recoveries".

    Once: "state" ("init", "running", "done"), "is_done", "waiters",
    "mmap_size", "acquires", "releases", "waits", "timeouts", "recoveries".

   eventfd Integration
        my $fd = $obj->eventfd;        # create eventfd, returns fd
        $obj->eventfd_set($fd);        # use existing fd (e.g. from fork)
        my $fd = $obj->fileno;         # current eventfd (-1 if none)
        $obj->notify;                  # signal eventfd
        my $n  = $obj->eventfd_consume;  # drain notification counter

    Notification is opt-in. Use with EV or other event loops.

SEE ALSO
    Data::Buffer::Shared - typed shared array

    Data::HashMap::Shared - concurrent hash table

    Data::Queue::Shared - FIFO queue

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

    Data::ReqRep::Shared - request-reply

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

    Data::Stack::Shared - LIFO stack

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

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

    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.

