Data-Buffer-Shared

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

        $buf->sync;             # msync(MS_SYNC) mmap to backing store
        $buf->unlink;           # remove backing file (dies for anonymous buffers)
        my $h = $buf->stats;    # diagnostic hashref

    "unlink" also works as a class method:
    "Data::Buffer::Shared::I64->unlink($path)". It croaks if the removal fails
    -- except when the file is already gone, which is what you asked for, so a
    cleanup path may safely run twice.

    "memfd" is an alias of "fd" (present on every variant): both return the
    backing file descriptor for a memfd-backed buffer (created with
    "new_memfd"), or "undef" for anonymous and file-backed buffers.

  API
    Replace "xx" with variant prefix: "i8", "u8", "i16", "u16", "i32", "u32",
    "i64", "u64", "f32", "f64", "str".

        buf_xx_set $buf, $idx, $value;    # set element (lock-free atomic for numeric)
        my $v = buf_xx_get $buf, $idx;    # get element (lock-free atomic for numeric)
        my @v = buf_xx_slice $buf, $from, $count;  # bulk read (seqlock)
        buf_xx_fill $buf, $value;         # fill all elements (write-locked)
        buf_xx_clear $buf;                # zero all elements (write-locked)

    "set_slice" is a method only (its variadic argument list has no keyword
    form); it writes a run of elements starting at $from and returns true on
    success:

        $buf->set_slice($from, @values);  # bulk write (write-locked)

    Integer variants also have:

        my $n = buf_xx_incr $buf, $idx;          # atomic increment, returns new value
        my $n = buf_xx_decr $buf, $idx;          # atomic decrement
        my $n = buf_xx_add $buf, $idx, $delta;   # atomic add
        my $ok = buf_xx_cas $buf, $idx, $old, $new;          # compare-and-swap
        my $p = buf_xx_cmpxchg $buf, $idx, $old, $new;       # CAS, returns prior value
        my $n = buf_xx_atomic_and $buf, $idx, $mask;         # atomic AND (integer variants)
        my $n = buf_xx_atomic_or  $buf, $idx, $mask;         # atomic OR
        my $n = buf_xx_atomic_xor $buf, $idx, $mask;         # atomic XOR

    Raw / bulk:

        my $raw = buf_xx_get_raw $buf, $byte_off, $nbytes;   # raw bytes, seqlock-guarded
        buf_xx_set_raw $buf, $byte_off, $raw;                # raw bytes, write-locked
        $buf->add_slice($from, @deltas);                     # batch atomic add (integer variants; flat list)
        my $ptr = buf_xx_ptr $buf;           # raw pointer to data, for FFI use
        my $ptr = buf_xx_ptr_at $buf, $idx;  # pointer to element at index

    "get_raw" and "set_raw" address the data area in bytes, not element
    indices -- unlike every other accessor here. On an "I64" buffer
    "$buf->get_raw(4, 4)" returns bytes 4..7, which is the upper half of
    element 0 and the lower half of element 1, not elements 4..7. Multiply by
    the element size to address elements. Both are bounds-checked against the
    data area and croak rather than run past it.

    Zero-copy:

        my $sv = $buf->as_scalar;   # mmap-aliased read-only scalar ref

    The returned scalar aliases the mapped bytes directly (no copy) and holds
    a reference to the buffer so the mapping stays alive while it is in use.

    Cross-process notification (all variants):

        my $efd = $buf->create_eventfd;   # create + attach an eventfd, returns the fd
        $buf->attach_eventfd($fd);        # attach an already-open eventfd
        my $efd = $buf->eventfd;          # current eventfd, or undef if none
        $buf->notify;                     # signal (eventfd write)
        my $n = $buf->wait_notify;        # drain the counter, non-blocking (undef if 0)

    These are a thin wrapper over an eventfd(2) descriptor stored in the
    handle, letting one process signal another that the buffer changed. The
    eventfd is created non-blocking, so "wait_notify" does not block: it reads
    and clears the counter, returning the accumulated notify count, or "undef"
    when the counter is zero (nothing pending) or no eventfd is attached.
    Nothing else in the API depends on them; watch the descriptor for
    readability in an event loop rather than expecting a blocking wakeup.

    Diagnostics:

        my $c = buf_xx_capacity $buf;
        my $s = buf_xx_mmap_size $buf;
        my $e = buf_xx_elem_size $buf;
        my $h = $buf->stats;    # hashref: capacity/elem_size/mmap_size/variant_id/recoveries

    Persistence:

        $buf->sync;             # msync(MS_SYNC) mmap to backing store

    Explicit locking (for batch operations):

        buf_xx_lock_wr $buf;    # write lock + seqlock begin
        buf_xx_unlock_wr $buf;  # seqlock end + write unlock
        buf_xx_lock_rd $buf;    # read lock
        buf_xx_unlock_rd $buf;  # read unlock

    The explicit locks are non-recursive and non-upgradable: calling "lock_wr"
    while holding "lock_rd" on the same handle, or calling "lock_wr" twice
    without an intervening "unlock_wr", self-deadlocks. Dropping the last
    reference to a handle while holding one of its locks leaks that handle's
    reader slot (and any held lock contribution) until the process exits.

CONCURRENCY AND CRASH SAFETY
    Single-element numeric get/set and the atomic counter operations
    ("incr"/"decr"/"add"/"cas"/"cmpxchg"/"atomic_and"/"atomic_or"/"atomic_xor"
    ) are lock-free and safe to call concurrently from any number of
    processes. Bulk reads ("slice", "get_raw") are guarded by a seqlock and
    retry if a writer intervenes. Bulk writes ("set_slice", "fill", "clear",
    "set_raw") and the explicit "lock_wr"/"unlock_wr" region take a write
    lock.

    The write/read lock is a write-preferring futex read-write lock with
    dead-process recovery: if a process crashes while holding the lock,
    another process detects the dead holder and reclaims its contribution so
    the mapping does not deadlock. See "Reader-slot exhaustion" for the one
    narrow case this recovery cannot cover.

    An interrupted create is also recovered. 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 owned by your effective uid and is still entirely



( run in 1.312 second using v1.01-cache-2.11-cpan-14f38c9f855 )