C · Batches and Arenas

Batches and Arenas

#include <ustore/types.h> 1.0.0-rc.1 · d6d40961

§ Executors, Blocking and Deferred Calls

An executor owns the threads that run calls and the scratch they use, and a NULL executor runs a call on the calling thread. Its kind picks where calls compute: a RAM or NVMe executor serves every engine on host threads, and a VRAM executor serves the reads and scans an engine computes on its one device, refusing changes, the control plane and every call no device tower serves yet with ustore_status_unsupported_k. ustore_executor_for_ram spawns its threads, caller-exclusive, so its first thread can serve a call in the caller’s place. Its ram options name the memory domains it serves, NUMA nodes as the operating system numbers them, where NULL derives every domain the process may allocate on and an unknown one is refused with ustore_status_invalid_argument_k. Each thread keeps its scratch on its local domain when the options name it, and on the first named domain otherwise, and the call’s own scratch and every arena the executor makes live where its first thread’s does. Zero threads take 7/8 of the cores local to those domains, rounded down and at least one, leaving the rest to blocking calls on their callers’ threads, and to the background flushes and compactions of RocksDB and LevelDB. ForkUnion spawns the threads near every domain or near one alone, so naming several domains but not all spreads them over the whole machine. ustore_executor_on_pool borrows a ForkUnion pool another library owns, which must outlive the executor, and places its scratch and arenas by the same ram options; a dispatch on a pool that runs another is refused, so a call finding the pool running its lender’s dispatch finishes ustore_status_contended_k instead of forking over the lender. ustore_executor_for_nvme spawns the threads of the NVMe engine, and its nvme options tell every database ustore_database_open_nvme opens on it how to move bytes: the transport, the channels and their buffers, read once at the open. It may borrow an executor of ustore_executor_for_vram, whose CUDA device and scratch limit the compactions take, and which answers ustore_status_unsupported_k in a library built without CUDA. That executor queues every call on one stream of its device, its own or one the caller lends through stream, from the calling thread, which waits on it there, so a deferred call is final when it returns. A call blocks by default and returns with its outcome final, while ustore_completion_deferred_k returns once the call is submitted:

int32_t const memory_domains[1] = {0};
ustore_executor_for_ram(&(ustore_executor_for_ram_t){
    .executor = &executor,
    .outcome = &outcome,
    .ram = {memory_domains, 1},
});

ustore_outcome_t outcomes[2] = {{0}};
for (size_t batch = 0; batch != 2; ++batch) { // one call per executor at a time, so two executors overlap two reads
    reads[batch].executor = executors[batch], reads[batch].outcome = &outcomes[batch], reads[batch].arena = arenas[batch];
    reads[batch].completion = ustore_completion_deferred_k;
    ustore_blobs_read(&reads[batch]);
}
while (ustore_outcome_status(&outcomes[0]) == ustore_status_deferred_k ||
       ustore_outcome_status(&outcomes[1]) == ustore_status_deferred_k)
    do_other_work(); // polling alone finishes both

Submission copies the argument struct, so the caller may reuse or drop it at once, while everything it points to, the outcome, inputs, outputs and arena alike, stays borrowed until the call is final. A deferred call’s status reads ustore_status_deferred_k until it finishes; the library then stores the message and the final status with release ordering and touches nothing the struct points to after that. The caller reads the status with acquire ordering, polling through ustore_outcome_status or waiting through ustore_outcome_wait, and never loads the field itself. An executor serves one call at a time, and nothing queues: a call on an executor serving another, from any thread, finishes at once with ustore_status_contended_k. A blocking call on an executor runs on the calling thread, and a deferred call is delegated to the executor’s first thread, which serves it in the caller’s place, so polling alone finishes it. Inside a call every fork of the executor’s pool splits the call’s batch over the other threads, in both modes. A call ends in one order: it releases its arena, cursor and transaction and its counts on the handles it names, then the executor, and stores its status last, so a caller seeing it final may submit again or free at once. On a borrowed pool, whose lender keeps its dispatches and joins, and on a caller-inclusive pool, as a build without OS threads spawns, a deferred call runs at submission and is final when it returns. ustore_executor_drain waits until no call holds the executor, then parks its threads or keeps them spinning. A call is refused with ustore_status_contended_k rather than waited for when its executor, arena, transaction or cursor serves another call, or when it drains its executor from a callback of that executor’s own call. Freeing a database, transaction, snapshot, cursor or arena waits until every executor call naming it ends, so a binding may drop its handle before those calls finish, but never from a callback of one of them. A progress callback never waits on a call its own executor has not finished, and polls the status instead. The C++ wrappers and the Rust and Python bindings choose the executor and the completion by receiver, as database.on(executor) and database.deferred(executor), so blocking calls read as they would without executors; the Design sections of rust/README.md and python/README.md state the rules every binding keeps. A call stops early only when its progress callback returns ustore_cancel_k, as Progress and Cancellation describes.

