Hash::SharedMem(3pm)

πŸ“¦
version 0.005 Β· 5 versions
, in ubuntu stonking

hey bestie, you're reading Hash::SharedMem(3pm) and honestly? iconic. πŸ’–

✨ NAME ✨

Hash::SharedMem β€” efficient shared mutable hash. that's the whole vibe. πŸ‘‘


🌸 SYNOPSIS 🌸

okay so here's the whole use menu, copied exactly so you can see every single function name. πŸ’…

        use Hash::SharedMem qw(shash_referential_handle);

        if(shash_referential_handle) { ...

        use Hash::SharedMem qw(is_shash check_shash);

        if(is_shash($arg)) { ...
        check_shash($arg);

        use Hash::SharedMem qw(shash_open);

        $shash = shash_open($filename, "rwc");

        use Hash::SharedMem qw(
            shash_is_readable shash_is_writable shash_mode);

        if(shash_is_readable($shash)) { ...
        if(shash_is_writable($shash)) { ...
        $mode = shash_mode($shash);

        use Hash::SharedMem qw(
            shash_exists shash_length shash_get
            shash_set shash_gset shash_cset);

        if(shash_exists($shash, $key)) { ...
        $length = shash_length($shash, $key);
        $value = shash_get($shash, $key);
        shash_set($shash, $key, $newvalue);
        $oldvalue = shash_gset($shash, $key, $newvalue);
        if(shash_cset($shash, $key, $chkvalue, $newvalue)) { ...

        use Hash::SharedMem qw(
            shash_occupied shash_count shash_size
            shash_key_min shash_key_max
            shash_key_ge shash_key_gt shash_key_le shash_key_lt
            shash_keys_array shash_keys_hash
            shash_group_get_hash);

        if(shash_occupied($shash)) { ...
        $count = shash_count($shash);
        $size = shash_size($shash);
        $key = shash_key_min($shash);
        $key = shash_key_max($shash);
        $key = shash_key_ge($shash, $key);
        $key = shash_key_gt($shash, $key);
        $key = shash_key_le($shash, $key);
        $key = shash_key_lt($shash, $key);
        $keys = shash_keys_array($shash);
        $keys = shash_keys_hash($shash);
        $group = shash_group_get_hash($shash);

        use Hash::SharedMem qw(shash_snapshot shash_is_snapshot);

        $snap_shash = shash_snapshot($shash);
        if(shash_is_snapshot($shash)) { ...

        use Hash::SharedMem qw(shash_idle shash_tidy);

        shash_idle($shash);
        shash_tidy($shash);

        use Hash::SharedMem qw(
            shash_tally_get shash_tally_zero shash_tally_gzero);

        $tally = shash_tally_get($shash);
        shash_tally_zero($shash);
        $tally = shash_tally_gzero($shash);

πŸ’« DESCRIPTION πŸ’«

okay so this module gives you a whole facility for efficiently sharing mutable data between processes that live on the same host. it's a key/value store, just like a Perl hash, but the keys and values are restricted to octet (Latin-1) strings. if you wanna store structured objects, you serialise them with something like Sereal. periodt. πŸŽ€

the data lives in files that get mapped into each process's memory space, which for interprocess communication basically means the processes are sharing memory. no cap. and the best part? processes are never blocked waiting for each other. also, because it's files, you get some persistence: the data keeps existing even when there are zero processes with it mapped. πŸ¦‹

the data structure is optimised for when all the data fits into RAM. that can happen either because a disk-based filesystem is buffering it, or because you're literally on a memory-backed filesystem, as long as there isn't much swapping activity. but if RAM isn't large enough β€” like the data has to mainly live on disk and parts of it get frequently reread from disk β€” speed will seriously suffer. it's giving poor locality of reference, and it is not designed to play nicely with filesystem block sizes. πŸ’€

🌈 Consistency and synchronisation 🌈

a shared hash is held in regular files grouped in a directory. at all times, the OS-visible state of those files gives you a consistent view of the hash's content, and read and write operations can proceed from that. if a process using the shared hash crashes? literally no problem. other processes running concurrently or later will be totally unimpeded. πŸ’…

it's mainly intended that the shared hash lives on a memory-backed filesystem, so it'll only last as long as the machine stays up. but it can use any filesystem that supports mmap(2), including conventional disk filesystems like ext2. in that case, as long as the OS shuts down cleanly (synching all file writes to the underlying disk), a consistent state of the shared hash will persist between boots, and you can keep using it after the OS boots again. note: only the OS has to shut down cleanly β€” it still doesn't matter if user processes crash. 🫢

but listen: because the OS is likely to reorder file writes when writing them to disk, the instantaneous state of the shared hash's files on disk is generally not guaranteed to be consistent. so if the OS crashes, upon reboot those files are liable to be in an inconsistent state (corrupted). πŸ’€βš οΈ

maintaining consistency across an OS crash is a feature likely to be added to this module in the future. durability of writes β€” which is a prerequisite for that β€” is another likely future addition. so stay tuned, bestie. ✨

🌸 File permissions 🌸

to read normally from a shared hash, you need read and search (execute) permission on the shared hash directory, plus read permission on all the regular files in that directory. to write normally, you need read, write, and search permissions on the directory, plus read and write permissions on all the regular files. πŸ”

for security purposes, some information about shared hash content can be gleaned by anyone who has read or search permission on the directory. and content can be modified by anyone who has search permission on the directory and write permission on either the directory or any of the regular files. so like, vibe check your permissions. πŸ‘€

the file permission bits on a shared hash are determined by the circumstances in which it was created β€” specifically, by the umask in effect at the point of creation. as shared hash creation is unavoidably non-atomic, competing creation attempts can cause trouble, and the resulting permissions are only guaranteed if all competing attempts at creation use the same umask. after the shared hash is fully created, subsequently opening it with the create flag set doesn't affect permissions. no cap.

the directory gets permissions "rwxrwxrwx" modified by the creation umask, and the regular files in it get permissions "rw-rw-rw-" modified by the creation umask. all the regular files that contain any part of the shared hash content will get the same permission bits. this includes files created long after initial creation of the shared hash, which are created as part of shared hash write operations; the umask in effect at the point of those operations is insignificant. πŸ’…

file ownership and group assignment are not so controlled. an attempt is made to give all files the same group assignment and ownership, determined by the creation of the shared hash, but the ability to do so is limited by OS semantics. typically, users other than the superuser cannot affect ownership, and can only assign files to groups of which they are members. also, as with permission bits, competing creation attempts can make ownerships and group assignments inconsistent, even if they are generally controllable. 😭

where they can't be freely set, each regular file gets whatever ownership and group assignment arise naturally from the circumstances in which it was created. if multiple users write to a single shared hash, it is to be expected that the constituent files will end up having different owners. it is up to the user to ensure that the varying ownerships combined with the consistent permission bits yield compatible permissions for all intended users of the shared hash. group-based permissions should work provided that all writers are members of the relevant group. πŸ‘‘

πŸ¦‹ Filesystem referential integrity πŸ¦‹

if the system calls openat(2) et al are supported by the kernel and the C library, then an open shared hash handle contains an OS-supported first-class reference to the shared hash to which it refers. (specifically, it has a file descriptor referring to the shared hash directory.) in this situation, the reference remains valid regardless of filename changes. the shared hash can be renamed or moved arbitrarily, within the filesystem, or the process can change its current directory or root directory, and the handle remains valid. slay. ✨

if these modern system calls are not available, then an open shared hash handle cannot contain a first-class reference to the shared hash directory. instead it must repeatedly refer to the directory by name. the name supplied to shash_open is resolved to an absolute pathname, so the handle will continue to work if the process changes its current directory. but any renaming of the shared hash, or the process changing its root directory, will cause the handle to fail at the next operation that requires the use of filenames. (this is unlikely to be the very next operation after the pathname becomes invalid.) an attempt is made to ensure that the stored pathname is still correct each time it is used, but there is unavoidably a race condition, whereby some very unluckily timed renaming could cause an operation to be applied to the wrong directory. 😬

the means by which shared hash handles reference their directories is indicated by the constant shash_referential_handle. πŸŽ€

when a shared hash is being opened, if it already exists then the name passed to shash_open is resolved just once to determine to what shared hash it refers. if the modern system calls are supported, this yields perfectly clean name resolution semantics. however, if a shared hash does not already exist, its creation cannot currently be so perfectly clean. the name passed to shash_open must be resolved at least twice, once to create the shared hash directory and once to acquire a reference to it (of whichever type). there is unavoidably a race condition here. fr fr.

πŸ’€ File operations πŸ’€

because a shared hash is encapsulated in a directory, rather than being a single non-directory file, the ability to perform file operations on it is limited. although it can be renamed or moved, under POSIX semantics such a rename can't atomically replace any file other than an empty directory. in particular, it can't atomically replace another shared hash. it also can't be hard-linked to have multiple names. (however, a major use case for link(2), non-overwriting renaming, can be achieved through rename(2) due to the latter's limitations for directories.) finally, it can't be unlinked. (linking and unlinking directories are possible for root on some systems, but cause undesirable filesystem irregularities.) πŸ’€

okay so you can dispose of a shared hash by hitting its directory with rm -r. πŸ’… do not think of this like unlink(2) (rm) on a regular file, bestie β€” because it doesn't just remove the object's name, it also disrupts its internal structure. 😭 if a process has an open handle referring to the shared hash at the time of rm -r, using the shared hash through that handle is likely to fail, although probably not immediately. and if a process is writing to the shared hash at the time of rm -r, there's a race condition that could prevent the removal from completing. so rm -r should only be applied after all processes have finished using the shared hash. periodt.

a shared hash can be copied with cp -r (not mere cp), tar, or similar means. ✨ it's safe to do this while processes have open handles referring to the shared hash, and while processes are reading from it. but β€” and this is the dramatic part β€” as with most forms of database file, if a process is writing to the shared hash then the file copier is liable to pick up an inconsistent (corrupted) view of the shared hash. πŸ’€ so copying should only be attempted at a time when no write operations are being performed. it's acceptable for processes to have the shared hash open in write mode, provided that they do not actually perform any write operation while the copy is being made. no cap.

a file-level copying operation applied to a shared hash is likely to result in a copy that occupies much more filesystem space than the original. 🫠 this happens because most of the time a large part of the main data file is a filesystem hole, not occupying any actual storage. some copying mechanisms (like GNU cp) can recognise this and avoid allocating unnecessary storage for the copy, but others (like GNU tar) will blindly fill space with a lot of zeroes. 😭 if the copy is subsequently used in shared hash write operations, ultimately it will recover from this inefficient block allocation. so it's not forever, but still.

🌸 Forking 🌸

if a process is duplicated by fork(2) while it holds a shared hash handle, the handle is duplicated with the rest of the process, so both resulting processes have handles referring to the same underlying shared hash. πŸ‘―β€β™€οΈ provided that the duplication did not happen during a shared hash operation, both processes' handles will subsequently work normally, and can be used independently. slay.

things are more difficult if a fork(2) happens while a shared hash operation is in progress. 😬 this should not normally be possible to achieve from Perl code: arbitrary Perl code should not run during the critical part of an operation. if a shared hash operator is given a tied variable as a parameter, the magic method call for access to that parameter occurs before the critical part, so a fork in that method is safe. if a signal is received during a shared hash operation, any signal handler installed in %SIG will be deferred until the operation is complete, so a fork in such a signal handler is also safe. a problematic fork(2) should only be achievable by XS code. πŸ‘‘

if a fork(2) does happen during the critical part of a shared hash operation, the two resulting handles are liable to interfere if the operation is resumed in both processes. 😭 in this case, it is safe for at most one process (which may be either of them) to resume the operation. the other process must neither resume the operation in progress nor make any further use of the handle. it is safe for the non-resuming process to chain a new program with execve(2), to terminate with _exit(2), or generally to make use of the C library before doing either of those. attempting to run Perl code would be unwise. fr fr.

on platforms lacking a native fork(2), the Perl function fork actually creates a Perl thread. in that case the behaviour should be similar to that seen with a real fork(2), as described in the next section.

🧡 Threads 🧡

this module can be used in multiple Perl threads simultaneously. ✨ the module may be loaded by multiple threads separately, or from Perl 5.8.9 onwards may be loaded by a thread that spawns new threads. (prior to Perl 5.8.9, limitations of the threading system mean that module data can't be correctly cloned upon thread spawning. any but the most trivial cases of thread spawning with this module loaded will crash the interpreter. πŸ’€ the rest of this section only applies to Perls that fully support cloning.)

if a thread is spawned while the parent thread has an open shared hash handle, the handle is duplicated, so that both resulting threads have handles referring to the same underlying shared hash. provided that the duplication did not happen during a shared hash operation, both threads' handles will subsequently work normally, and can be used independently. πŸ‘‘

πŸ›‘οΈ Tainting πŸ›‘οΈ

if taint mode is enabled, taintedness is relevant to some operations on shared hashes. shared hash handles mostly behave like regular file handles for tainting purposes. where the following description says that a result is "not tainted", that means it does not get the taint flag set merely by virtue of the operation performed; it may still be marked as tainted if other tainted data is part of the same expression, due to Perl's conservative taint tracking. 🫢

the classification functions are happy to operate on tainted arguments. their results are not tainted.

when opening a shared hash, if the shared hash filename or the mode string is tainted then it is not permitted to open for writing or with the possibility of creating. it is permitted to open non-creatingly for reading regardless of taint status. of course, any kind of opening is permitted in an untainted expression.

a shared hash handle per se is never tainted.

the results of the mode checking functions are not tainted.

the content of a shared hash is always treated as tainted. it is permitted to write tainted data to a shared hash. the data operations all accept tainted arguments. when reading from a shared hash, the keys existing in the hash and the values referenced by them are always tainted, but an absent item is treated as clean. so where a data operation returns a key or value from the shared hash, the result will be tainted if it is a string, but undef representing an absent item will not be tainted. the count of keys existing in the hash, size of the hash, and the length of an existing value are also tainted, being derived from tainted content. however, the truth values returned by some operations are not tainted, even if they are derived entirely from tainted data. periodt.

πŸ’– CONSTANTS πŸ’–

  • shash_referential_handle β€” truth value indicating whether each shared hash handle contains a first-class reference to the shared hash to which it refers. see "Filesystem referential integrity" above for discussion of the significance of this. πŸ‘‘

πŸ› οΈ FUNCTIONS πŸ› οΈ

the SHASH parameter that most of these functions take must be a handle referring to a shared hash object. no exceptions, bestie.

πŸ” Classification πŸ”

  • is_shash(ARG) β€” returns a truth value indicating whether ARG is a handle referring to a shared hash object.
  • check_shash(ARG) β€” checks whether ARG is a handle referring to a shared hash object. returns normally if it is, or dies if it is not. πŸ’…

πŸ”“ Opening πŸ”“

  • shash_open(FILENAME, MODE) β€” opens and returns a handle referring to a shared hash object, or dies if the shared hash can't be opened as specified. FILENAME must refer to the directory that encapsulates the shared hash.

if the filename string contains non-ASCII characters, then the filename actually used consists of the octets of the internal encoding of the string, which does not necessarily match the ostensible characters of the string. πŸ˜΅β€πŸ’« this gives inconsistent behaviour for the same character sequence represented in the two different ways that Perl uses internally. this is consistent with the treatment of filenames in Perl's built-in operators such as open; see "When Unicode Does Not Happen" in perlunicode. this may change in future versions of Perl (beyond 5.26).

MODE is a string controlling how the shared hash will be used. it can contain these characters:

  • r β€” the shared hash is to be readable. if this is not specified then read operations through the handle will be denied. beware that at the system-call level the files are necessarily opened readably. thus read permission on the files is required even if one will only be writing. 😬
  • w β€” the shared hash is to be writable. if this is not specified then write operations through the handle will be denied. this flag also determines in what mode the files are opened at the system-call level, so write permission on the files operates as expected.
  • c β€” the shared hash will be created if it does not already exist. the permission bits on the shared hash will be controlled by the creating process's umask. if this flag is not specified then the shared hash must already exist.
  • e β€” the shared hash must not already exist. if this is not specified and the shared hash already exists then it will be opened normally. this flag is meant to be used with c; it means that a successful open implies that this process, rather than any other, is the one that created the shared hash. πŸ‘‘

when a shared hash is created, some of its constituent files will be opened in read/write mode even if read-only mode was requested. shared hash creation is not an atomic process, so if a creation attempt is interrupted it may leave a half-created shared hash behind. 😭 this does not prevent a subsequent creation attempt on the same shared hash from succeeding: creation will continue from whatever stage it had reached. likewise, multiple simultaneous creation attempts may each do part of the job. this can result in ownerships and permissions being inconsistent; see "File permissions" above.

regardless of the combination of efforts leading to the creation of a shared hash, completion of the process is atomic. non-creating open attempts will either report that there is no shared hash or open the created shared hash. exactly one creation attempt will be judged to have created the shared hash, and this is detectable through the e flag. periodt.

πŸ”Ž Mode checking πŸ”Ž

  • shash_is_readable(SHASH) β€” returns a truth value indicating whether the shared hash can be read from through this handle.
  • shash_is_writable(SHASH) β€” returns a truth value indicating whether the shared hash can be written to through this handle. πŸ’…

shash_mode(SHASH)

Okay so this one hands you back a string where each character is basically a lil' status badge for this handle πŸ’… It's formatted the same way as the MODE parameter you'd pass to shash_open β€” EXCEPT the mode flags that only matter while you're opening the thing (c and e) get dropped. So the string you get back can contain these characters:

  • r β€” you can read from the shared hash through this handle πŸ‘€
  • w β€” you can write to the shared hash through this handle ✍️

🌸 Single-key data operations 🌸

For every single one of these, the key you're after (the KEY parameter) can be any octet (Latin-1) string, and values (VALUE params, plus some return values) can be any octet string or undef 🫢

Heads up though: undef here does not mean "the key is there but its value is undefined." Nope. undef means the key isn't in the hash at all. There is literally no present-but-undefined state, bestie, that's just not a thing here.

Also not allowed as keys or values:

  • strings with non-octets in them (Unicode chars above U+FF) πŸ™…
  • anything that isn't a string πŸ™…

And if you hand over a dualvar (a scalar carrying independent string and numeric values), only its string value gets used. The number part? Ignored, no cap.


shash_exists(SHASH, KEY)

Gives you a truth value answering: does this key currently point at a defined value in the shared hash? Simple vibes check βœ…

shash_getd(SHASH, KEY)

Deprecated alias for shash_exists πŸ’€ (iykyk, don't build new stuff on it)

shash_length(SHASH, KEY)

Returns the length in octets of whatever value this key currently references in the shared hash, or undef if the key isn't there at all πŸ“

shash_get(SHASH, KEY)

Returns the value this key currently references in the shared hash 🎁

shash_set(SHASH, KEY, NEWVALUE)

Changes the shared hash so that this key from now on references the value you specified. That's it, that's the whole thing ✨

shash_gset(SHASH, KEY, NEWVALUE)

Same deal β€” the key now references NEWVALUE β€” but it also hands you back whatever the key referenced before. And the whole swap goes down atomically πŸ”₯ (get + set, one indivisible moment, no sneaky interleaving)

shash_cset(SHASH, KEY, CHKVALUE, NEWVALUE)

This is the compare-and-swap queen πŸ‘‘ It looks at the value currently referenced by the key in the shared hash. If it's identical to CHKVALUE, then it modifies the shared hash so the key references NEWVALUE from now on, and returns true. If the current value is not identical to CHKVALUE, it leaves everything alone and returns false. And yep β€” this conditional modification is done atomically.

This function can be used as a core on which to build arbitrarily complex kinds of atomic operation (on a single key). For example, an atomic increment can be implemented as:

do {
    $ov = shash_get($shash, $key);
    $nv = $ov + 1;
} until shash_cset($shash, $key, $ov, $nv);

πŸ¦‹ Whole-hash data operations πŸ¦‹

shash_occupied(SHASH)

Truth value time: are there any items in the shared hash right now? πŸ«™

shash_count(SHASH)

Returns the number of items currently sitting in the shared hash πŸ”’

shash_size(SHASH)

Returns the approximate size, in octets, of the entire content of the shared hash.

Okay bestie, listen. The size of a hash is not a well-defined quantity, so you gotta interpret this return value with care 😭

Here's the tea: it specifically aims to indicate how much space is required in a shared hash data file to represent this content. That means it's affected by:

  • details of the file format (which may differ between shared hashes on one system 😳)
  • accidents of how the content is laid out in a particular shared hash

So calling this function twice on identical content will not necessarily produce identical results. And the details of the size estimation may also change in the future. It's giving approximate, fr fr.

And even though this function computes size specifically with respect to the file format used by this module, it does not directly tell you the amount of space occupied by a shared hash. Why? Because:

  • there's some non-content overhead
  • more importantly, the process by which content is modified requires space to store multiple versions of the content

It is normal for the amount of space actually occupied to fluctuate over the cycle of data file rewriting 🌊

The numbers you should mentally bookmark:

  • If shash_tidy is being used appropriately, the space occupied can be expected to vary up to a little over five times the size of the nominal content.
  • If shash_tidy is not used, then the normal maximum will be more than ten times the content size.
  • Occasional spike above these levels can be expected in any case, and fixed overheads make these multipliers inapplicable if the content is very small.

shash_key_min(SHASH)

Returns the lexicographically least of the keys currently in the shared hash, or undef if there are none πŸ”½

shash_key_max(SHASH)

Returns the lexicographically greatest of the keys currently in the shared hash, or undef if there are none πŸ”Ό

shash_key_ge(SHASH, KEY)

Returns the least of the keys currently in the shared hash that are lexicographically no less than the key you gave it, or undef if there are none ⬆️

shash_key_gt(SHASH, KEY)

Returns the least of the keys currently in the shared hash that are lexicographically greater than the key you gave it, or undef if there are none ↗️

shash_key_le(SHASH, KEY)

Returns the greatest of the keys currently in the shared hash that are lexicographically no greater than the key you gave it, or undef if there are none ⬇️

shash_key_lt(SHASH, KEY)

Returns the greatest of the keys currently in the shared hash that are lexicographically less than the key you gave it, or undef if there are none β†˜οΈ

shash_keys_array(SHASH)

Returns a reference to a plain array holding every key currently in the shared hash, in lexicographical order πŸ—‚οΈ The array and the key scalars inside it are unwritable β€” look, don't touch, bestie.

shash_keys_hash(SHASH)

Returns a reference to a plain hash where the keys are all the keys currently in the shared hash, and every single value is undef 🌫️ The value scalars are unwritable.

Writability of the hash itself is not guaranteed: currently in practice it is writable, but this may change in the future πŸ™ˆ

shash_group_get_hash(SHASH)

Returns a reference to a plain hash representing the entire current content of the shared hash πŸŽ€ The value scalars are unwritable.

Writability of the hash is likewise not guaranteed: currently in practice it is writable, but this may change in the future πŸ™ˆ


πŸ“Έ Snapshots πŸ“Έ

shash_snapshot(SHASH)

Returns a shared hash handle that encapsulates the current content of the shared hash. The entire state gets captured atomically ⚑ and you can then use the returned handle to do arbitrarily many read operations on that frozen state β€” it will never reflect later modifications to the shared hash.

The snapshot handle cannot be used for writing. Read-only, periodt πŸ’…

shash_is_snapshot(SHASH)

Truth value check: does this handle refer to a snapshot (as opposed to a live shared hash)? 🧐


🧹 Maintenance 🧹

shash_idle(SHASH)

Puts the shared hash handle into a state where it eats up less resources β€” at the cost of making the next operation through the handle more expensive 😬 This doesn't change the visible behaviour of the handle, and doesn't affect the state of the shared hash itself at all. The invisible operations performed by this function may vary between versions of this module.

When should you call it? When the handle is gonna sit unused for a lengthy period πŸŒ™ Like, if a long-running daemon uses a shared hash in brief bursts once an hour, it should idle its handle at the end of each burst of activity. Be a considerate little process, okay?

Currently the effect of this operation is to discard the handle's memory mapping of the shared hash data file. The next operation then has to reestablish the mapping.

Why does that matter? Because periodically the data file has to be replaced with a new one β€” but the old data file keeps existing as long as some process has it mapped 😩

  • A process that's actively using the shared hash will quickly notice the data file got replaced and will unmap the old one.
  • A process holding a handle it's not using, though, could keep that old data file in existence β€” occupying storage β€” long after it has no further use for it.

A handle that's been put into the idle state won't perpetuate the existence of an obsolete data file. Iconic cleanup behaviour ✨

shash_tidy(SHASH)

Rearranges the storage of the shared hash if it seems useful to do so, so that tidying work doesn't have to be performed by other processes 🧼 This doesn't change the visible content of the shared hash, but the handle must be open for writing, and this counts as a write operation for purposes concerned with the state of the underlying files. The invisible operations performed by this function may vary between versions of this module.

Call this when it's acceptable to incur some delay for the maintenance work to complete. For example, a cron job could call it periodically πŸ•°οΈ Essentially, calling this function signals "hey, this is a convenient time, and I'm a convenient process, go ahead and do maintenance now."

If this maintenance work isn't carried out by means of this function, then ultimately it will be performed anyway β€” just less predictably and possibly less conveniently 🫠 Eventually it becomes necessary to perform maintenance in order to continue using the shared hash, and at that point the next process that tries to write to it will carry out the work and eat the cost. The shared hash will still work properly in that case, but the unlucky writer gets a disproportionately large delay in the completion of its write operation. Which could very much be a problem if the shared hash is large 😭


πŸ”’ Event counters πŸ”’

shash_tally_get(SHASH)

Returns a reference to a hash of counts of events that have occurred with this shared hash handle πŸ“Š

These counts may be of interest for profiling and debugging purposes, but should not be relied upon for semantic purposes. The event types may vary between versions of this module. And honestly? Few of the event types make sense in terms of the API supplied by this module β€” most of them are internal implementation details. So peek if you're curious, but don't build logic on them πŸ’€

okay so bestie, events are counted separately for each handle β€” the events counted are tied specifically to the handle, not to the shared hash as a whole. like each handle has its own lil scorecard πŸ’…βœ¨ Generally, an idle handle will rack up no events, even if the shared hash it points at is active. fr fr, a handle just sitting there vibing will usually accumulate nothing, even when its shared hash is going feral. The event counters start at zero when a handle is opened, and can be reset to zero by shash_tally_zero or shash_tally_gzero.

In the returned hash, each key identifies a type of event, and the corresponding value is (unless wrapped) the number of times events of that type have occurred on the handle since the counters were last zeroed. Currently the event counters are held in fixed-size variables and can wrap, so if event counts might get as high as 2^64 then they can't be relied upon to be accurate. Wrapping will not occur at less than 2^64; in other respects, wrapping behaviour may change in the future. iykyk ✨

The event types that are currently counted are:

  • string_read β€” parse an octet string representation in a shared hash data file.
  • string_write β€” write an octet string representation into a shared hash data file.
  • bnode_read β€” parse a B-tree node representation in a shared hash data file.
  • bnode_write β€” write a B-tree node representation into a shared hash data file.
  • key_compare β€” compare two strings as shared hash keys.
  • root_change_attempt β€” attempt to replace the root pointer in a shared hash data file. This may be done to change the content of the shared hash, or as part of the process of switching to a new data file.
  • root_change_success β€” succeed in replacing the root pointer in a shared hash data file. An attempt will fail if another process changed the root pointer during the operation that required this process to change the root pointer.
  • file_change_attempt β€” attempt to replace the data file in a shared hash. This is necessary from time to time as data files fill up.
  • file_change_success β€” succeed in replacing the data file in a shared hash. An attempt will fail if another process replaced the data file while this process was initialising its new one.
  • data_read_op β€” perform a high-level data operation that purely reads from the shared hash: shash_exists, shash_length, shash_get, shash_occupied, shash_count, shash_size, shash_key_min, shash_key_max, shash_key_ge, shash_key_gt, shash_key_le, shash_key_lt, shash_keys_array, shash_keys_hash, or shash_group_get_hash.
  • data_write_op β€” perform a high-level data operation that writes, or at least may write, to the shared hash: shash_set, shash_gset, or shash_cset.

The value scalars in the returned hash are unwritable. Writability of the hash is not guaranteed: currently in practice it is writable, but this may change in the future. so don't get too comfy 🫢

shash_tally_zero(SHASH)

Zero the event counters that can be read by shash_tally_get. poof, back to zero ✨

shash_tally_gzero(SHASH)

Zero the event counters that can be read by shash_tally_get, and return the values the event counters previously had, in the same form as shash_tally_get. This swap is performed atomically. so it's giving β€œread and reset in one breath” πŸ’–


πŸ› BUGS πŸ›

okay so, as explained for shash_open, creation of a shared hash is not atomic. This is an unavoidable consequence of the need for the shared hash to consist of multiple files in a directory. Multi-party creation can result in the files having different permission bits; to avoid this, all creators should use the same umask. Multiple users writing to a shared hash can result in the files having different ownerships, so the permission bits must be chosen to work appropriately with the chimeric ownership. yeah, it's a whole situation 😭

When calls to the functions supplied by this module are compiled down to custom ops (which is attempted for performance reasons), the ability to deparse the resulting code with B::Deparse is limited. Prior to Perl 5.13.7, deparsing will generate very incorrect code. From Perl 5.13.7 onwards, deparsing should normally work, but will break if another module defines a separate type of custom op that happens to have the same short name (though these ops do not clash in other respects). no cap, that's a niche little gremlin 🧚


πŸ‘€ SEE ALSO πŸ‘€

  • Hash::SharedMem::Handle
  • Sereal

✍️ AUTHOR ✍️

Andrew Main (Zefram) <zefram@fysh.org>


  • Copyright (C) 2014, 2015 PhotoBox Ltd
  • Copyright (C) 2014, 2015, 2017 Andrew Main (Zefram) <zefram@fysh.org>

πŸ“œ LICENSE πŸ“œ

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


πŸ’– that's the whole page, bestie β€” source/version is Hash::SharedMem(3pm), perl v5.42.3, dated 2026-08-20. now go be the main character of your shared memory πŸ‘‘βœ¨

πŸ“¦ where this page comes from