Data-Buffer-Shared

 view release on metacpan or  search on metacpan

lib/Data/Buffer/Shared.pm  view on Meta::CPAN

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

C<unlink> also works as a class method: C<< 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.

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

=head2 API

Replace C<xx> with variant prefix: C<i8>, C<u8>, C<i16>, C<u16>,
C<i32>, C<u32>, C<i64>, C<u64>, C<f32>, C<f64>, C<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)

C<set_slice> is a method only (its variadic argument list has no keyword form);
it writes a run of elements starting at C<$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

C<get_raw> and C<set_raw> address the data area in B<bytes>, not element
indices -- unlike every other accessor here. On an C<I64> buffer
C<< $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 C<eventfd(2)> descriptor stored in the handle,
letting one process signal another that the buffer changed. The eventfd is
created non-blocking, so C<wait_notify> does B<not> block: it reads and clears
the counter, returning the accumulated notify count, or C<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 B<non-recursive> and B<non-upgradable>: calling
C<lock_wr> while holding C<lock_rd> on the same handle, or calling C<lock_wr>
twice without an intervening C<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.

=head1 CONCURRENCY AND CRASH SAFETY

Single-element numeric get/set and the atomic counter operations
(C<incr>/C<decr>/C<add>/C<cas>/C<cmpxchg>/C<atomic_and>/C<atomic_or>/C<atomic_xor>)
are lock-free and safe to call concurrently from any number of processes. Bulk
reads (C<slice>, C<get_raw>) are guarded by a seqlock and retry if a writer
intervenes. Bulk writes (C<set_slice>, C<fill>, C<clear>, C<set_raw>) and the
explicit C<lock_wr>/C<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 L</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; C<new> re-initializes such a file automatically, but only when it is
owned by your effective uid and is still entirely zero, so a file holding data



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