NAME
    Data::HashMap::Shared - Multiprocess shared-memory hash maps with LRU
    eviction and per-key TTL

SYNOPSIS
        use Data::HashMap::Shared::II;

        # Create or open a shared map (file-backed mmap)
        my $map = Data::HashMap::Shared::II->new('/tmp/mymap.shm', 100000);

        # Keyword API (fastest)
        shm_ii_put $map, 42, 100;
        my $val = shm_ii_get $map, 42;

        # Method API
        $map->put(42, 100);
        my $v = $map->get(42);

        # Atomic counters (under the read lock, without LRU or TTL)
        shm_ii_incr $map, 1;            # 1
        shm_ii_incr_by $map, 1, 10;     # 11
        shm_ii_max $map, 1, 50;         # monotonic: store max(current, 50) -> 50

        # Compare-and-swap (all variants; byte-compare for string values)
        shm_ii_cas $map, 1, 50, 42;     # swap to 42 only if current == 50

        # LRU cache (evicts least-recently-used when full)
        my $cache = Data::HashMap::Shared::II->new('/tmp/cache.shm', 100000, 1000);
        shm_ii_put $cache, 42, 100;    # auto-evicts LRU entry if size > 1000

        # TTL (entries expire after N seconds)
        my $ttl_map = Data::HashMap::Shared::II->new('/tmp/ttl.shm', 100000, 0, 60);
        shm_ii_put $ttl_map, 1, 10;          # expires in 60s
        shm_ii_put_ttl $ttl_map, 2, 20, 5;   # per-key: expires in 5s

        # Multiprocess
        if (fork() == 0) {
            my $child = Data::HashMap::Shared::II->new('/tmp/mymap.shm', 100000);
            shm_ii_incr $child, 1;   # atomic increment visible to parent
            exit;
        }
        wait;

