NAME
    Data::Shared - Path-addressed facade over the Data-*-Shared family

SYNOPSIS
        use Data::Shared qw(shared_get shared_set shared_exists shared_incr
                            shared_call shared_push shared_pop
                            shared_compile sharedc_get shareda_get shareda_set);

        # Anonymous: shared with processes forked after the mounts.
        my $root = Data::Shared->new;
        $root->mount('cache', 'hashmap:ii', undef, 100_000);
        $root->mount('visitors', 'hyperloglog', undef, 14);
        $root->mount('jobs', 'queue:int', undef, 1024);

        shared_set($root, '/cache/42', 100);
        shared_get($root, '/cache/42');            # 100
        shared_incr($root, '/cache/42');           # 101
        shared_exists($root, '/cache/42');         # 1

        shared_call($root, '/visitors', 'add', 'alice');
        shared_call($root, '/visitors', 'count');  # ~1

        shared_push($root, '/jobs', 42);
        shared_pop($root, '/jobs');                # 42

        # File-backed: any process opens it by its directory.
        my $app = Data::Shared->new('/tmp/demo.shared');
        $app->mount('cache', 'hashmap:ii', undef, 100_000)
            unless $app->mount_info('cache');      # topology is set up once
        shared_set($app, '/cache/42', 100);
        my $same = Data::Shared->open('/tmp/demo.shared');
        shared_get($same, '/cache/42');            # 100, from another handle

        # Hot loops: parse the path once.
        my $cp = shared_compile('/cache/42');
        sharedc_get($root, $cp);                   # 101

        # A key containing '/' needs the array form.
        $root->mount('names', 'hashmap:ss', undef, 100_000);
        shareda_set($root, ['names', 'a/b'], 1);
        shareda_get($root, ['names', 'a/b']);      # 1