§ Inclusive and Exclusive Executors

A pool is caller-inclusive when the thread that spawned or lent it counts as one of its threads, and caller-exclusive when it only dispatches and waits. An inclusive pool needs its caller to run anything, so a deferred call on it runs at submission and is final when it returns. An exclusive pool, which every executor UStore spawns is, delegates a deferred call to its own first thread, one call at a time, and forks the other threads for it. So one manager thread in int main() can overlap a USearch RAM executor, a UStore NVMe executor and a UForm VRAM executor, giving each one deferred call and polling the three statuses, with no thread of its own per engine.

§ Progress and Cancellation

Scans, cursor pulls, saves and compactions take an optional progress callback and the progress_state handed to it. Each reports in the units its verb consumes: finished tasks for a scan, one unit for a pull, bytes of keys and values for a save, and bytes compacted where the engine measures them, as RocksDB does, or collections elsewhere. A callback never runs concurrently with itself, however many threads do the work, never runs after the call is final, and never sees finished decrease:

static ustore_continuation_t within_budget(ustore_size_t finished, ustore_size_t total, void* state) {
    ustore_size_t const budget = *(ustore_size_t const*)state;
    fprintf(stderr, "saved %llu of %llu bytes\n", (unsigned long long)finished, (unsigned long long)total);
    return finished <= budget ? ustore_continue_k : ustore_cancel_k;
}
ustore_size_t budget = 1ull << 30;
char const path[] = "/var/backups/ustore";
ustore_snapshot_save(&(ustore_snapshot_save_t){
    .snapshot = snapshot,
    .outcome = &outcome,
    .path = {(uint8_t const*)path, sizeof(path) - 1},
    .progress = within_budget,
    .progress_state = &budget,
});

Returning ustore_cancel_k is the only way to stop a call, which then answers ustore_status_cancelled_k:

  • scan and pull outputs stay NULL, and a cancelled pull leaves its cursor where it was;
  • a cancelled save removes what it wrote, as a failed one does;
  • a cancelled compaction stops between collections, keeping what it compacted.

The callback runs on the thread that runs the call, the caller’s for a blocking call and one of the executor’s for a deferred one, so a binding’s callback enters its language’s runtime there.

API

Types

ustore_outcome_tOutcome of one call: a status to branch on, and a static message that is never freed and is NULL exactly when the status is ustore_status_success_k.
types.h:357 ↗
struct ustore_outcome_t

Fields

statusustore_status_tWhat went wrong, ustore_status_success_k, or ustore_status_deferred_k until a deferred call finishes.
messagechar const *Static detail the status cannot carry.
ustore_completion_tWhether a call returns final or once submitted to its executor.
types.h:392 ↗
enum ustore_completion_t

Values

ustore_completion_blocking_k0Return with the final status stored and every output readable; the default.
ustore_completion_deferred_k1Return once submitted, with the status ustore_status_deferred_k until the call finishes; the caller waits or polls.
ustore_continuation_tWhether the call a progress callback watches carries on or unwinds.
types.h:415 ↗
enum ustore_continuation_t

Values