DESCRIPTION
    Data::HashMap::Shared provides type-specialized hash maps stored in
    file-backed shared memory (mmap(MAP_SHARED)) for multiprocess data sharing
    on Linux. With opt-in LRU eviction and per-key TTL it doubles as a fast
    cross-process cache; lookups take a lock-free seqlock path.

    Linux-only. Requires 64-bit Perl on a little-endian architecture.

  Features
    *   File-backed mmap for cross-process sharing

    *   Futex-based read-write lock (fast userspace path)

    *   Atomic counters (incr/decr under the read lock on maps without LRU or
        TTL)

    *   Elastic capacity (starts small, grows/shrinks automatically)

    *   Arena allocator for string storage in shared memory

    *   Keyword API via XS::Parse::Keyword for maximum speed

    *   Opt-in LRU eviction -- clock/second-chance algorithm; reads stay
        lock-free

    *   Opt-in per-key TTL expiry -- lazy removal on access; monotonic clock

    *   Stale lock recovery for both writers and readers (dead PIDs detected
        and drained automatically)

  Variants
    Data::HashMap::Shared::I16 - int16 to int16
    Data::HashMap::Shared::I32 - int32 to int32
    Data::HashMap::Shared::II - int64 to int64
    Data::HashMap::Shared::I16S - int16 to string
    Data::HashMap::Shared::I32S - int32 to string
    Data::HashMap::Shared::IS - int64 to string
    Data::HashMap::Shared::SI16 - string to int16
    Data::HashMap::Shared::SI32 - string to int32
    Data::HashMap::Shared::SI - string to int64
    Data::HashMap::Shared::SS - string to string

  Integer Range and Wrapping
    Integer keys and values are fixed-width two's complement: 16-bit for
    "I16"/"SI16"/"I16S", 32-bit for "I32"/"SI32"/"I32S", 64-bit for
    "II"/"IS"/"SI". A number outside the variant's range is silently truncated
    to its low bits: on an "I16" map 70000 is key 4464. Numbers beyond 64 bits
    saturate first (1e20 becomes -1, NaN 0). "incr" and "decr" wrap the same
    way. Pick a variant wide enough for your data.

  Constructor
        my $map = Data::HashMap::Shared::II->new($path, $max_entries);
        my $map = Data::HashMap::Shared::II->new(undef, $max_entries);    # anonymous
        my $map = Data::HashMap::Shared::II->new($path, $max_entries, $max_size);
        my $map = Data::HashMap::Shared::II->new($path, $max_entries, $max_size, $ttl);
        my $map = Data::HashMap::Shared::II->new($path, $max_entries, $max_size, $ttl, $lru_skip);
        my $map = Data::HashMap::Shared::SS->new($path, $max_entries, 0, 0, 0, $arena_cap); # explicit arena bytes
        my $map = Data::HashMap::Shared::II->new($path, $max_entries, $max_size, $ttl, $lru_skip, $arena_cap, $file_mode);
        my $map = Data::HashMap::Shared::II->new_sharded($prefix, $shards, $max_entries, $max_size, $ttl, $lru_skip, $arena_cap, $file_mode);
        my $map = Data::HashMap::Shared::II->new_memfd($name, $max_entries, ...); # memfd-backed
        my $map = Data::HashMap::Shared::II->new_from_fd($fd);            # reopen memfd
        my $fd  = $map->memfd;                                            # -1 if not memfd

    Creates or opens a map backed by file $path; "undef" creates an anonymous
    mapping shared only with "fork"ed children. Any number of processes may
    open the same file. The sizing arguments apply only when the file is
    created: an existing file's header wins, though the arguments are still
    range-checked. Opening a file of another variant, or a corrupt one,
    croaks.

    A handle cannot cross into a new ithread or be copied: open the map again
    instead (in a thread, for a memfd map, with "new_from_fd" on a
    "$map->memfd" taken before the thread starts). "Storable" croaks on a
    handle; "Clone" makes a second object that frees the map under the first.

    "new_memfd" creates an unlinked memfd-backed map whose descriptor can be
    inherited across "fork" or sent with "SCM_RIGHTS"; $name is only a label
    and may be "undef". The descriptor is close-on-exec: pass
    POSIX::dup($map->memfd) across "exec". "new_from_fd" reopens such a
    descriptor from a duplicate, so the one you pass stays yours to close.
    "$map->memfd" returns the handle's own descriptor; do not close it.

    $max_size enables LRU eviction: an insert at $max_size entries evicts the
    least recently used (clock/second-chance; an eviction spares at most 64
    recently read entries). 0 disables it. A $max_size at or above the table's
    slot count (2048 for $max_entries 1000) can never be reached, and the
    constructor warns (category "misc").

    $ttl sets the default time-to-live in seconds (0 disables it). An expired
    entry is invisible to reads but keeps its slot, and counts in "size",
    until the next write to that key, a flush ("flush_expired",
    "flush_expired_partial"), or an insert that needs room: an insert flushes
    every expired entry at once when the table or arena is full or the table
    passes its design load. TTLs are whole seconds, truncated, so a TTL of "n"
    expires between "n-1" and "n" seconds later: refresh a heartbeat at least
    two seconds before its TTL. Every store resets an entry's TTL to the map
    default (a permanent entry stays permanent); "put_ttl", "update_ttl" and
    "set_ttl" set a per-key one. Expiry follows "CLOCK_MONOTONIC_COARSE",
    which restarts on reboot: TTLs do not survive a reboot or a move to
    another host.

    $lru_skip (0-99) reduces LRU promotion on updates to a power-of-two rate:
    below 50 every update promotes, 50 one in two, 90 one in sixteen, 99 one
    in 128. Reads never promote; they set the clock bit eviction consults. It
    pays off only on Zipfian write workloads; leave it at 0 otherwise.

    $arena_cap sizes the string arena in bytes (default about 128 per entry,
    clamped to 4096 .. 0xFFFFFFFF; per shard; ignored by integer-only
    variants). Keys and values of 7 bytes or fewer are stored inline and need
    no arena; one must stay under 1 GB. Arena blocks are powers of two from 16
    bytes, recycled only within their size, and the arena reserves its first
    16 bytes: size it from the rounded lengths, with room for a block of every
    size you store.

    When the arena is full, an insert on an LRU map evicts an entry and
    retries, and a TTL map flushes its expired entries; if that makes no room
    the store fails, so check what it returns. An overwrite stores the new
    value before freeing the old one and can fail the same way. Space freed in
    the wrong sizes is gathered by compaction, which the next store of the
    handle that was refused runs; "$map->compact" runs it on demand and
    returns the bytes reclaimed. "arena_used" is a high-water mark: only
    compaction, "clear" and refilling an emptied map lower it.

    $file_mode (default 0600) sets the permissions of a newly created file
    exactly, regardless of umask; above 07777 it croaks, except for a regular
    file's type bits, so "(stat $f)[2]" passes. Use 0660 to share across
    users.

    "get" and "exists" are lock-free (under write contention that keeps
    invalidating them they fall back to the read lock). On a map with neither
    LRU nor TTL, "incr", "decr", "incr_by", "max", "min" and an integer-value
    "cas" update an existing key under the read lock; every other write takes
    the write lock.

  String Keys/Values and UTF-8
    String keys compare as raw bytes. The UTF-8 flag round-trips but is not
    part of the key: ASCII keys match whatever their flag, while a non-ASCII
    key in two encodings ("caf\xe9" and "caf\xc3\xa9") is two keys, which also
    collide in "to_hash". Normalize with "Encode::encode_utf8" when input
    encodings are mixed. A stored key keeps the flag it was first inserted
    with. String values round-trip their flag; "cas" compares bytes only.

  Sharding
        my $map = Data::HashMap::Shared::II->new_sharded($path_prefix, $shards, $max_entries, ...);

    Creates $shards maps (files "$path_prefix.0", "$path_prefix.1", ...)
    behind one handle, each sized as a map of its own. Keys route by hash, and
    writes to different shards run in parallel. $shards is rounded up to a
    power of two (0 is 1, more than 4096 croaks); a path prefix is required.
    All operations work on sharded maps; size and capacity figures are totals,
    and "reserve $n" grows each shard to $n. Use the smallest shard count that
    relieves lock contention.

    Batches and whole-map operations lock shard by shard, so they are not
    atomic across shards; "keys", "values", "items" and "to_hash" hold every
    shard's read lock until their copy is done. Cursors chain across shards.

    Every shard must come from the same configuration and shard count; a
    mismatch croaks. Treat the files as one unit: a shard file that goes
    missing is recreated empty, losing its keys.

  API
    Replace "xx" with variant prefix: "i16", "i32", "ii", "i16s", "i32s",
    "is", "si16", "si32", "si", "ss".

        my $ok = shm_xx_put $map, $key, $value;   # insert or overwrite
        my $ok = shm_xx_add $map, $key, $value;   # insert only if key absent
        my $ok = shm_xx_update $map, $key, $value; # overwrite only if key exists
        my $old = shm_xx_swap $map, $key, $value; # put + return old value (undef if new)
        my $ok = shm_xx_cas $map, $key, $expected, $desired; # compare-and-swap
        my $v  = shm_xx_cas_take $map, $key, $expected; # compare-and-remove; returns value on match, undef otherwise
        my $n  = $map->set_multi($k, $v, ...);   # batch put under single lock, returns count
        my $n  = $map->remove_multi(@keys);      # batch remove under single lock, returns count
        my @v  = $map->get_multi($k1, $k2, ...); # batch get under single lock with prefetch pipeline
        my ($v, $ttl) = $map->get_with_ttl($key); # atomic snapshot; () if missing, $ttl is undef on non-TTL map, 0 = permanent; sets LRU clock bit
        my $v  = shm_xx_get $map, $key;           # returns undef if not found
        my $ok = shm_xx_remove $map, $key;        # returns false if not found
        my $ok = shm_xx_exists $map, $key;        # returns boolean
        my $s  = shm_xx_size $map;
        my $m  = shm_xx_max_entries $map;
        my @k  = shm_xx_keys $map;
        my @v  = shm_xx_values $map;
        my @items = shm_xx_items $map;            # flat (k, v, k, v, ...)
        while (my ($k, $v) = shm_xx_each $map) { ... }  # auto-resets at end
        shm_xx_iter_reset $map;
        shm_xx_clear $map;
        my $href = shm_xx_to_hash $map;
        my $v  = shm_xx_get_or_set $map, $key, $default;  # returns value

    A store fails when there is no room: every table slot is taken or, for
    string data, the arena is full. Then "get_or_set" returns "undef", "add"
    and "cas" return false (as they do when the key exists or the value
    differs), and "swap" returns "undef" (as for a new key) and leaves an
    existing key alone; check "exists" when you need to tell these apart.
    "cas" compares strings byte-wise. "get_multi" returns one element per key,
    "undef" for a miss.

    The counters and integer "cas" on a map without LRU or TTL run under the
    read lock, so a bulk read ("get_multi", "values", "items", "to_hash") can
    combine values that never existed together; quiesce those updates for a
    consistent snapshot.

    Integer-value variants also have:

        my $n = shm_xx_incr $map, $key;           # returns new value
        my $n = shm_xx_decr $map, $key;           # returns new value
        my $n = shm_xx_incr_by $map, $key, $delta;
        my $n = shm_xx_max $map, $key, $desired;  # store max(current, desired), return it
        my $n = shm_xx_min $map, $key, $desired;  # store min(current, desired), return it

    A missing key starts from zero ("incr" returns 1), and "max"/"min" insert
    $desired. They die only when a new key finds no room, and wrap at the
    variant's width. "max" never lowers and "min" never raises a value,
    whatever runs concurrently.

    LRU/TTL operations ("put_ttl", "add_ttl", and "update_ttl" require a
    TTL-enabled map):

        my $ok = shm_xx_put_ttl $map, $key, $value, $ttl_sec;  # per-key TTL (0 = permanent); requires TTL-enabled map
        my $ok = shm_xx_add_ttl $map, $key, $value, $ttl_sec;  # insert-if-absent with per-key TTL (0 = permanent)
        my $ok = shm_xx_update_ttl $map, $key, $value, $ttl_sec; # overwrite-only with per-key TTL (0 = permanent)
        my $ms = shm_xx_max_size $map;            # LRU capacity (0 = disabled)
        my $t  = shm_xx_ttl $map;                 # default TTL in seconds
        my $r  = shm_xx_ttl_remaining $map, $key; # whole seconds left, rounded up (0 = permanent, undef if missing/expired/no TTL)
        my $ok = shm_xx_touch $map, $key;         # refresh TTL to default (permanent entries stay permanent); promotes in LRU; false if no TTL/LRU
        my $ok = shm_xx_persist $map, $key;       # remove TTL, make key permanent; false on non-TTL maps
        my $ok = shm_xx_set_ttl $map, $key, $sec; # change TTL without changing value (0 = permanent); false on non-TTL maps
        my $n  = shm_xx_flush_expired $map;       # proactively expire all stale entries, returns count
        my ($n, $done) = shm_xx_flush_expired_partial $map, $limit;  # scan $limit slots (per shard); $done at the end of a cycle

    Call "flush_expired_partial" on a timer with a $limit that cycles the
    whole table (the slots "max_entries" allows, divided by the ticks in a TTL
    window).

    Atomic remove-and-return:

        my $v = shm_xx_take $map, $key;           # remove key and return value (undef if missing)
        my ($k, $v) = shm_xx_pop $map;            # remove+return from LRU tail / scan forward
        my ($k, $v) = shm_xx_shift $map;          # remove+return from LRU head / scan backward
        my @kv = shm_xx_drain $map, $n;           # remove+return up to N entries as flat (k,v,...) list

    On an LRU map "pop" takes the least and "shift" the most recently used
    entry; otherwise they sweep the table from where their last call stopped.
    "drain" removes in "pop" order. All three return an empty list on an empty
    map.

    Cursors (independent iterators, allow nesting and removal during
    iteration):

        my $cur = shm_xx_cursor $map;             # create cursor
        while (my ($k, $v) = shm_xx_cursor_next $cur) { ... }
        shm_xx_cursor_reset $cur;                 # restart from beginning
        my $ok = shm_xx_cursor_seek $cur, $key;   # position at key (best-effort across resize); true if found, false if missing/expired
        # cursor auto-destroyed when out of scope
        $cur->next; $cur->reset; $cur->seek($key);   # method forms

    "each" and cursors tolerate "remove" during iteration. A table resize
    restarts an iteration, which may then return keys again; under heavy churn
    from other processes a pass may never finish, while "keys", "values",
    "items" and "to_hash" always do. Leaving an "each" loop early keeps the
    iterator open and defers tombstone compaction on that handle: call
    "iter_reset" ("keys" does not reset it).

    Diagnostics:

        my $cap = shm_xx_capacity $map;           # current table capacity (slots)
        my $tb  = shm_xx_tombstones $map;         # tombstone count
        my $au  = shm_xx_arena_used $map;         # arena high-water mark
        my $ac  = shm_xx_arena_cap $map;          # arena total capacity (0 for int-only)
        my $sz  = shm_xx_mmap_size $map;          # backing file size in bytes
        my $ok  = shm_xx_reserve $map, $n;        # pre-grow (false if exceeds max)
        my $ev  = shm_xx_stat_evictions $map;     # cumulative LRU eviction count
        my $ex  = shm_xx_stat_expired $map;       # cumulative TTL expiration count
        my $rc  = shm_xx_stat_recoveries $map;    # cumulative stale lock recovery count
        my $n   = $map->compact;                 # reclaim the arena, returns bytes (method only)
        my $p   = $map->path;                    # backing file path (method only; undef if none)
        my $s   = $map->stats;                   # hashref with all diagnostics in one call (not an atomic snapshot)
        # stats keys: size, capacity, max_entries, tombstones, mmap_size,
        #   arena_used, arena_cap, evictions, expired, recoveries, max_size, ttl,
        #   frozen, readonly

    "max_entries" reports the entry count at the table's 75% design load (a
    map created with 1000 reports 1536, over 2048 slots). Inserts succeed
    beyond it until every slot is taken, but probes grow long near full: run
    at "max_entries", not above it. The table shrinks as entries go, so
    "reserve" again before refilling a drained map.

    "set_multi", "get_multi", "remove_multi", "get_with_ttl", "stats",
    "compact", "path", "sync", "unlink", "freeze", "frozen", "readonly" and
    "memfd" are method-only (no keyword form).

    Keywords take their arguments as a list without parentheses:

        shm_ii_put $map, $key, $value;            # correct
        shm_ii_put($map, $key, $value);           # wrong

    An argument that opens with a parenthesis ends at its close, so write "$t
    + 60", not "($t) + 60", in the last position. List-returning calls --
    "keys", "values", "items", "each", "get_multi", "get_with_ttl", "pop",
    "shift", "drain", "flush_expired_partial", a cursor's "next" -- yield
    their last element in scalar context; use "size" for a count. "no
    Data::HashMap::Shared::II;" disables that variant's keywords in the
    enclosing scope.

    File management:

        $map->sync;                               # flush the mmap to the backing file (msync MS_SYNC)
        $map->unlink;                             # remove backing file (mmap stays valid)
        Data::HashMap::Shared::II->unlink($path); # class method form (single file)

    "sync" matters only for durability on disk; other processes see changes
    without it. "unlink" returns false instead of dying when nothing was
    removed; "$map->unlink" removes only the file the map was opened on, even
    after a "chdir" or a rename over its path.

  Frozen (Read-Only) Mode
        $map->freeze;                                       # seal the file immutable (durable)
        my $ro = Data::HashMap::Shared::II->new_readonly($path);
        my $v  = $ro->get($key);                            # lock-free query; writes nothing
        my $is_frozen   = $map->frozen;                     # true once sealed
        my $is_readonly = $ro->readonly;                    # true for a read-only handle

    "freeze" seals a map for good, durably: every mutator croaks afterwards.
    Stop your writers first -- a write already under way when "freeze" runs,
    or the rest of a batch on a sharded map, still lands.

    "new_readonly" maps a frozen file read-only. Its queries take no lock and
    write nothing, so it works from a read-only filesystem and any number of
    processes can share the file; every query and iterator is supported. A
    frozen file cannot be opened read-write, and "new_readonly" refuses one
    that is not frozen. There is no read-only sharded constructor, so freeze
    single-file maps. A frozen file is a memory image: read it on the same
    architecture, and ship it by copying, not over a network filesystem.

  Crash Safety
    A writer that dies holding the lock (SIGKILL, OOM kill) is detected within
    2 seconds, and the next process to take a lock recovers the map: an
    interrupted resize or "clear" is finished, the LRU list repaired, and an
    interrupted compaction leaves every entry intact. The entry being written
    at the time may be left stale or partial and some arena space may leak;
    call "clear" after a recovery ("stat_recoveries") where that matters.
    Upgrade every process sharing a map together.

    Each handle records its read locks in one of 1024 slots, so a killed
    reader is cleared by the next writer; a handle beyond 1024 runs without
    one, and its crash inside a read lock cannot be recovered. Every write
    scans the slots in use, so writes cost more as handles multiply.

    Liveness is tested with "kill($pid, 0)", so all processes must share one
    PID namespace, and a lock left by a process that died before a reboot or
    container restart can name a reused PID and block the map for good. Carry
    a map across a restart only if every process using it exited cleanly, and
    copy one only while nothing uses it.

    Keep a map on tmpfs ("/run", "/dev/shm"): on a disk filesystem under
    memory pressure, writeback can stall the lock holder, and everyone behind
    it, for hundreds of milliseconds.

    Perl croaks a call once 120 signals are pending. Long writes under the
    write lock block signals once one arrives, so the croak cannot land inside
    them, but it can still hit a write waiting for readers or reading a
    paged-out map back from disk: that write keeps the lock until its process
    exits. A read croaked inside its read lock keeps it until the handle next
    locks. This assumes Perl's deferred signals (not "PERL_SIGNALS=unsafe");
    drive periodic work from an event loop rather than a fast signal timer.

    Map files are sparse, and a full filesystem raises SIGBUS in a writer
    touching a new page. Leave "mmap_size" bytes of headroom, or set
    "DATA_HASHMAP_SHARED_SPARSE=0" to reserve the space at creation (the
    constructor then croaks instead; on tmpfs and memfd this commits the
    memory, and "new_sharded" needs a free descriptor per shard while it
    creates the set).

    A creator killed mid-create leaves an all-zero file, which the next "new"
    initializes when the file has the expected size and owner; one killed
    later leaves "incomplete map file left by an interrupted create; remove it
    and retry".