DESCRIPTION
    Data::Shared wraps all 39 "Data-*-Shared" distributions behind one
    path-based API inspired by Data::Path::XS. A root object holds named
    mounts; a slash path resolves to a mount plus an intra-structure
    operation:

        /cache/42        mount "cache", key 42
        /lat/0/5         mount "lat", indices 0, 5 (NDArray)
        /visitors        mount "visitors" (used with shared_call)

    Paths select a mount and an adapter operation. Composite mounts ("DEEP
    PATHS (COMPOSITES)") add one child address per level. The string, array
    and compiled tiers mirror Data::Path::XS; requesting its ":keywords"
    syntax croaks. See Data::Shared::Cookbook for recipes.

    A root is anonymous or file-backed. An anonymous root ("new" without a
    directory) is shared with every process forked after its mounts and
    disappears with the last of them. A file-backed root lives in a
    directory: any process can "open" it there, and it persists until
    "unlink_all". Mount from one process and let the others read and write;
    "SHARING A ROOT" has the rules, and the Cookbook's PROCESSES section
    shows both shapes.

    Linux only, 64-bit Perl, matching every leaf. Handles are never cloned
    into ithreads ("CLONE_SKIP").

    Every error this facade raises begins with "Data::Shared: ", so one
    pattern catches them all. A message without that prefix was raised by
    something the facade called -- a leaf's API, a factory you passed, or
    the operating system -- and is reported as it was written.

    The leaf dists are recommended rather than required, so installing this
    facade does not compile all 39 of them. Install the ones you mount;
    mounting a type whose dist is absent croaks naming the dist to install.
    "cpanm --with-recommends Data::Shared" installs every leaf, and each
    leaf's minimum version (recorded in Data::Shared::Registry) is enforced
    when it is first used. One dependency is not implied by what you mount:
    a composite on an anonymous root locks its children with
    Data-Sync-Shared, so install that dist too. File-backed composites use
    the directory lock and do not need it.

ROOT AND MOUNTS
  Data::Shared->new([$dir])
    Without a directory, creates an anonymous root. With a directory,
    creates it if needed; if a manifest.json already exists there it is
    replayed (every mount reopened), otherwise an empty manifest is written.
    When the root is file-backed and a family takes its backing path as the
    leading constructor argument, empty arguments or a leading "undef" or
    ':file' make the facade supply "$dir/<name>.shm", the name sanitized. A
    family that takes the path in its options ("sortedset:strings") or
    builds it itself ("perfecthash") refuses the placeholder and says what
    it wants instead.

  Data::Shared->open($dir)
    Requires an existing directory and manifest. The complete manifest
    schema, mount specs, and mount backing paths are checked before
    constructing handles. Missing storage is an error; opening never
    recreates an empty backing file. Composite descendant files are checked
    when the child is opened lazily. Native backends validate their own file
    headers.

  Data::Shared->open_sealed($dir)
    Reopens frozen leaves read-only while allowing lifecycle cleanup with
    "unlink_all": it gives a writable root over sealed leaves, so reads work
    and the leaves refuse data writes. Of the three openers on a frozen
    store, "open" refuses it outright, "open_readonly" is for readers and
    refuses lifecycle writes, and this one is for teardown.

  Data::Shared->open_readonly($dir, strict => 1)
    Freeze the store in the producing process first. The 21 families with
    "new_readonly" (the "freeze" entries in Data::Shared::Registry,
    including "hashmap" and "bloomfilter") require frozen files; a live file
    aborts replay with the leaf's error under "failed to replay manifest".
    Other leaves use their normal constructor. The generic mutating verbs
    ("set", "delete", "incr", "push", "pop"), every root lifecycle write
    ("mount", "unmount", "freeze_all", "unlink_all") and native "unlink",
    "destroy" and "freeze" through "shared_call" croak on any read-only
    root. A handle taken with "handle" is the leaf itself and carries no
    root flag: every native method on it, those three included, keeps
    whatever permissions that leaf's own file gives it.

    "strict => 1", off by default, requires "new_readonly" or immutability
    (PerfectHash) throughout the declared tree, including lazy composite
    leaves. Unsupported backends are rejected before any handle is opened.
    Raw handles expose the backend's API; filesystem permissions govern
    direct writes.

  $root->mount($name, $spec, @ctor_args)
    Mounts a structure under $name (which may itself contain "/", for nested
    namespaces; longest-prefix wins). $spec may be:

    *   a short spec: 'hashmap', 'hashmap:ii', 'buffer:i64', 'queue:int',
        'sync:semaphore', 'sortedset:strings', etc. Short specs accept mixed
        case and resolve to lowercase. See Data::Shared::Registry for the
        full inventory.

    *   a full class name: 'Data::HashMap::Shared::SS'.

    *   a hashref: "{ spec => 'hashmap:ss', args => [...] }" or with an
        explicit "ctor" (for "new_weighted"/"new_decayed" style
        constructors, also available as 'reservoir:weighted' /
        'topk:decayed').

    *   an already-constructed handle (transient; not written to the
        manifest).

    *   a coderef factory called as "$factory->($root, $name)" (transient).

    *   a composite spec (arrayref or hashref with "child"): see "DEEP PATHS
        (COMPOSITES)".

    Returns the native handle (for a composite mount, a Data::Shared::Node
    for the mount). "$root->handle($path)" also returns it, and any native
    method can always be reached that way or via "shared_call".

    Configuration arrays and hashes are copied before construction. Cycles
    are rejected; a constructor argument JSON has no form for at all -- a
    code reference, or an object that is not a JSON::PP boolean -- makes the
    mount transient rather than refusing it. Failed mount publication
    restores the previous namespace and removes backing paths created by
    that attempt. Existing files are retained. Factories manage their own
    external side effects. Nested mount mutations inside a factory are
    rejected, and so is reopening the same root: the factory runs while its
    caller holds the manifest lock, which no second root in the same process
    can acquire. Use the $root it is given.

    Mount arguments must survive manifest replay, so a value JSON would
    record as something else is refused at mount time instead: "Inf", "NaN"
    and lone surrogates, a stringified "Inf" or "NaN" number included; a
    plain string such as 'Inf' is kept. Strings are recorded as bytes; a
    character string (perl's UTF8 flag on) is encoded to UTF-8 first,
    uniformly from U+0080 up, so "\xe9" after "utf8::upgrade" and the plain
    byte "\xe9" name two different files.

    Special cases: "perfecthash" is an immutable build-then-load structure,
    so a 'perfecthash' spec takes either a built file path or a "{ build_int
    => \@keys, path => $file }" / "build_str" hashref. A build spec creates
    a missing file once; an existing file is loaded without rebuilding. To
    supply one some other way, pass the factory or the loaded handle as the
    spec, as for any other type. "sortedset:strings" takes "mount('names',
    'sortedset:strings', { max => 100_000 })"; "set"/"keys" backing paths
    are auto-filled for file-backed roots. Explicit backing paths must be
    absolute. Derived paths follow the root's directory spelling and are
    relative exactly when that directory is.

    Backing paths already owned by another mount are rejected before
    construction, including aliases produced by filename sanitization (e.g.
    "a/b" and "a__b") and string SortedSet sidecars. Mount the same native
    handle under another name to share it intentionally; an "unmount" that
    would unlink storage another mount still names is refused, so retire the
    aliases first.

  $root->unmount($name), $root->mounts, $root->mount_info($name)
    Lifecycle and introspection for mounts. Names are canonicalized as
    "mount" canonicalizes them, so "/a/" and "a" name one mount. "unmount"
    returns 1 when it removed a mount and 0 when there was none;
    "mount_info" returns "undef" for an absent name, and otherwise a hashref
    of "name", "class", "key", "dist", "path_class", "file", "transient" and
    "composite". "mount_names" is a synonym for "mounts"; "available_types"
    (class or root method) lists the 39 short spec names and "registry"
    returns the Data::Shared::Registry table itself.

    "unmount" retains storage in a manifest ledger; remounting the same name
    adopts it. To reclaim it while serving other mounts, remount it with the
    same spec and "unmount($name, unlink => 1)". "unlink_all" also reclaims
    retained storage from any process, but removes the whole store. Each
    retained store costs a manifest entry; use "unlink => 1" when rotating
    fresh names.

    "unmount($name, unlink => 1)" publishes the removal and retires storage
    under the same directory lock. Native "unlink" and "destroy" through
    "shared_call" at a mount do the same. See "SHARING A ROOT" for what
    other processes observe.

  $root->dir, $root->readonly, $root->manifest_path
    Accessors: the root's directory, whether it was opened read-only, and
    the path of its manifest. The directory and the manifest path are both
    "undef" for an anonymous root.

  $root->sync_all, $root->freeze_all, $root->unlink_all, $root->stats, $root->frozen
    "sync_all", "freeze_all" and "unlink_all" visit supported handles in
    mount order. Sync and freeze include a composite's parent, child index
    and every registered child, open or not. "freeze_all" counts newly
    sealed handles; 0 means nothing was left to seal.

    "stats" returns a hashref keyed by mount name, containing native stats
    or "{ class => ref $handle }". Composite stats include child and open
    counts and stats for cached children only; evicted children are absent.
    Stats fields are backend-specific. eg/monitoring.pl walks registered
    children and compares native capacities and configured child budgets
    with usage.

    "frozen" reports whether a nonempty store is sealed or immutable
    throughout, transient mounts aside, since they never ship with it; only
    21 of the 39 families can be sealed. "perfecthash" is immutable and
    needs no sealing; any of the other 17 ("buffer", "queue", "stack",
    "deque", "heap", "log", "pool", "pubsub", "reqrep", "ringbuffer",
    "sync", "timingwheel", "hiertimingwheel", "reservoir", "topk",
    "segmenttree", "disjointset") or the "sortedset:strings" variant
    prevents "frozen", at any level of a composite too. Freezing is one-way,
    so "freeze_all" refuses such a store before sealing anything and names
    the first mount it cannot seal; which opener to reach for afterwards is
    under "Data::Shared->open_sealed($dir)" above. A transient mount is
    neither sealed nor counted: the handle stays its owner's.

    "unlink_all" removes mounts, the manifest and all retained storage,
    including storage retained by other processes. It also unlinks every
    .shm file directly in the root directory, which reclaims what an
    interrupted mount left but takes any other .shm kept there with it, and
    every .manifest- temporary a killed publication abandoned. A flat mount
    interrupted between construction and publication is reclaimed only if
    its backing file is one of those: a path you gave outside the root, or a
    sidecar with another suffix, is left for its caller. Composite storage
    under .children is removed whole, whatever the suffixes inside it. It
    leaves .manifest.lock, which another process may still hold. A transient
    mount is not unlinked through its handle, though the sweeps above still
    take any of its files they match. "freeze_all" and "unlink_all" croak on
    a read-only root.