ustore_continue_k0Carry on to the next unit of work.
ustore_cancel_k1Stop, publish nothing, and report ustore_status_cancelled_k.
ustore_progress_callback_tPeriodic report of a long call's advance, and the caller's only way to stop it.
types.h:434 ↗
typedef ustore_continuation_t(*) ustore_progress_callback_t(ustore_size_t finished, ustore_size_t total, void *state);

Counts are in the units the verb consumes: tasks for a scan, one pull for a cursor, bytes of keys and values for a save, and bytes or collections for a compaction. The callback never runs concurrently with itself, however many threads do the work, and never after the call stores its final status. Its arguments are the units finished so far, never decreasing within one call, the units the call finishes if it runs to the end, and the progress_state registered beside it.

ustore_executor_tAn opaque handle to the threads or I/O rings that run calls, shared by any number of databases.
types.h:528 ↗
typedef struct ustore_executor_handle_t * ustore_executor_t;

Its kind picks where calls compute: host threads for every engine, or one device for the reads and scans an engine computes there, where a change, the control plane and every call no device tower serves yet answer ustore_status_unsupported_k, and an arena or cursor of another kind of executor ustore_status_device_k. Calls on one executor start in submission order, and a blocking call runs alone: after every call submitted before it finishes, and before any submitted after it starts. Databases stay freely concurrent. A blocking call runs on the calling thread, while one driver thread hands deferred calls in batches to the executor's threads. In a build without OS threads, an executor runs every call at submission, before returning. It owns the scratch and staging its calls need, placed where it computes and grown to a high-water mark, so a steady workload allocates once: a RAM executor keeps the scratch of each thread on the memory domain local to it.

An executor holds its deferred calls in a deferred table sized at creation by deferred_limit, so submitting never allocates, and a deferred submission that finds the table full stores ustore_status_contended_k as its final status. A call leaves the table before its status turns final, so the table is full only of calls no caller has seen final. From inside a progress callback its own executor runs, ustore_executor_drain and a blocking call on that executor report ustore_status_contended_k, so nothing waits on itself. Frees cannot report, so ustore_executor_free is never called there, nor the free of a handle a deferred call names.

The library stages only to change layout on the side that computes, or to make pageable memory DMA-able, and never routes device bytes through the host:

CaseAction
Device inputs on the executor's deviceread in place
Device inputs needing padding or a casta device-to-device copy or a cast kernel
Pinned or managed host inputsa direct asynchronous upload
Pageable host inputsthe pinned bounce buffer, then an upload
Outputs in an arena on the executor's devicethe kernel writes them directly
ustore_mood_tWhat an executor's threads or rings do once it goes idle.
types.h:531 ↗
enum ustore_mood_t

Values

ustore_mood_parked_k0Parked, so the next call pays a wake-up.
ustore_mood_spinning_k1Spinning, trading power for the next call's latency.

Spellings

char const * ustore_mood_name(ustore_mood_t mood);
ustore_mood_t ustore_mood_from_name(ustore_bytes_view_t name);
ustore_cpu_class_tThe CPU generation a database or an executor confines itself to, named as the sibling libraries name it.
types.h:548 ↗
enum ustore_cpu_class_t

Values

ustore_cpu_class_unknown_k0Whatever the running CPU offers.
ustore_cpu_class_serial_kThe portable floor every CPU runs.
ustore_cpu_class_intel_haswell_kx86 cores from Intel Haswell on.
ustore_cpu_class_intel_sierra_kx86 efficiency cores from Intel Sierra Forest on.
ustore_cpu_class_intel_skylake_kx86 server cores from Intel Skylake on.
ustore_cpu_class_intel_icelake_kx86 server cores from Intel Ice Lake on.
ustore_cpu_class_aws_g2_kArm cores from AWS Graviton 2 on.
ustore_cpu_class_apple_m2_kArm cores from Apple M2 on.
ustore_cpu_class_aws_g5_kArm server cores from AWS Graviton 5 on.
ustore_cpu_class_alibaba_c930_kRISC-V cores with RVV, Zawrs and Zacas, from Alibaba's C930 on.
ustore_cpu_class_monoversioned_kThe one class of a monoversioned build, whatever its floor implies or its preset vouches for.

Spellings

