Mojo · Batches and Arenas

Batches and Arenas

from ustore import On, DeferredOn, Completion, … 1.0.0-rc.1 · d6d40961

§ Receivers & Deferred Calls

The receiver chooses where a verb runs, what it sees and how it completes, and the verb stays the same. A database, transaction or snapshot runs a verb on the calling thread, handle.on(executor) blocks on an executor, and handle.deferred(executor) submits it there and answers a Deferred:

db.write(collections, keys, values)                       # the head, on the calling thread
db.on(executor).write(collections, keys, values)          # blocking, on the executor
var call = db.deferred(executor).read(collections, keys, arena)  # Deferred[...]
print(call.wait().present(0))

On and DeferredOn offer the same verbs with the same arguments, and the blocking verbs are written once, in the BlobsVerbs, StringsVerbs, DocsVerbs, GraphVerbs, PagesVerbs and DatabaseVerbs traits every blocking receiver conforms to. The verb names follow the C ones: blobs verbs are unprefixed, as write and scan, and the other modalities carry theirs, as strings_scan, docs_gather, graph_read_edges and pages_insert. Reads see the head of a database, a transaction, or a snapshot; writes join the receiver’s transaction, recording their keys unless it came from txn.unwatched(), and a snapshot’s receiver, which has none, writes at the head. stage, commit, rollback and reset act on the receiver’s transaction and save on its snapshot, and the engine answers Status.UNINITIALIZED for a receiver without one.

A Deferred owns its outcome and outputs until the call is final, and borrows the arena and every input it was handed, which Mojo keeps alive for as long as the Deferred is; freeing the database, the executor or a transaction waits for the calls naming them. Mojo has no runtime of its own to await on, so wait is the whole interface: it blocks in ustore_outcome_wait until the call is final, then answers the result or raises the failure, as many times as it is asked. status reads the outcome without blocking, and destroying an unfinished Deferred waits for it.

§ Arenas

Every output whose length only the call knows, values, keys, offsets, bounds, columns and edges, lands in an Arena you pass, and the verb answers a view over it without copying. Arena(executor) places it where that executor computes, decided once, and Arena(library) makes host pages for the calling thread. Each call rewinds its arena, so the previous call’s views die and a steady loop allocates once; reading a stale one raises Status.INVALID_ARGUMENT. To keep a result past the next call, pass that call another arena, and give every deferred call its own.

§ Progress & Cancellation

Scans take their start keys and how many keys every scan keeps, plus an optional config for the end keys, the direction, the patterns or filter, the predicate and the progress handler:

def stop_at_once(finished: Int, total: Int, seen: Pointer[Int, MutAnyOrigin]) -> Continuation:
    seen[] = finished
    return Continuation.CANCEL

def admit_even(keys: Span[Key, MutAnyOrigin], admissions: Span[Admission, MutAnyOrigin], state: Pointer[Int, MutAnyOrigin]):
    for index in range(len(keys)):
        admissions[index] = Admission.ADMIT if keys[index].uuid.bytes[15] % 2 == 0 else Admission.REJECT

var seen = 0
var config = BlobsScanConfig(predicate=Predicate.of[admit_even](Pointer(to=seen)))
var runs = db.scan(CollectionsView.repeating(people), KeysView.repeating(start), 100, arena, config)
_ = db.scan(collections, starts, 100, arena, BlobsScanConfig(progress=Progress.of[stop_at_once](Pointer(to=seen))))  # raises Status.CANCELLED

Handlers are compile-time parameters, since a capturing closure cannot cross into C, and their state crosses beside them as a pointer that must outlive the call. Progress handlers return a value, which Mojo 1.2 is confirmed to pass back on Linux x86-64; Linux AArch64 and macOS rest on the CI matrix.

API

StructOnA database, transaction or snapshot bound to the executor its verbs block on. Binding-only.
struct On[origin: ImmOrigin]

Parameters

originImmOrigincomptimeThe handle and the executor, which this receiver borrows.

Fields