SHARING A ROOT
    Topology changes serialize under a directory lock and revision. A root
    opened before another writer's change gets "stale manifest revision"
    from "mount", "unmount", "freeze_all" and "unlink_all", raised before
    any constructor or factory runs: reopen the root and retry against the
    current configuration. A store rebuilt at the same directory, or a copy
    renamed into its place, is refused as "this store was replaced" (an
    older copy as a stale revision) -- each store records an identity of its
    own in the manifest, so the refusal holds even when the rebuild repeats
    the old topology and revision -- and one another process removed as
    "this store was unlinked"; "unlink_all" returns 0 there instead.
    Ordinary reads and writes neither check the revision nor refresh the
    namespace. A write to a mount another process retired with "unlink => 1"
    can succeed into a mapping whose file is gone.

    A root keeps its namespace and cached composite children until reopened.
    After another process rebuilds the store, cached reads, "exists" and
    "children" may still report the old instance until a composite lock
    triggers an instance check. They never cross into the replacement
    store's storage. Workers that must notice teardown should reopen.

    Configure topology from one coordinator and let the workers open the
    result; that is the pattern the revision protocol is built for.

PATH FORMAT
    Components are separated by "/". A leading "/" is optional, repeated and
    trailing slashes are tolerated, and the empty path addresses the root
    ("shared_get" returns the root, "shared_exists" is true, mutating the
    root croaks). The first component(s) select the mount (longest prefix
    match); the remainder is interpreted by the mount's adapter:

    *   Map-like (HashMap, Buffer, RadixTree, Intern, Pool, PerfectHash):
        one key/index component, and more croaks. RadixTree instead joins
        the remainder with "/" so route-style keys work in string paths.

    *   Set-like (Bloom family, BitSet, RoaringBitmap, SortedSet,
        DisjointSet): one member component, and more croaks.

    *   Indexed (NDArray, Fenwick, Fenwick2D, SegmentTree, Graph): all
        remaining components are indices/coordinates.

    *   Queue-, sketch- and sync-like: no remainder; use
        "shared_push"/"shared_pop"/"shared_call", which take the value as an
        argument. The keyed sketches "countminsketch" and "topk" are the
        exception: they require exactly one component, the item, and croak
        without it. "ringbuffer"'s "get" may read one, a sequence number,
        and reads the latest entry without it.

    With "users" and "users/sales" mounted, "/users/sales/eu" selects
    "users/sales", hiding the "sales" record or child of "users". Array
    paths join the same way, including components containing "/", but a
    component is never split: "['users', 'sales/eu']" is the key "sales/eu"
    in "users", not "eu" in "users/sales", so the same characters select a
    different mount in the two tiers. Avoid nested mount names that spell an
    address their parent also serves.

    No escaping is provided in the string tier: keys containing "/" or the
    empty string need the array tier.