char const * ustore_cpu_class_name(ustore_cpu_class_t cpu_class);
ustore_cpu_class_t ustore_cpu_class_from_name(ustore_bytes_view_t name);
ustore_ram_options_tThe host memory domains a RAM executor serves, NUMA nodes as the operating system numbers them.
types.h:599 ↗
struct ustore_ram_options_t

Names where the executor keeps memory and runs its threads, never where a database keeps its payload. The scratch of each thread lives on the domain local to that thread when the options name it, and on the first domain named otherwise, as does every arena the executor makes.

Fields

memory_domainsint32_t const *Identifiers of the memory domains, copied by the call, where NULL derives every domain the process may allocate on, and an identifier no domain carries is refused with ustore_status_invalid_argument_k.
memory_domains_countustore_size_tEntries of memory_domains, where zero derives as NULL does.
ustore_transport_tHow the NVMe engine moves bytes between its files and memory.
types.h:692 ↗
enum ustore_transport_t

Values

ustore_transport_automatic_k0io_uring where the kernel offers it, Linux AIO where it offers only that, and blocking calls elsewhere.
ustore_transport_blocking_k1Positioned reads and writes that block the polling thread, which every platform has.
ustore_transport_linux_uring_k2io_uring on unbuffered files, refused where the kernel creates no ring.
ustore_transport_linux_aio_k3Linux AIO on unbuffered files.
ustore_transport_linux_spdk_k4SPDK's user-space driver on NVMe devices the process owns.

Spellings

char const * ustore_transport_name(ustore_transport_t transport);
ustore_transport_t ustore_transport_from_name(ustore_bytes_view_t name);
ustore_nvme_options_tHow the threads of an NVMe executor move bytes between the drives and memory, where every zero size or count keeps the engine's default.
types.h:725 ↗
struct ustore_nvme_options_t

Every thread of the executor keeps a channel of its own, and the threads outside it share shared_io_channels more. A channel is one queue pair of queue_depth submissions over pinned request and streaming buffers.

Fields

transportustore_transport_tHow bytes move between the files and memory.
shared_io_channelsustore_size_tI/O channels the threads outside the executor share.
queue_depthustore_size_tSubmissions a channel holds at once.
request_buffer_bytesustore_size_tBytes of each request buffer of a channel.
request_buffers_countustore_size_tRequest buffers of each channel.
stream_buffer_bytesustore_size_tBytes of each streaming buffer of a channel.
stream_buffers_countustore_size_tStreaming buffers of each channel.
ustore_gds_tHow a VRAM executor moves bytes between the drives and its device through GPUDirect Storage.
types.h:791 ↗
enum ustore_gds_t

Values

ustore_gds_automatic_k0Direct transfers where the nvidia_fs driver is loaded, and cuFile's compatibility mode elsewhere.
ustore_gds_direct_k1Direct transfers, refused with ustore_status_device_k where the driver is absent.
ustore_gds_compatibility_k2cuFile's compatibility mode, which bounces through host pages it manages.

Spellings

char const * ustore_gds_name(ustore_gds_t gds);
ustore_gds_t ustore_gds_from_name(ustore_bytes_view_t name);
ustore_gpu_vendor_tThe GPU vendors one binary drives at once, each through a runtime opened on first use.
types.h:812 ↗
enum ustore_gpu_vendor_t

Values

ustore_gpu_vendor_unknown_k0No vendor, as an unrecognized spelling reads back.
ustore_gpu_vendor_nvidia_k1NVIDIA, through the CUDA driver when one is installed.
ustore_gpu_vendor_amd_k2AMD, through the HIP runtime when one is installed.

Spellings

char const * ustore_gpu_vendor_name(ustore_gpu_vendor_t vendor);
ustore_gpu_vendor_t ustore_gpu_vendor_from_name(
    ustore_bytes_view_t name);
ustore_gpu_class_tThe GPU generations UStore tells apart, the device twin of ustore_cpu_class_t: a class implies its vendor, and within a vendor a newer device takes the newest class it satisfies.
types.h:833 ↗
enum ustore_gpu_class_t

Values

