Data-Buffer-Shared
view release on metacpan or search on metacpan
$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 )