GENERIC VERBS
    "shared_get", "shared_set", "shared_delete", "shared_exists" and
    "shared_incr" map to native methods per type in Data::Shared::Registry
    (e.g. HashMap "put/get/remove/exists", RadixTree
    "insert/lookup/delete/exists", SortedSet "add/score/remove", Intern
    "intern/id_of"). Unsupported verbs croak with guidance to use
    "shared_call": plain Bloom filters have no delete, and unkeyed sketches
    and sync primitives have no keyed get/set.

    Each generic verb is also a method on the root, taking the function's
    arguments without the leading $root: "$root->get('/cache/42')", "set",
    "delete", "exists", "incr", "push", "pop" and "$root->call($path,
    $method, @args)". "$root->compile($path)" is "shared_compile".

    Where it is defined, "shared_exists" always returns 1 or 0, an index
    outside the structure included; the families with no honest "exists" --
    the queue-like leaves, the unkeyed sketches, "ndarray" and "fenwick2d"
    among them -- croak like any other absent verb. "shared_incr" adds one,
    or the delta when one is given. "sortedset", "fenwick", "fenwick2d" and
    "segmenttree" score or add rather than count, so there it needs a delta
    and croaks without one, and a "buffer", "pool" or "hashmap" variant with
    no integer value to count in croaks either way.

    Backend semantics:

    *   "shared_set($path, 0)" clears for BitSet, but counts as an add for
        RoaringBitmap, TopK and the Bloom, counting Bloom and Cuckoo
        filters, whose value argument is ignored. RoaringBitmap and the
        counting Bloom and Cuckoo filters remove with "shared_delete"; TopK
        and a plain Bloom filter cannot. "intern" ignores it as well, and
        returns the id it assigned the key.

    *   "shared_delete" on a "buffer" writes the variant's zero, which for
        "buffer:str" is the one-character string "0".

    *   A write can fail by returning false instead of croaking:
        "sortedset:strings" and "intern" derive a string arena of 32 bytes
        per declared entry, so longer members exhaust it early and
        "shared_set" returns "undef". Pass a larger "arena", or a larger
        third constructor argument for "intern", and check the return value.
        A full "queue", "deque", "stack" or "heap" drops a "shared_push" the
        same way, as does a "shared_set" past the end of a "buffer".

    *   Keys cross into XS as bytes, so encode wide characters first. A
        character above U+00FF is accepted by the HashMap family and the
        integer SortedSet, and fatal in RadixTree, Intern, the filter
        family, CountMinSketch, TopK, PerfectHash and "sortedset:strings"
        (whose every verb dies).

    *   An integer-addressed family refuses a non-integer key or index
        ("index must be an integer"): the integer "hashmap" and "sortedset"
        variants and a "perfecthash" built with "build_int" included, while
        their string forms take words as keys. "shared_exists" returns 0 for
        keys the structure cannot hold, and croaks where the family has no
        "exists" of its own.

  shared_call($root, $path, $method, @args)
    Resolves $path to a mount, prepends remaining components to @args, and
    calls the native $method, including methods with no generic verb. For
    composites, the first remaining component selects a child; the rest are
    passed to its method.

    "unlink" and "destroy" are accepted only at a mount or composite child,
    and refused at a record below one. They retire storage with its
    registration; at a child this is equivalent to "shared_delete" on that
    address.

        shared_call($root, '/visitors', 'add_many', ['a', 'b']);
        shared_call($root, '/fenwick', 'prefix', 500);
        shared_call($root, '/sem', 'acquire', 1.5);

  shared_push($root, $path, @values) / shared_pop($root, $path, @args)
    Queue sugar mapping to the native pair per type (Queue "push/pop", Deque
    "push_back/pop_front", Stack "push/pop", Heap "push/pop" with
    priority+value, Log "append" (no pop), PubSub "publish" (no pop),
    RingBuffer "write" (no pop)). Unsupported directions croak. Waiting with
    a timeout is native -- "pop_wait" on Queue, Stack and Heap,
    "pop_front_wait" on Deque: "shared_call($root, '/jobs', 'pop_wait',
    0.5)" returns "undef" when nothing arrived in time.