scopeScopeWhat every verb addresses.

Conforms to

BlobsVerbs, Copyable, DatabaseVerbs, Deinitable, DocsVerbs, GraphVerbs, ImplicitlyCopyable, Movable, PagesVerbs, StringsVerbs
StructDeferredOnA database, transaction or snapshot bound to the executor its verbs are submitted to: every verb answers a Deferred at once, borrowing its arena and inputs until final. Binding-only.
struct DeferredOn[origin: ImmOrigin]

The verbs and their arguments are those of the blocking receivers.

Parameters

originImmOrigincomptimeThe handle and the executor, which this receiver borrows.

Fields

scopeScopeWhat every verb addresses.

Conforms to

BlobsDeferredVerbs, Copyable, DatabaseDeferredVerbs, Deinitable, DocsDeferredVerbs, GraphDeferredVerbs, ImplicitlyCopyable, Movable, PagesDeferredVerbs, StringsDeferredVerbs
StructCompletionWhether a verb returns with its final status or once submitted, ustore_completion_t.
struct Completion

The receiver picks it: a handle and handle.on(executor) pass BLOCKING, and handle.deferred(executor) passes DEFERRED, so no verb takes it as an argument.

Fields

codec_intThe raw ustore_completion_t value the ABI exchanges.

Values

BLOCKINGCompletion(Int32(0))Return with the final status stored and every output readable.
DEFERREDCompletion(Int32(1))Return once submitted; the status reads DEFERRED until final.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructMoodWhat an executor's threads or rings do once it goes idle, ustore_mood_t.
struct Mood

Fields

codec_intThe raw ustore_mood_t value the ABI exchanges.

Values

PARKEDMood(Int32(0))Parked, so the next call pays a wake-up.
SPINNINGMood(Int32(1))Spinning, trading power for the next call's latency.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructCPUClassThe CPU generation a database or an executor confines itself to, ustore_cpu_class_t.
struct CPUClass

Fields

codec_intThe raw ustore_cpu_class_t value the ABI exchanges.

Values

UNKNOWNCPUClass(Int32(0))Whatever the running CPU offers.
SERIALCPUClass(Int32(1))The portable floor every CPU runs.
INTEL_HASWELLCPUClass(Int32(2))x86 cores from Intel Haswell on.
INTEL_SIERRACPUClass(Int32(3))x86 efficiency cores from Intel Sierra Forest on.
INTEL_SKYLAKECPUClass(Int32(4))x86 server cores from Intel Skylake on.
INTEL_ICELAKECPUClass(Int32(5))x86 server cores from Intel Ice Lake on.
AWS_G2CPUClass(Int32(6))Arm cores from AWS Graviton 2 on.
APPLE_M2CPUClass(Int32(7))Arm cores from Apple M2 on.
AWS_G5CPUClass(Int32(8))Arm server cores from AWS Graviton 5 on.
ALIBABA_C930CPUClass(Int32(9))RISC-V cores with RVV, Zawrs and Zacas, from Alibaba's C930 on.
MONOVERSIONEDCPUClass(Int32(10))The one class of a monoversioned build.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructTransportHow the NVMe engine moves bytes between its files and memory, ustore_transport_t.
struct Transport

Fields

codec_intThe raw ustore_transport_t value the ABI exchanges.

Values

AUTOMATICTransport(Int32(0))io_uring where the kernel offers it, Linux AIO where it offers only that, and blocking calls elsewhere.
BLOCKINGTransport(Int32(1))Positioned reads and writes that block the polling thread.
LINUX_URINGTransport(Int32(2))io_uring on unbuffered files.
LINUX_AIOTransport(Int32(3))Linux AIO on unbuffered files.
LINUX_SPDKTransport(Int32(4))SPDK's user-space driver on NVMe devices the process owns.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructGdsHow a VRAM executor moves bytes between the drives and its device, ustore_gds_t.
struct Gds

Fields

codec_intThe raw ustore_gds_t value the ABI exchanges.