BENCHMARKS
    "Benchmark" rates over 25,000 entries, single process, Linux x86_64, in
    whole passes per second (multiply by 25,000 for operations per second);
    the cross-process table is in operations per second. Reproduce with "perl
    -Mblib bench/vs.pl 25000".

    Integer key -> integer value (Shared::II):

                  BerkeleyDB   LMDB   Shared::II
        INSERT          30       44         280
        LOOKUP          38       38         353
        INCREMENT       16       17         247

    String key -> string value, short (inline <= 7B, Shared::SS):

                  FastMmap   BerkeleyDB   LMDB   SharedMem   Shared::SS
        INSERT        17          30       43        64          189
        LOOKUP        15          35       36       154          220
        DELETE        --          15       19        34          101

    String key -> string value, long (~50-100B, Shared::SS), measured
    separately on a slower machine, so compare it only within itself:

                  BerkeleyDB   LMDB   SharedMem   Shared::SS
        INSERT        17         28        46          111
        LOOKUP        23         25        92          139

    LRU cache lookup (25K entries, lock-free clock eviction):

                  plain   LRU
        II         342    327   (lock-free; within run-to-run noise of plain)
        SS         165    164

    Cross-process (25K SS entries, 2 processes, ops/s):

                      Shared::SS   SharedMem       LMDB
        READS        3,798,000    3,085,000     854,000
        WRITES       2,424,000      984,000     130,000
        MIXED 50/50  5,738,000    2,470,000     275,000

    LMDB benchmarked with
    MDB_WRITEMAP|MDB_NOSYNC|MDB_NOMETASYNC|MDB_NORDAHEAD. BerkeleyDB with
    DB_PRIVATE|128MB cache.