ARRAY PATH API
    "shareda_get/set/delete/exists/incr/call/push/pop" take an arrayref of
    components instead of a slash string. Binary-safe; components may
    contain "/" or be empty strings, subject to the resolution rule in "PATH
    FORMAT".

COMPILED PATH API
    shared_compile($path) (string or arrayref) returns a
    Data::Shared::Compiled object holding parsed components; "sharedc_*"
    functions reuse it without parsing. First use against a root binds the
    resolved mount, verb and key to that root; subsequent calls reuse that
    binding. One compiled path serves any number of roots, and needs no
    recompiling after a "fork" or a mount change: a root rebinds when its
    own topology changes. The root owns its bindings, so keeping a compiled
    path does not keep an unmounted native handle alive.

DEEP PATHS (COMPOSITES)
    A composite keeps one child structure per stable parent address. Specs
    are nested arrayrefs or hashrefs with "spec"/"args"/"child". Constructor
    arguments exclude the backing path; a leading "undef" placeholder is
    tolerated. A trailing arrayref is always a child spec, so constructors
    taking a trailing arrayref (such as SpatialHash grid wrapping) must use
    the hashref form:

        $root->mount('users', ['hashmap:ss', 10_000, ['bloomfilter', 10_000, 0.01]]);
        $root->mount('leaders', ['sortedset:strings', { max => 10_000 }, ['bloomfilter', 10_000, 0.01]]);
        $root->mount('atlas', ['hashmap:ss', 10_000, ['kdtree', 2, 100_000, ['hashmap:ss', 10_000]]]);

        shared_set($root, '/users/alice/event-42', 1);
        shared_exists($root, '/users/alice/event-42');
        shared_set($root, '/leaders/alice', 1500);           # SortedSet score
        shared_get($root, '/atlas/europe/42/name');

    Parents must be addressable: "hashmap" and "sortedset" (child per
    key/member; for SortedSet the path ending at the child still reads and
    writes its score), plus "kdtree" and "graph" as id-parents (child per
    point/node id; geometry is added through
    "$root->node('/atlas/europe')->call('add', [$x, $y], $id)", and a deep
    write for an id does not require the point to exist). Bloom filters,
    sketches, queues, stacks, logs, sync and timing mounts are rejected as
    parents at mount time; use them as child leaves.

    Semantics at an intermediate path: "shared_get" returns a
    Data::Shared::Node (for SortedSet, the score instead), "shared_exists"
    reports whether a child is registered, "shared_delete" deletes the whole
    subtree, and "shared_set" croaks except for the SortedSet score case.
    "$root->node($path)" (also exported as "shared_node") returns a cursor
    with relative-path
    "get/set/delete/exists/incr/push/pop/call/call_at/node/children/handle"
    methods; "$root->children($path)" ("shared_children") lists registered
    child addresses. "handle" on a child leaf path returns its native
    handle. Reads, "shared_delete" and "shared_pop" at a missing child never
    create it; on a writable root, "shared_set", "shared_incr",
    "shared_push" and a "call" other than "unlink" and "destroy" do. Probe
    with "shared_exists" to avoid allocating children.

    Each composite declares "max_children" (default 10_000) and "cache_size"
    open children (default 32, LRU close/reopen) via the hashref spec form
    or a trailing "{max_children => ..., cache_size => ...}" in the arrayref
    form. Budget explicitly for high-cardinality parents. "cache_size"
    bounds one composite's own cache; a nested composite is itself a cached
    entry with its own cache plus its parent and index segments, so a deep
    tree's open-segment ceiling multiplies per level.

    The budget sizes the child index when the composite is created and
    cannot be raised afterwards: mounting a name over storage an "unmount"
    retained with a larger "max_children" is refused, and a nested level
    cannot exceed the ceiling its storage was built with. "unmount($name,
    unlink => 1)" first, and build the subtree again. Under a "hashmap" or
    "sortedset" parent every child also consumes a parent entry: size the
    parent for the budget. A "sortedset" parent of four fills after four
    children regardless of "max_children". Both ceilings assume short
    addresses: a 256-byte address exhausts the child index near half of
    "max_children", and a string-keyed parent's own arena can bind first.

    Anonymous composites retain all opened children until subtree deletion
    or root destruction, because an anonymous segment cannot be reopened
    after eviction; "cache_size" bounds only file-backed child caches, so a
    long-lived anonymous composite keeps one mapping per address it has
    touched, up to "max_children". Use a file-backed root when processes
    need to discover children created after "fork". File-backed roots store
    children under .children/ next to the manifest and reopen them on
    demand. Composites support "sync_all", "stats", "freeze_all" and subtree
    deletion. Compiled paths over composites stay correct across child
    creation, eviction, and deletion because they re-dispatch to the child
    per call.

    Allocation, construction, publication, score-only registration, and
    subtree deletion use the composite lifecycle lock. Failed construction
    rolls back new registrations, counts, and newly created paths; existing
    scores and reservations are preserved. Allocated child IDs are never
    reused. Cached leaf operations use the backend's own locking. An
    operation already in progress during deletion may complete against the
    old instance; an explicit native handle continues to refer to that
    instance after recreation.

    The lifecycle lock protects the shared child counter. An unfinished
    mutation marker triggers a rebuild on writable open or the next
    lifecycle lock. After a holder dies, the kernel releases file locks;
    anonymous locks recover the dead owner's lock, costing about 20 kB of
    shared mapping per composite. A child directory with no surviving
    registration requires explicit recovery: no API reports it, and it goes
    only when the subtree that owns it is removed: by "unmount" with
    "unlink", by "unlink_all", or by deleting the nested composite that owns
    it. A composite kept alive for months therefore keeps that storage. A
    child whose registration survives -- a "sortedset" score reserves one
    before construction -- is reclaimed by deleting its address. A
    preexisting unpublished directory for a reserved child is an error.
    Recovery covers exceptions and limited interruption, without promising
    power-loss transactions or automatic orphan cleanup.