Values

AUTOMATICGds(Int32(0))Direct transfers where the driver is loaded, and the compatibility mode elsewhere.
DIRECTGds(Int32(1))Direct transfers, refused with DEVICE where the driver is absent.
COMPATIBILITYGds(Int32(2))The compatibility mode, which bounces through host pages it manages.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructGPUVendorThe GPU vendors one binary drives at once, ustore_gpu_vendor_t.
struct GPUVendor

Fields

codec_intThe raw ustore_gpu_vendor_t value the ABI exchanges.

Values

UNKNOWNGPUVendor(Int32(0))No vendor.
NVIDIAGPUVendor(Int32(1))NVIDIA, through the CUDA driver.
AMDGPUVendor(Int32(2))AMD, through the HIP runtime.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructGPUClassThe GPU generations UStore tells apart, ustore_gpu_class_t.
struct GPUClass

Fields

codec_intThe raw ustore_gpu_class_t value the ABI exchanges.

Values

UNKNOWNGPUClass(Int32(0))No class.
NVIDIA_AMPEREGPUClass(Int32(1))Ampere-class NVIDIA and every NVIDIA GPU without a class of its own.
NVIDIA_ADAGPUClass(Int32(2))Ada-class NVIDIA.
NVIDIA_HOPPERGPUClass(Int32(3))Hopper-class NVIDIA.
NVIDIA_BLACKWELLGPUClass(Int32(4))Blackwell-class NVIDIA datacenter parts.
NVIDIA_BLACKWELLRTXGPUClass(Int32(5))Consumer Blackwell NVIDIA.
AMD_CDNA3GPUClass(Int32(6))CDNA3 AMD, gfx942.
AMD_CDNA4GPUClass(Int32(7))CDNA4 AMD, gfx950.
AMD_CDNA5GPUClass(Int32(8))CDNA5 AMD, gfx1250.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructGPUDeviceOne GPU as UStore numbers it, ustore_gpu_device_t: its position in Library.gpu_devices is the ordinal a VRAM executor names it by.2
struct GPUDevice

Fields

vendorGPUVendorThe vendor whose runtime drives the device.
vendor_ordinalUInt32The device's ordinal in its vendor's runtime.
gpu_classGPUClassThe class of the device.
compute_unitsUInt32Streaming multiprocessors or compute units.
memory_bytesUInt64Device memory in bytes.

Conforms to

Copyable, Defaultable, Deinitable, Movable
__init__An empty entry for the listing to fill.
def __init__(out self)
nameThe vendor's own name for the device.
def name(self) -> String
StructContinuationWhether the call a progress handler is watching carries on or unwinds, ustore_continuation_t.
struct Continuation

Fields

codec_intThe raw ustore_continuation_t value the ABI exchanges.

Values

CONTINUEContinuation(Int32(0))Carry on to the next unit of work.
CANCELContinuation(Int32(1))Stop, publish nothing, and finish the call with CANCELLED.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructProgressHow a long call reports its advance: the progress and progress_state pair of every verb that takes one. The handler never runs concurrently with itself, nor after its call is final.2
struct Progress

Fields

callbackAddressThe handler's C entry point; NULL_ADDRESS skips every report.
stateAddressForwarded into every report; it must outlive the call.

Conforms to

Copyable, Defaultable, Deinitable, ImplicitlyCopyable, Movable
__init__No handler: the call reports nothing.
def __init__(out self)
ofReports to handler with state, which returns Continuation.CANCEL to stop the call.
def of[T: AnyType, origin: MutOrigin, //, handler: def(Int, Int, Pointer[T, MutAnyOrigin]) thin -> Continuation](
    state: Pointer[T, origin]
) -> Self

Parameters

TAnyTypecomptimeWhat state points at, inferred from it.
originMutOrigincomptimeThe state's origin, inferred with it.
handlerdef(Int, Int, Pointer[T, MutAnyOrigin]) thin -> ContinuationcomptimeCalled with the units finished, the units in total, and state.
statePointer[T, origin]Forwarded into every report; it must outlive the call.
StructExecutorThe threads or rings a verb runs on, freed once it and every device arena it made go out of scope.6
struct Executor