SEE ALSO
    Data::HashMap::Shared::Cookbook - recipes for counters, caches, rate
    limits, liveness, dedup and atomic state

    Data::Buffer::Shared - typed shared array

    Data::Queue::Shared - FIFO queue

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

    Data::ReqRep::Shared - request-reply

    Data::Sync::Shared - synchronization primitives

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

    Data::Stack::Shared - LIFO stack

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

    Data::Log::Shared - append-only log (WAL)

    Data::Heap::Shared - priority queue

    Data::Graph::Shared - directed weighted graph

    Data::BitSet::Shared - shared bitset (lock-free per-bit ops)

    Data::RingBuffer::Shared - fixed-size overwriting ring buffer

SECURITY
    Files are created with mode 0600, "O_EXCL" and "O_NOFOLLOW", and their
    header is validated on attach. A constructor attaches to any valid map
    already at the path, whoever made it, so keep maps in a directory only the
    processes sharing them can write to -- not /tmp. Every process with write
    access to a map, and every map file you open, is trusted: corruption is
    outside the threat model, and only string bounds are checked against it.

    Keys are hashed with unseeded XXH3, so whoever chooses the keys can pile
    them into one probe run and slow every operation on it (4000 such keys
    made them ten to thirty times slower). Hash keys from an untrusted party
    with a keyed hash (an HMAC under a secret, kept as a string key) first.

    Under taint mode "new" and "new_sharded" with a path, "new_from_fd" and
    "unlink" refuse tainted arguments, and a handle opened from tainted input
    is itself tainted. Files from before 0.16 are refused; recreate them.

AUTHOR
    vividsnow

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

    It bundles xxHash by Yann Collet, used under the BSD 2-Clause licence; see
    LICENSE.xxhash in the distribution.