PERFORMANCE
    On a flat mount of a "map" or "set" path class (the "path_class" in
    Data::Shared::Registry), "get", "set", "delete", "exists" and "incr" run
    in XS at every tier and call the leaf directly, as does "call" on any
    flat mount. Composite paths, "push" and "pop", the other path classes
    and malformed arguments take the Perl implementation, with the same
    results; an error or warning a leaf raises on the XS path reports the
    caller's line. A sample run of bench/overhead.pl on one x86_64 host,
    reading one integer key, rates taken from measured CPU time:

        native handle    0.08 us/op
        compiled         0.20 us/op
        shared_call      0.28 us/op
        string path      0.33 us/op
        array path       0.35 us/op

    Two consecutive runs on a lightly loaded host agreed within 6%; measure
    on your hardware.

    A string or array path is split and looked up on every call; a compiled
    path checks root identity and generation and calls the leaf. Use
    compiled paths or a native "handle" in hot loops. bench/benchmark.pl,
    bench/overhead.pl and bench/mounts.pl supply the measurements here.
    bench/composite.pl measures deep reads with a warm child cache and with
    repeated cache eviction; a composite compiled path costs more than a
    flat one for the reason "DEEP PATHS (COMPOSITES)" gives.

    Creating flat mounts costs quadratic total time: each validates and
    republishes the whole manifest. bench/mounts.pl measures this; on the
    same host the average was 1.6 ms per mount at 25 mounts and 7.2 ms at
    200, i.e. 1.4 s to create 200, with the 200th alone costing 13 ms. Keys
    are not affected: writing 10_000 keys into one flat mount took 33 ms
    without touching the manifest. Opening such a root is superlinear too,
    and paid by every process that opens it: 0.07 s to replay 400 mounts on
    the same host. A namespace with hundreds of mounts belongs in a
    composite mount ("DEEP PATHS (COMPOSITES)") instead, where children are
    registered in the parent and the root manifest stays a single record.

    bench/scaling.pl records child-creation scaling, working-set cache
    costs, process RSS, and wall-time latency percentiles for concurrent
    increments. Its samples are workload measurements, not latency
    guarantees.

