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 )