ustore_gpu_class_unknown_k0No class, which no listed device carries.
ustore_gpu_class_nvidia_ampere_k1Ampere-class NVIDIA and every NVIDIA GPU without a class of its own - A100, A10 & RTX 30.
ustore_gpu_class_nvidia_ada_k2Ada-class NVIDIA - L4, L40S & RTX 40.
ustore_gpu_class_nvidia_hopper_k3Hopper-class NVIDIA - H100 & H200.
ustore_gpu_class_nvidia_blackwell_k4Blackwell-class NVIDIA - B200, B300 & GB300.
ustore_gpu_class_nvidia_blackwellrtx_k5Consumer Blackwell NVIDIA - RTX 50, RTX PRO 6000 & DGX Spark.
ustore_gpu_class_amd_cdna3_k6CDNA3 AMD - MI300X & MI325X, gfx942.
ustore_gpu_class_amd_cdna4_k7CDNA4 AMD - MI350X & MI355X, gfx950.
ustore_gpu_class_amd_cdna5_k8CDNA5 AMD - MI400, gfx1250.

Spellings

char const * ustore_gpu_class_name(ustore_gpu_class_t gpu_class);
ustore_gpu_class_t ustore_gpu_class_from_name(ustore_bytes_view_t name);
ustore_gpu_device_tOne GPU as UStore numbers it: its position in the listing of ustore_gpu_devices is the ordinal ustore_executor_for_vram_t::device names it by.
types.h:872 ↗
struct ustore_gpu_device_t

Fields

vendorustore_gpu_vendor_tThe vendor whose runtime drives the device.
vendor_ordinaluint32_tThe device's ordinal in its vendor's runtime, as nvidia-smi or rocm-smi list it.
gpu_classustore_gpu_class_tThe class of the device.
compute_unitsuint32_tStreaming multiprocessors or compute units.
memory_bytesuint64_tDevice memory in bytes.
namecharThe vendor's own name for the device, null-terminated and truncated to fit.
ustore_arena_tMemory the library writes every output whose length scales with a call into, reused across calls.
types.h:1026 ↗
typedef struct ustore_arena_handle_t * ustore_arena_t;

Memory

Made by ustore_arena_init where its executor computes, and an executor whose device runs out of memory reports ustore_status_out_of_memory_k rather than falling back to the host. Each call given the arena rewinds it, which invalidates every output of the previous call, and refills it, growing only when the new outputs outsize everything it held, so a steady loop allocates once. Every output starts aligned to 64 bytes, as Arrow buffers do, so typed columns read in place. To keep one call's outputs alive past the next call, pass the next call another arena.

A NULL arena is refused with ustore_status_invalid_argument_k, and so is a call whose inputs point into the arena it is about to rewind. An arena serves one call at a time, so a second call naming it finishes ustore_status_contended_k, and a deferred one does so at submission. A call whose executor computes on host threads given an arena on a device, or on another device than the arena's, reports ustore_status_device_k. A blocking call borrows the arena until it returns, and a deferred one holds it until final. A call that fails, is cancelled, or has nothing to report leaves its outputs NULL.

ustore_residency_tWhere an arena's outputs live, as ustore_arena_describe reports it.
types.h:1065 ↗
enum ustore_residency_t

Values

ustore_residency_unknown_k0No residency, which only a zeroed description reads as.
ustore_residency_ram_k1Host pages, which the calling thread reads in place.
ustore_residency_vram_k2One device's memory, which only that device's code reads.

Functions

ustore_outcome_waitBlocks on an atomic wait until outcome is final, with acquire ordering.
types.h:375 ↗
ustore_status_t ustore_outcome_wait(ustore_outcome_t *outcome);

Never wait on a call's own outcome from inside its progress callback: the call finishes only after the callback returns.

Parameters

outcomeinAn outcome passed to a call, read until its status leaves ustore_status_deferred_k.

Returns

The final status.

ustore_outcome_statusReads the status of outcome without waiting, with the acquire ordering its final store pairs with, so a final status also publishes every output of the call.
types.h:389 ↗
ustore_status_t ustore_outcome_status(ustore_outcome_t *outcome);

