Skip to content

Shared Named Values

SharedNamedValues

Byte values under case-insensitive names, shared by every process that opens the same three files: a RawHashMap from the hash of each name to the block that holds its value, a RawArena of blocks, and a SharedEpochs table that keeps a block readable while a reader who found it is still reading. It is what the shm: PowerShell drive stores its values in.

Files

create(dir, stem, layout) obtains <stem>.map, <stem>.arena and <stem>.epochs in dir, creating each that is absent and attaching to each that is present. The layout names the map’s capacity (the most names the store holds), the arena’s block space and class range, and the pin slots (the most readers at once, across every process).

A value’s life

  • set(name, value) takes a block for the name and the value, writes both, swaps the block’s handle into the map under the name’s hash, publishes the block, and retires the block the swap replaced. A name the store holds keeps the spelling it was first written with.
  • get(name) pins the current epoch, looks the hash up, reads the block, and checks the name stored in it against the one asked for. An entry that names a block still being written belongs to a writer between its swap and its publish: the reader yields until the writer publishes, and collects when the writer has died, which publishes it.
  • remove(name) takes the entry out of the map and retires its block.
  • entries() lists every name as first written with its value.

A block retired at an epoch stays readable until every pin taken before that epoch is released, then comes free through the arena’s reclaim.

Names

A name is compared without regard to case: its key is the 128-bit FNV-1a hash of its lowercase form (name_key). The name as first written is stored ahead of the value, so a listing shows it and so two names whose hashes collide are told apart: the second is refused with NameCollision, naming both, never merged with the first. A name is at most 65,535 bytes of UTF-8.

Errors

ErrorWhen
TooManyNames { capacity }the map has no slot left for a new name
TooLarge { bytes, max }the name and value together need more than the largest block holds
Full { bytes }no block could be found for the value, after collecting
NameCollision { stored, asked }another name with the same hash holds the entry
NameTooLong { bytes, max }the name is longer than 65,535 bytes
Corrupta block’s stored name cannot be read
Map, Arena, Epochsthe underlying structure’s error

Recovery

A writer that dies between taking a block and publishing it, or between the swap and the retire, leaves a block the map does not reach, or reaches while the block is still marked as being written. When the arena reports itself exhausted, collect() walks it with the live map as the root set: a block is reached when the map’s entry for the name stored in it is that very block, which is read from the map as the walk goes rather than from a snapshot, so a value swapped in during the walk is never taken for garbage.

Verified

The module’s unit tests run a store of 16 names over a 64 KiB arena with classes 6 to 12 and 4 pins: a value comes back under its name in any case; replacing a value retires its block and a pinned reader still reads it; removing a name retires its block; entries list every name as first written; a name whose hash another name holds is refused; a store holds its names and no more; a full arena is collected with the map as its root set; names and values past their bounds are refused; a second handle on the same files sees the first one’s values.