Executor(library) owns nothing: every verb handed it runs on the calling thread. Calls on one executor start in submission order, and a blocking call runs alone.

Fields

libraryLibraryThe loaded engine, held so its symbol table outlives every call made through the handle.
addressAddressThe opaque ustore_executor_t every verb hands back to the engine; NULL_ADDRESS runs inline.
vramBoolWhether it drives a GPU, so the arenas it makes hold device memory.

Conforms to

Deinitable, Movable
__init__The caller-thread executor, which every verb accepts.
def __init__(out self, library: Library)
def __init__(out self, *, library: Library, address: Int, vram: Bool)

Adopts a handle that is already spawned, which is how the factories hand one back.

Parameters

libraryLibraryThe loaded engine, kept so the shared object stays mapped.
for_ramSpawns host threads for the RAM, RocksDB and LevelDB engines. Calls ustore_executor_for_ram.
def for_ram[ram_origin: ImmOrigin, //](
    library: Library,
    ram: RamOptions[ram_origin] = RamOptions(),
    threads: Int = Int(0),
    deferred_limit: Int = Int(0),
    cpu_class: CPUClass = CPUClass.UNKNOWN
) -> Self

Parameters

libraryLibraryThe loaded engine, kept so the shared object stays mapped.
ramRamOptions[ram_origin]The memory domains it serves; copied, not borrowed.
threadsIntThreads to spawn; zero takes 7/8 of the cores those domains carry.
deferred_limitIntDeferred calls it holds at once; zero takes a default.
cpu_classCPUClassThe class its threads confine their waits and atomics to; UNKNOWN derives.

Raises

Error if the domains cannot be resolved or the OS refuses the threads.
on_poolSchedules onto a borrowed ForkUnion pool, so several libraries in one process share one set of threads. Calls ustore_executor_on_pool.
def on_pool[ram_origin: ImmOrigin, //](
    library: Library,
    pool: Int,
    ram: RamOptions[ram_origin] = RamOptions(),
    deferred_limit: Int = Int(0)
) -> Self

Parameters

libraryLibraryThe loaded engine, kept so the shared object stays mapped.
poolIntA live ForkUnion fu_pool_t, which must outlive the executor.
ramRamOptions[ram_origin]The memory domains it serves; copied, not borrowed.
deferred_limitIntDeferred calls it holds at once; zero takes a default.

Raises

Error if the pool or the domains are refused.
for_nvmeSpawns threads for the NVMe engine, each submitting and polling its own I/O ring. Calls ustore_executor_for_nvme.
def for_nvme(
    library: Library,
    nvme: NvmeOptions = NvmeOptions(),
    threads: Int = Int(0),
    deferred_limit: Int = Int(0),
    cpu_class: CPUClass = CPUClass.UNKNOWN
) -> Self

Parameters

libraryLibraryThe loaded engine, kept so the shared object stays mapped.
nvmeNvmeOptionsHow the databases it opens move bytes; copied, not borrowed.
threadsIntThreads to spawn; zero derives them from the cores.
deferred_limitIntDeferred calls it holds at once; zero takes a default.
cpu_classCPUClassThe class its threads confine their waits and atomics to; UNKNOWN derives.

Raises

Error if the transport is unknown or the OS refuses the threads.
for_vramOpens one submission stream on device, or borrows the caller's, driven by one host thread. Calls ustore_executor_for_vram.
def for_vram(
    library: Library,
    device: Int = Int(0),
    stream: Int = Int(0),
    scratch_limit: Int = Int(0),
    gds: Gds = Gds.AUTOMATIC,
    deferred_limit: Int = Int(0)
) -> Self

Parameters