CLASSIFICATION OF THE 39 LEAVES
    Map-like: HashMap (10 variants), Buffer (11), RadixTree, Intern, Pool
    (+I64/I32/F64/Str), PerfectHash (read-only). Set-like: BloomFilter,
    CountingBloomFilter, CuckooFilter, BitSet, RoaringBitmap, SortedSet
    (+Strings), DisjointSet. Queue-like: Queue, Deque, Stack, Heap,
    RingBuffer, Log, PubSub. Indexed: NDArray, Fenwick, Fenwick2D,
    SegmentTree, KDTree, IntervalTree, SpatialHash, Graph. Sketch/stats:
    HyperLogLog, CountMinSketch, MinHash, TopK, DDSketch, Histogram,
    Reservoir. Sync/timing (call-only): Sync (Semaphore, Barrier, RWLock,
    Condvar, Once), TimingWheel, HierTimingWheel, ReqRep.

SECURITY
    A file-backed root is created mode 0700, and the manifest and the
    ".manifest.lock" that serialises topology changes are created 0600.
    Republishing the manifest keeps whatever mode it already had, so a root
    you widened on purpose stays widened. Backing files come from the
    leaves, which create them 0600 and exclusively: with "O_NOFOLLOW" and
    "O_EXCL", or for "perfecthash" through a "mkstemp" temporary renamed
    into place. A directory or lock that already exists keeps its own mode,
    so sharing a root between users stays an explicit choice; the facade
    does not offer a mode argument for it, and a group-writable root lets
    any member of that group unlink the manifest.

    The manifest is trusted input. Whatever can write it chooses which
    mounts this process opens and which backing files it maps, so a writer
    who also controls those files controls what the process attaches to: a
    recorded argument list is not re-applied to a file that already exists,
    and the leaf rejects a file whose header does not match the declared
    type, but the file itself decides the mapping. An "args" entry naming an
    absolute path is honoured, so a manifest can reach a file outside the
    root.

    Put the root where only its trusted writers can create it: under a
    world-writable directory -- /tmp on a shared host, as the examples here
    spell it for brevity -- anyone can pre-create it and so choose what the
    first real process attaches to.

    Mount names are sanitised into the root directory, so a name containing
    ".." or "/" cannot place a backing file outside it. Anything you grant
    write access to a shared mapping is trusted not to corrupt it while
    others are attached, as with the leaves.

SEE ALSO
    Data::Path::XS (the API model), Data::Shared::Registry (the inventory
    and verb tables), Data::Shared::Cookbook (per-dist recipes), the
    runnable scripts in the distribution's eg/ directory (eg/job-workers.pl
    drains a queue with forked workers), and each "Data::*::Shared" leaf for
    its native semantics.

AUTHOR
    vividsnow

LICENSE
    This is free software; you can redistribute it and/or modify it under
    the same terms as Perl itself.

