Data-SpatialHash-Shared

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

FROZEN (READ-ONLY) MODE
    A file-backed spatial hash can be frozen and then shipped to other
    machines, where consumers open it read-only and query it with no locking
    at all.

        # producer: build, freeze, ship the file
        my $s = Data::SpatialHash::Shared->new("/tmp/world.sph", 100_000, 0, 1.0);
        $s->insert($_, $_, $_) for 1 .. 1000;
        $s->freeze;                 # seal: now immutable, and $s itself is read-only
        # ... copy /tmp/world.sph to another host ...

        # consumer (any process, same architecture): read-only, lock-free
        my $ro = Data::SpatialHash::Shared->new_readonly("/tmp/world.sph");
        my @near = $ro->query_radius(5, 5, 10);

    "freeze" takes the write lock, marks the spatial hash permanently
    immutable (there is no unfreeze -- rebuild the file to change it), and
    flushes the seal to disk. A frozen spatial hash rejects every mutator
    ("insert", "insert_many", "insert_geo", "move", "move_many", "move_geo",
    "remove", "set_value", "set_radius", "clear") with a croak, and a
    read-write reopen ("new($path, ...)" or "new_from_fd") of a sealed file is
    refused -- so a shipped artifact can never be silently mutated out from
    under its readers.

    new_readonly($path) maps the file "O_RDONLY" / "PROT_READ" and requires it
    to be frozen (it croaks on a file that was never "freeze"d). Because a
    sealed spatial hash's entries and geometry are immutable, every accessor
    and query method -- "has", "value", "get_radius", "position",
    "position_geo", "count", "query_cell", "query_aabb", "query_radius",
    "query_radius_many", "query_knn", "query_geo_radius", "each_in_radius",
    "each_pair_within", "each_colliding_pair", and "stats" -- reads it
    directly, taking no reader lock. The mapping is never written, so a
    read-only view works from a read-only file descriptor or a read-only
    filesystem, and any number of processes can share one "PROT_READ" mapping.
    "sync" is a silent no-op on a read-only view. "frozen" and "readonly"
    report the two states.

    Portability. The on-disk format is native binary (native-endian 64-bit
    words), so a frozen file may be copied only between machines of the same
    architecture; a wrong-endian file is rejected at open by the magic check.
    Copy the file to each consumer -- do not share one file over a network
    filesystem: the lock is a Linux futex (process-local to one kernel), and
    the "no live writer" contract assumes a static copy. Linux-only; 64-bit
    Perl.

SECURITY
    Backing files are created with mode 0600 (owner-only) by default, so only
    the creating user can open and attach them. To share a backing file across
    users, pass an explicit octal file mode such as 0660 via a "mode => 0660"
    option to "new"; the mode is applied when the file is created, and when a
    file left behind by an interrupted create is re-initialized (see "CRASH
    SAFETY"); a file already in use keeps its own permissions. The file is
    opened with "O_NOFOLLOW", so a symlink planted at the path is refused, and
    created with "O_EXCL"; the on-disk header is validated when the file is
    attached. Any process you grant write access to a shared mapping is
    trusted not to corrupt its contents while other processes are using it.

CRASH SAFETY
    The write lock is a futex-based rwlock with PID-encoded ownership. If the
    writer process dies while holding the lock, the next writer that cannot
    acquire the lock checks whether the owner PID is still alive and, if not,
    recovers the lock. Reader slots are similarly reclaimed when a dead
    reader's slot is detected.

    Limitation: PID reuse is not detected. If a new process acquires the same
    PID as a dead lock holder before recovery runs, the stale lock may not be
    released automatically. This edge case requires the kernel to reassign
    PIDs faster than lock-recovery attempts, which is very unlikely in
    practice but cannot be ruled out.

    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.

    An interrupted create is recovered too. 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 exactly the size the requested geometry needs, is owned by your
    effective uid, and is still entirely zero -- a file holding data is never
    re-initialized. If the creator got as far as writing part of the header,
    the file cannot be told apart from a corrupt one and "new" croaks with
    "incomplete spatial hash file left by an interrupted create; remove it and
    retry". A file left behind by an interrupted create never held data, so
    removing it is safe -- but a file whose header was corrupted after the
    fact reaches the same croak, so confirm it is an abandoned create before
    deleting anything you care about.

SEE ALSO
    Data::Graph::Shared - directed weighted graph

    Data::Heap::Shared - priority queue (for Dijkstra, Prim, etc.)

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

    Data::HashMap::Shared - concurrent hash table

    Data::Buffer::Shared - typed shared array

    Data::Queue::Shared - FIFO queue

    Data::Stack::Shared - LIFO stack

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

    Data::Log::Shared - append-only log

    Data::Sync::Shared - synchronization primitives

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

    Data::ReqRep::Shared - request-reply



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