libraryLibraryThe loaded engine, kept so the shared object stays mapped.
deviceIntThe UStore ordinal of the device, its position in Library.gpu_devices.
streamIntA borrowed stream on that device, which must outlive the executor; null creates one.
scratch_limitIntCeiling on the device scratch a call takes; zero lets it grow.
gdsGdsHow reads and compactions move bytes between the drives and the device.
deferred_limitIntDeferred calls it holds at once; zero takes a default.

Raises

Error with DEVICE if no device carries the ordinal.
drainWaits for every call submitted before the drain, then adopts mood whenever it next idles. Calls ustore_executor_drain.
def drain(self, mood: Mood = Mood.PARKED)

Raises

Error for a failure of the executor itself, never one of a drained call.
StructResidencyWhere an arena's outputs live, ustore_residency_t.
struct Residency

Fields

codec_intThe raw ustore_residency_t value the ABI exchanges.

Values

UNKNOWNResidency(Int32(0))No residency, which only a zeroed description reads as.
RAMResidency(Int32(1))Host pages, which the calling thread reads in place.
VRAMResidency(Int32(2))One device's memory, which only that device's code reads.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructArenaThe memory every output whose length scales with a call lands in, freed when it goes out of scope.4
struct Arena

Made where an executor computes, decided once; nothing is allocated until a call writes into it. Every call handed it rewinds and refills it, which invalidates the previous call's outputs, so a steady loop allocates once. One arena serves one call at a time; a second concurrent use is refused by the engine as CONTENDED, which leaves the holder's results readable.

A result refuses every read with INVALID_ARGUMENT once a later call rewinds its arena, and a result over a device arena refuses every host read with DEVICE.

Conforms to

Deinitable, Movable
__init__Host pages for calls on the calling thread. Calls ustore_arena_init.
def __init__(out self, library: Library)
def __init__(out self, executor: Executor)

Memory where executor computes, serving the calls of its kind. Calls ustore_arena_init.

__deinit__Frees the arena and every output in it, then lets a device arena's executor go. Calls ustore_arena_free.
def __deinit__(deinit self)

The executor and the loader reference are surrendered only after the call, for the reason _ExecutorHandle records.

vramWhether its memory is a GPU's, which host reads refuse. Binding-only.
def vram(self) -> Bool
describeWhere the arena's outputs live, as the library reports it. Calls ustore_arena_describe.
def describe(self) -> ArenaDescription
StructDeferredA call submitted to an executor, which owns its outcome and outputs until it is final. Binding-only.4
struct Deferred[C: Call]

status reads DEFERRED until then, without blocking, and wait blocks until the call is final and answers its result or raises its failure, as many times as it is asked. The result is the view a blocking call answers, borrowing the same arena. Dropping an unfinished one waits the same way, so nothing the engine still writes is ever freed under it. The crossing's origins keep the arena and every borrowed input alive for as long as this is, and freeing the database, the executor or a transaction waits for the calls naming them.

Parameters

CCallcomptimeThe verb's crossing, which names the result.

Fields

libraryLibraryThe loaded engine, held so the wait can still reach ustore_outcome_wait.
slotsOwnedPointer[CallSlots[C.Outputs]]The outcome and outputs the engine writes, on the heap since they outlive the verb.
callOptional[C]What the call lends the engine, until a wait concludes it.
answerOptional[C.Result]The result, once a wait found the call succeeded.
failureOptional[Error]The failure, once a wait found the call failed.
generationIntThe stamp served answered at submission, so a result waited on late still reads as stale.

Conforms to

Deinitable, Movable
__init__Submits call deferred, on the receiver's executor.
def __init__(out self, scope: Scope, var call: C)
__deinit__Waits until the call is final, then frees what it lent. Calls ustore_outcome_wait.
def __deinit__(deinit self)
statusDEFERRED until the call is final, then its final status; never blocks. Calls ustore_outcome_status.
def status(self) -> Status
waitBlocks until the call is final, then answers its result or raises its failure, every time it is asked. Calls ustore_outcome_wait.
def wait(mut self) -> ref[self] C.Result

Raises

Error carrying the call's failure.