The one way to poll: the status word is atomic only by this contract, so a caller reading the field directly needs an atomic load its language may not offer on foreign memory.

Parameters

outcomeinAn outcome passed to a call.

Returns

The final status once the call is final.

Statuses

ustore_executor_for_ramCreates an executor of host threads for the RAM, RocksDB and LevelDB engines.
types.h:651 ↗
void ustore_executor_for_ram(ustore_executor_for_ram_t *arguments);

The default thread count leaves an eighth of the cores to the driver thread, to blocking calls, which run on their callers' threads, and to the background flushes and compactions of RocksDB and LevelDB. The threads interleave across the compute domains local to the memory domains named, where ForkUnion spawns near them: every domain the process may allocate on, or one alone. Naming several but not all spreads them over the whole machine.

Parameters

executorustore_executor_t *Output for the new executor, which the caller frees with ustore_executor_free; NULL on failure.
outcomeustore_outcome_t *Output for the outcome, final on return.
ramustore_ram_options_tThe memory domains the executor serves; copied, not borrowed.
threadsustore_size_tThreads to spawn, interleaved across the compute domains local to ram, where zero takes 7/8 of the cores those domains carry, rounded down and at least one.
deferred_limitustore_size_tDeferred calls the executor holds at once, sizing its deferred table, where zero takes a default.
cpu_classustore_cpu_class_tThe CPU class the threads confine their waits and atomics to, where zero takes what the CPU offers.

Statuses

ustore_executor_on_poolCreates an executor for the RAM, RocksDB and LevelDB engines over a ForkUnion pool it borrows, so several libraries in one process share one set of threads.
types.h:689 ↗
void ustore_executor_on_pool(ustore_executor_on_pool_t *arguments);

A dispatch on a pool that runs another is refused, so a batch finding the pool running its lender's dispatch runs on the executor's own driver thread instead of forking over the lender.

Parameters

executorustore_executor_t *Output for the new executor, which the caller frees with ustore_executor_free; NULL on failure.
outcomeustore_outcome_t *Output for the outcome, final on return.
ramustore_ram_options_tThe memory domains the executor serves, keeping the scratch of each thread and its arenas there; copied, not borrowed.
poolvoid *The ForkUnion fu_pool_t to borrow; it must outlive the executor, and keeps its lender's mood.
deferred_limitustore_size_tDeferred calls the executor holds at once, sizing its deferred table, where zero takes a default.

Statuses

ustore_executor_for_nvmeCreates an executor for the NVMe engine, whose every thread submits and polls its own I/O ring.
types.h:788 ↗
void ustore_executor_for_nvme(ustore_executor_for_nvme_t *arguments);

A database ustore_database_open_nvme opens on it moves bytes as nvme names, which the open reads once, so the executor may be freed before the databases it opened. Where its merges run is the open's own compactions.

Parameters

executorustore_executor_t *Output for the new executor, which the caller frees with ustore_executor_free; NULL on failure.
outcomeustore_outcome_t *Output for the outcome, final on return.
nvmeustore_nvme_options_tHow the databases it opens move bytes; copied, not borrowed.
threadsustore_size_tThreads to spawn, each owning one submission ring, where zero derives them from the cores.
deferred_limitustore_size_tDeferred calls the executor holds at once, sizing its deferred table, where zero takes a default.
cpu_classustore_cpu_class_tThe CPU class the threads confine their waits and atomics to, where zero takes what the CPU offers.

Statuses

ustore_gpu_devicesLists every GPU this process can drive, NVIDIA devices first, then AMD, so the position of a device is the ordinal ustore_executor_for_vram_t::device names it by.
types.h:920 ↗
void ustore_gpu_devices(ustore_gpu_devices_t *arguments);

Each vendor's runtime opens on the first call, and a vendor whose runtime is absent lists no devices, so the call succeeds with a zero count on a machine without GPUs. Devices of no class UStore knows are left out. The listing is taken once per process and never changes after.

Parameters

outcomeustore_outcome_t *Output for the outcome, final on return.
devicesustore_gpu_device_t *Output for up to devices_capacity devices, in UStore ordinal order; may be NULL.
devices_capacityustore_size_tEntries devices holds.
devices_countustore_size_t *The optional output for every device this process can drive, which may exceed devices_capacity.
ustore_executor_for_vramOpens one submission stream on device, or borrows the caller's, driven by one host thread.
types.h:970 ↗
void ustore_executor_for_vram(ustore_executor_for_vram_t *arguments);

Every call queues on that stream in order, so one host thread keeps the device fed, and a multi-GPU caller makes one executor per device. Its calls read through an engine's device verbs, which refuse changes and the control plane, and every call no device tower serves yet, with ustore_status_unsupported_k, and arenas it makes live on its device. ustore_database_open_nvme takes one of an NVIDIA device as its compactions, reading its device and scratch limit once. Every build spawns one, as the vendor's runtime opens at the first call and the library links none.

Parameters

executorustore_executor_t *Output for the new executor, which the caller frees with ustore_executor_free; NULL on failure.
outcomeustore_outcome_t *Output for the outcome, final on return.
deviceint32_tThe UStore ordinal of the device every call and arena of this executor uses, its position in the listing of ustore_gpu_devices; an ordinal no device carries is refused with ustore_status_device_k.
streamvoid *The optional borrowed stream on device, a cudaStream_t on NVIDIA and a hipStream_t on AMD, which must outlive the executor; NULL creates one.
scratch_limitustore_size_tThe optional ceiling on the device scratch a call or a merge takes; zero lets scratch grow as calls ask.
gdsustore_gds_tHow reads and compactions move bytes between the drives and the device.
deferred_limitustore_size_tDeferred calls the executor holds at once, sizing its deferred table, where zero takes a default.

Statuses

ustore_executor_drainWaits for every call submitted before the drain began, so later submissions never delay it.
types.h:994 ↗
void ustore_executor_drain(ustore_executor_drain_t *arguments);

Parameters

executorustore_executor_tThe executor to drain.
outcomeustore_outcome_t *Output for the outcome: an executor-level failure, such as a lost device, never one of a drained call.
moodustore_mood_tWhat the threads do whenever the executor next goes idle, where the last drain wins.

Statuses

ustore_executor_freeDrains every call, then frees the executor's threads, rings and scratch, and hands a borrowed pool back to its lender; NULL is safe, and it cannot fail, so takes no outcome.
types.h:1004 ↗
void ustore_executor_free(ustore_executor_t executor);

Waits until every call running on the executor, blocking or deferred, is final. Arenas it made stay valid, each keeping its memory until freed.

Parameters

executorinoutThe executor to be freed; invalid afterwards.
ustore_arena_initCreates an empty arena where executor computes, so its placement is decided once and device code keeps its outputs on the device without asking.
types.h:1054 ↗
void ustore_arena_init(ustore_arena_init_t *arguments);

Nothing is allocated until a call writes into it. An arena may outlive the executor that made it: it keeps its own memory, and the device that memory lives on, until it is freed.

Parameters

arenaustore_arena_t *Output for the new arena, which the caller frees with ustore_arena_free; NULL on failure.
executorustore_executor_tThe executor whose calls the arena serves, which decides where it lives, as a RAM executor places it on the first of its memory domains; NULL serves calls on the calling thread from host pages.
outcomeustore_outcome_t *Output for the outcome, final on return.

Statuses

ustore_arena_freeFrees the arena and every output in it; NULL is safe, and it cannot fail, so takes no outcome.
types.h:1062 ↗
void ustore_arena_free(ustore_arena_t arena);

Waits until every deferred call naming the arena is final.

Parameters

arenainoutThe arena to be freed; invalid afterwards.
ustore_arena_describeReports where an arena's outputs live, as the executor that made it decided, so a caller reads them as host memory or hands them to device code without remembering that executor.
types.h:1109 ↗
void ustore_arena_describe(ustore_arena_describe_t *arguments);

Parameters

arenaustore_arena_tThe arena to describe.
outcomeustore_outcome_t *Output for the outcome, final on return.
descriptionustore_arena_description_t *Output for where the arena's outputs live.

Statuses