# Quickstart

> ForkUnion for C

__`ForkUnion`__ is dead-simple to use!
There is no nested parallelism, exception handling, or "future promises"; they are banned.
A pool runs one dispatch at a time and refuses any other with the `contended` status, including one from inside its own callbacks.
The thread pool itself has a few core operations:

- `spawn` to initialize worker threads, and
- `for_threads` to launch a blocking callback on all threads.

Higher-level APIs for index-addressable tasks are also available:

- `for_n` - for individual evenly-sized tasks,
- `for_n_dynamic` - for individual unevenly-sized tasks,
- `for_slices` - for contiguous runs of evenly-sized tasks, one per worker.

For additional flow control and tuning, following helpers are available:

- `nap` - to park workers on the CPU's address monitor, waking with no syscall,
- `sleep(microseconds)` - to block workers in the kernel for long idle periods, with 0 waiting for the next dispatch,
- `terminate` - to kill the threads before the destructor is called,
- `unsafe_for_threads` - to broadcast a callback without blocking, returning a generation token,
- `unsafe_join` - to block until the completion of a broadcasted generation,
- `is_complete` - to poll a generation token for completion without blocking.

Every dispatch is identified by a `generation` token.
On `caller_exclusive_k` pools you can dispatch, overlap your own work, poll `is_complete`, and join - the classic poll-then-join pattern.
On `caller_inclusive_k` pools the calling thread owes one slice of the work, which only runs inside `unsafe_join`, so completion can't be reached by polling alone.
The same rule shapes the RAII guard returned by `for_threads` and, in C++, the `for_n` family: the workers start at the guard's construction on every pool, and on inclusive pools the caller's own slice runs at `join` or destruction.

On `caller_exclusive_k` pools thread 0 may also fork from inside its own slice, at any depth: such a fork waits for every other slice of the one enclosing it, runs on all threads, and returns complete.
So `unsafe_for_threads` can hand a whole multi-step call to thread 0, which forks the rest of the pool as it goes, while the caller only polls a status word the call stores at its end.
That is how one manager thread keeps several exclusive pools busy with no queue and no extra thread.
A join on such a pool waits for whatever dispatch runs, since the call may end before its submitter stores the token.

In C++ it provides [NUMA extensions](https://www.unum.cloud/docs/forkunion/c/numa.md).
That includes the `colocated_pool` analog of the `flat_pool` and the `domain_allocator_t` for allocating memory in a specific memory domain.
Those are out-of-the-box compatible with the higher-level APIs.
Most interestingly, for Big Data applications, a higher-level `distributed_pool` class addresses and balances the work across all compute domains.

> `domain_allocator_t` is backed by `linux_numa_allocator_t`, `freebsd_numa_allocator_t`, or `windows_numa_allocator_t`.

ForkUnion provides a pure C99 API via `forkunion/forkunion.h`, wrapping the C++ implementation in pre-compiled libraries: `libforkunion_static.a` or `libforkunion_shared.so`.
Installing also drops a `forkunion.pc`, so `pkg-config --cflags --libs forkunion` works outside CMake.
The C API uses opaque `fu_pool_t` handles and function pointers for callbacks, making it compatible with any C99+ compiler.

To integrate using CMake:

```cmake
FetchContent_Declare(
    forkunion
    GIT_REPOSITORY https://github.com/ashvardanian/ForkUnion
    GIT_TAG v4.0.0-rc.1
)
FetchContent_MakeAvailable(forkunion)
target_link_libraries(your_target PRIVATE forkunion::static)
```

A minimal C example:

```c
#include <stdio.h>                // printf
#include <forkunion/forkunion.h>  // fu_pool_t, fu_pool_new, fu_pool_spawn

void hello_callback(void *context, size_t thread, size_t compute_domain) {
    (void)context;
    printf("Hello from thread %zu (compute_domain %zu)\n", thread, compute_domain);
}

int main(void) {
    fu_topology_t topology;
    fu_pool_t pool;
    size_t cores;
    if (fu_topology_new(&topology) != fu_success_k) return 1;
    if (fu_pool_new("my_pool", fu_capabilities_all_k, &pool) != fu_success_k) return 1;
    if (fu_logical_cores_count(topology, &cores) != fu_success_k) return 1;
    if (fu_pool_spawn(topology, pool, cores, fu_caller_inclusive_k) != fu_success_k) return 1;

    if (fu_pool_for_threads(pool, hello_callback, NULL) != fu_success_k) return 1;
    fu_pool_delete(pool);
    fu_topology_delete(topology);
    return 0;
}
```

The `fu_`-prefixed functions report the [hardware topology](https://www.unum.cloud/docs/forkunion/c/topology.md), to size and place work:

```c
fu_topology_t topology;
size_t domains, cores, level, memory;
if (fu_topology_new(&topology) != fu_success_k) return 1;
if (fu_compute_domains_count(topology, &domains) != fu_success_k) return 1;
for (size_t domain = 0; domain < domains; ++domain) {
    fu_logical_cores_count_in(topology, domain, &cores);
    fu_compute_level_in(topology, domain, &level);
    fu_local_memory_of(topology, domain, &memory);
    printf("domain %zu: %zu cores, level %zu, allocate on memory domain %zu\n", domain, cores, level, memory);
}
fu_topology_delete(topology);
```

For parallel tasks with context:

```c
struct task_context {
    int *data;
    size_t size;
};

void process_task(void *ctx, size_t task, size_t thread, size_t compute_domain) {
    (void)thread; (void)compute_domain;
    struct task_context *context = (struct task_context *)ctx;
    context->data[task] = task * 2;
}

int main(void) {
    fu_topology_t topology;
    fu_pool_t pool;
    if (fu_topology_new(&topology) != fu_success_k) return 1;
    if (fu_pool_new("tasks", fu_capabilities_all_k, &pool) != fu_success_k) return 1;
    if (fu_pool_spawn(topology, pool, 4, fu_caller_inclusive_k) != fu_success_k) return 1;

    int data[100] = {0};
    struct task_context ctx = { .data = data, .size = 100 };
    fu_pool_for_n(pool, 100, process_task, &ctx);        // static scheduling
    fu_pool_for_n_dynamic(pool, 100, process_task, &ctx); // dynamic scheduling

    fu_pool_delete(pool);
    fu_topology_delete(topology);
    return 0;
}
```

### GCC Nested Functions Extension

GCC supports [nested functions](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html) that can capture variables from the enclosing scope:

```c
#include <stdio.h>
#include <stdatomic.h>
#include <forkunion/forkunion.h>

int main(void) {
    fu_topology_t topology;
    fu_pool_t pool;
    if (fu_topology_new(&topology) != fu_success_k) return 1;
    if (fu_pool_new("gcc_nested", fu_capabilities_all_k, &pool) != fu_success_k) return 1;
    if (fu_pool_spawn(topology, pool, 4, fu_caller_inclusive_k) != fu_success_k) return 1;

    atomic_size_t counter = 0;

    // GCC nested function - captures 'counter' from enclosing scope
    void nested_callback(void *ctx, size_t task, size_t thread, size_t compute_domain) {
        (void)ctx; (void)thread; (void)compute_domain;
        atomic_fetch_add(&counter, 1);
    }

    fu_pool_for_n(pool, 100, nested_callback, NULL);
    printf("Completed %zu tasks\n", (size_t)atomic_load(&counter));

    fu_pool_delete(pool);
    fu_topology_delete(topology);
    return 0;
}
```

Compile: `gcc -std=c11 test.c -lforkunion_static -lstdc++ -lpthread`
The archive is C++ behind a C facade, so a C consumer names the C++ runtime itself — `-lc++` under Apple's toolchain.

### Clang Blocks Extension

Clang provides [blocks](https://clang.llvm.org/docs/BlockLanguageSpec.html) with `^{}` syntax:

```c
#include <stdio.h>
#include <stdatomic.h>
#include <Block.h>
#include <forkunion/forkunion.h>

typedef void (^task_block_t)(void *, size_t, size_t, size_t);

struct block_wrapper { task_block_t block; };

void block_wrapper_fn(void *ctx, size_t task, size_t thread, size_t compute_domain) {
    ((struct block_wrapper *)ctx)->block(NULL, task, thread, compute_domain);
}

int main(void) {
    fu_topology_t topology;
    fu_pool_t pool;
    if (fu_topology_new(&topology) != fu_success_k) return 1;
    if (fu_pool_new("clang_blocks", fu_capabilities_all_k, &pool) != fu_success_k) return 1;
    if (fu_pool_spawn(topology, pool, 4, fu_caller_inclusive_k) != fu_success_k) return 1;

    __block atomic_size_t counter = 0;

    task_block_t my_block = ^(void *c, size_t task, size_t t, size_t col) {
        (void)c; (void)t; (void)col;
        atomic_fetch_add(&counter, 1);
    };

    task_block_t heap_block = Block_copy(my_block);
    struct block_wrapper wrapper = { .block = heap_block };

    fu_pool_for_n(pool, 100, block_wrapper_fn, &wrapper);

    Block_release(heap_block);
    printf("Completed %zu tasks\n", (size_t)atomic_load(&counter));

    fu_pool_delete(pool);
    fu_topology_delete(topology);
    return 0;
}
```

Compile: `clang -std=c11 -fblocks test.c -lforkunion_static -lstdc++ -lpthread -lBlocksRuntime`, where Apple's toolchain needs `-lc++` and no `-lBlocksRuntime`.

`#include <forkunion/forkunion.h>`

## fu_bool_t

Boolean type: 0 for false, non-zero for true.

```c
typedef int fu_bool_t;
```

## fu_status_t

Why a call failed, in a vocabulary the core and every binding share.

```c
enum fu_status_t
```

Success is `0`; every failure is negative, so `status < 0` and `status != [fu_success_k](#fu_success_k)` agree. Values are fixed and portable, so a binding may hard-code them.

Some enumerators name failures the core never returns itself: a binding that rejects a call before it crosses reports the same status the core would if it could see the same thing.

Statuses fall into four categories, with no interruptions to report. Resources: [`fu_bad_alloc_k`](#fu_bad_alloc_k), [`fu_thread_refused_k`](#fu_thread_refused_k), [`fu_topology_unavailable_k`](#fu_topology_unavailable_k), [`fu_permission_denied_k`](#fu_permission_denied_k), [`fu_library_missing_k`](#fu_library_missing_k) and [`fu_symbol_missing_k`](#fu_symbol_missing_k). Input: [`fu_invalid_argument_k`](#fu_invalid_argument_k). State: [`fu_capacity_exhausted_k`](#fu_capacity_exhausted_k), [`fu_config_mismatch_k`](#fu_config_mismatch_k), [`fu_already_spawned_k`](#fu_already_spawned_k), [`fu_not_spawned_k`](#fu_not_spawned_k), [`fu_unsupported_k`](#fu_unsupported_k) and [`fu_contended_k`](#fu_contended_k). Defects: [`fu_unknown_k`](#fu_unknown_k). Any verb that allocates, binds a device or touches a file may also return a Resources status. A verb's own block lists only what its contract adds.

- `fu_success_k` = `0`: The call completed and any output pointer holds a meaningful value.
- `fu_unknown_k` = `-1`: No reason was reported, or one this build does not name.
- `fu_bad_alloc_k` = `-2`: An allocation or mapping failed; a smaller request may succeed.
- `fu_capacity_exhausted_k` = `-3`: A fixed ceiling was reached, so a smaller request will not help either.
- `fu_invalid_argument_k` = `-4`: An argument was malformed, out of range, or would overflow a byte count.
- `fu_config_mismatch_k` = `-5`: The handles or the pool kind cannot serve this call together.
- `fu_already_spawned_k` = `-6`: The pool is already spawned; terminate it first.
- `fu_not_spawned_k` = `-7`: The pool holds no workers: it was never spawned, or was terminated since.
- `fu_thread_refused_k` = `-8`: The OS declined to create a thread - a resource limit, or permissions.
- `fu_topology_unavailable_k` = `-9`: The machine could not be described; transient if its CPU set changed mid-probe.
- `fu_permission_denied_k` = `-10`: A privileged operation was declined.
- `fu_unsupported_k` = `-11`: This build or this machine has no such facility.
- `fu_library_missing_k` = `-12`: Binding-only: nothing to load, or a different major version.
- `fu_symbol_missing_k` = `-13`: Binding-only: the loaded core is missing a symbol.
- `fu_contended_k` = `-14`: The pool is running another dispatch: another caller's, or the one this call runs inside.

## fu_pool_t

Opaque, cross-platform thread-pool handle, either flat, colocated, or distributed.

```c
typedef void * fu_pool_t;
```

## fu_lambda_context_t

Type-punned pointer to a user-defined callback context.

```c
typedef void * fu_lambda_context_t;
```

## fu_for_threads_t

Callback type for thread-level operations.

```c
typedef void(*) fu_for_threads_t(fu_lambda_context_t context, size_t thread, size_t compute_domain);
```

## fu_for_task_t

Callback type for task-level operations receiving individual indices.

```c
typedef void(*) fu_for_task_t(fu_lambda_context_t context, size_t task, size_t thread, size_t compute_domain);
```

## fu_for_range_t

Callback type for slice-level operations receiving ranges of tasks.

```c
typedef void(*) fu_for_range_t(fu_lambda_context_t context, size_t first, size_t count, size_t thread, size_t compute_domain);
```

## fu_caller_exclusivity_t

Defines the in- and exclusivity of the calling thread for the executing task.

```c
enum fu_caller_exclusivity_t
```

This enum affects how the join is performed. If the caller is inclusive, 1/Nth of the work runs on the calling thread, and the join happens in its own scope.

- `fu_caller_inclusive_k`: The calling thread participates in the workload.
- `fu_caller_exclusive_k`: The calling thread only coordinates, doesn't execute tasks.

See also: [fu_caller_inclusive_k](#fu_caller_inclusive_k) and [fu_caller_exclusive_k](#fu_caller_exclusive_k)

## fu_capabilities_t

Describes all the special library features, both those compiled in and those found here.

```c
enum fu_capabilities_t
```

One bit per facility, and two accessors ask two questions of the same bit. [`fu_comptime_capabilities`](#fu_comptime_capabilities) reports whether the code was __built:__ a set [`fu_capability_place_huge_pages_on_domain_k`](#fu_capability_place_huge_pages_on_domain_k) means we compiled the path that asks for them. [`fu_runtime_capabilities`](#fu_runtime_capabilities) reports whether the machine __offers__ it now.

Neither implies the other. A binary that built [`fu_capability_place_memory_on_domain_k`](#fu_capability_place_memory_on_domain_k) runs perfectly well on a single-node box, where the runtime accessor never sets that bit; and a machine with four NUMA nodes reports none of them to a build that left the topology out.

- `fu_capabilities_unknown_k` = `0`: No bit set: nothing requested, and nothing yet found.
- `fu_capability_x86_pause_k` = `1 << 0`: The `PAUSE` spin hint, on every x86 since the Pentium 4: a short pipeline stall that keeps a busy-wait from flooding the load ports and eases the sibling hardware thread.
- `fu_capability_x86_tpause_k` = `1 << 1`: `TPAUSE` sleeps the core until a deadline rather than spinning - the `WAITPKG` feature, the CPUID leaf 7, subleaf 0, ECX bit 5, on Sapphire Rapids, Alder Lake and their successors.
- `fu_capability_arm64_yield_k` = `1 << 2`: The `YIELD` hint, on every AArch64: releases the pipeline to a sibling hardware thread and costs nothing where there is none.
- `fu_capability_arm64_wfet_k` = `1 << 3`: `WFET` sleeps the core until a deadline or an event on the monitored line - `FEAT_WFxT`, Armv8.7, read from `ID_AA64ISAR2_EL1` on Linux and `hw.optional.arm.FEAT_WFxT` on Apple.
- `fu_capability_riscv64_pause_k` = `1 << 4`: The `PAUSE` spin hint of the `Zihintpause` extension - encoded as a `FENCE` every hart accepts, so it is reported on every RISC-V.
- `fu_capability_riscv64_wrs_k` = `1 << 5`: `WRS.STO` sleeps the hart until a reservation breaks or a short timeout - the `Zawrs` extension, attested by the kernel's `hwprobe`.
- `fu_capability_os_threads_k` = `1 << 6`: Own the raw OS thread handle instead of a `std::thread` - the substrate every thread lever below stands on.
- `fu_capability_topology_k` = `1 << 7`: Enumerate this machine's cores, compute domains, and memory domains - the root every placement needs.
- `fu_capability_place_threads_by_affinity_k` = `1 << 8`: Bind a thread to a set of cores, choosing where it runs.
- `fu_capability_place_threads_by_core_class_k` = `1 << 9`: Steer a thread onto a class of core at creation, choosing where it runs.
- `fu_capability_reschedule_threads_by_class_k` = `1 << 10`: Reclass a thread's scheduler to sleep or wake it, choosing when it runs.
- `fu_capability_place_memory_on_domain_k` = `1 << 11`: Place a buffer's pages on a chosen memory domain.
- `fu_capability_place_huge_pages_on_domain_k` = `1 << 12`: Place larger-than-base pages on a chosen memory domain, a narrower case of memory placement.
- `fu_capability_huge_transparent_pages_k` = `1 << 13`: The kernel promotes base pages to huge pages on its own: passive, runtime-only.
- `fu_capability_colocate_pools_on_domain_k` = `1 << 14`: The domain-aware `colocated_pool` and `distributed_pool` are compiled.
- `fu_capability_x86_cldemote_k` = `1 << 15`: `CLDEMOTE` moves a just-written line from this core's private caches toward the shared LLC and retains it there.
- `fu_capability_arm64_dc_cvac_k` = `1 << 16`: `DC CVAC` cleans a dirty line to the coherency point, AArch64's nearest demote.
- `fu_capability_riscv64_zicbom_k` = `1 << 17`: The kernel enabled user-mode Zicbom cache-block management, attested through `hwprobe` - the C ABI's cascade selects `cbo.clean` on this bit.
- `fu_capability_x86_cmpccxadd_k` = `1 << 18`: `CMPCCXADD`, the conditional atomic add - CPUID leaf 7, subleaf 1, EAX bit 7, on Sierra Forest, Diamond Rapids and their successors: a bounded claim as one instruction instead of a compare-exchange loop.
- `fu_capability_x86_raoint_k` = `1 << 19`: RAO-INT `aadd`, `aand`, `aor` and `axor` - CPUID leaf 7, subleaf 1, EAX bit 3, on Grand Ridge:
- `fu_capability_arm64_lse_k` = `1 << 20`: Armv8.1 `FEAT_LSE:` `swp`, `cas`, `ldadd` and the no-return `st*` forms, one instruction per read-modify-write instead of a load-exclusive loop.
- `fu_capability_arm64_rcpc_k` = `1 << 21`: Armv8.3 `FEAT_LRCPC:` `ldapr`, the RCpc acquiring load that needn't wait for the core's earlier release stores.
- `fu_capability_riscv64_zacas_k` = `1 << 22`: `Zacas:` `amocas`, compare-and-swap as one instruction instead of an `lr` and `sc` loop, attested by the kernel's `hwprobe`.
- `fu_capability_riscv64_atomic_k` = `1 << 23`: The A extension: `lr`, `sc` and the `amo*` read-modify-writes, RISC-V's baseline every Linux ABI guarantees.
- `fu_capability_arm64_prfm_k` = `1 << 24`: The AArch64 `PRFM` prefetch hints, base ISA that can never fault, so they are reported on every AArch64 core.
- `fu_capability_riscv64_zicbop_k` = `1 << 25`: The `Zicbop` prefetches, `prefetch.r` and `prefetch.w`, encoded as `ORI` hints every hart accepts, so they are reported on every RISC-V core.
- `fu_capability_riscv64_zabha_k` = `1 << 26`: `Zabha:` the byte and halfword `amo` forms, and with `Zacas` the byte and halfword `amocas`, attested by the kernel's `hwprobe`.
- `fu_capability_any_yield_k` = `fu_capability_x86_pause_k | fu_capability_x86_tpause_k | fu_capability_riscv64_pause_k |
                                fu_capability_arm64_yield_k | fu_capability_arm64_wfet_k | fu_capability_riscv64_wrs_k`: Composite mask of every busy-wait waiter bit above, to enumerate the ones a machine offers in one intersection with [`fu_runtime_capabilities`](#fu_runtime_capabilities).
- `fu_capability_any_instruction_k` = `(fu_capability_any_yield_k | fu_capability_x86_cldemote_k | fu_capability_arm64_dc_cvac_k |
         fu_capability_riscv64_zicbom_k | fu_capability_x86_cmpccxadd_k | fu_capability_x86_raoint_k |
         fu_capability_arm64_lse_k | fu_capability_arm64_rcpc_k | fu_capability_riscv64_zacas_k |
         fu_capability_riscv64_atomic_k | fu_capability_arm64_prfm_k | fu_capability_riscv64_zicbop_k |
         fu_capability_riscv64_zabha_k)`: Composite mask of every instruction-level bit: the waiters, cache hints and atomics a CPU class may narrow, leaving the OS, topology, placement and NUMA bits alone.
- `fu_capabilities_all_k` = `~0`: All-ones allow-mask: pass to [`fu_pool_new`](#fu_pool_new) to disable capability filtering.

See also: [fu_comptime_capabilities](#fu_comptime_capabilities) and [fu_runtime_capabilities](#fu_runtime_capabilities)

## fu_generation_t

Token identifying one dispatch on one pool; an __odd__ number, or zero once refused.

```c
typedef size_t fu_generation_t;
```

Every generation advances the pool's internal epoch by exactly two: once at dispatch and once when the last contributor finishes. Odd epochs are in-flight, even are idle.

## fu_version_*

Returns the major version component of the ForkUnion library.

```c
int fu_version_major(void);
```

- `fu_version_major`
- `fu_version_minor`
- `fu_version_patch`

## fu_status_to_string

Static, English description of `status`.

```c
char const * fu_status_to_string(fu_status_t status);
```

Never returns NULL, never allocates.

- `status` (in): Any value; one this build does not name reads as the unknown text.

## fu_comptime_capabilities

Which kernel facilities this build of ForkUnion was compiled to use.

```c
fu_capabilities_t fu_comptime_capabilities(void);
```

See also: [fu_runtime_capabilities](#fu_runtime_capabilities)

## fu_runtime_capabilities

Which features this machine turned out to offer, probing the CPU and the memory system.

```c
fu_capabilities_t fu_runtime_capabilities(void);
```

See also: [fu_comptime_capabilities](#fu_comptime_capabilities)

## fu_name_capabilities

Writes `capabilities` as a comma-separated name list such as "arm64_yield,arm64_wfet".

```c
fu_status_t fu_name_capabilities(
    fu_capabilities_t capabilities,
    char *name_buffer,
    size_t name_buffer_length,
    size_t *written_out);
```

- `capabilities` (in): The features to name.
- `name_buffer` (out): Destination, always null-terminated; the list is truncated to fit.
- `name_buffer_length` (in): Size of `name_buffer` in bytes.
- `written_out` (out): Receives the bytes written, excluding the null terminator.

Statuses: - [`fu_invalid_argument_k`](#fu_invalid_argument_k) A NULL `written_out`, a NULL `name_buffer` or an empty one.

## fu_pool_new

Creates an empty pool constrained to an allow-mask of capabilities.

```c
fu_status_t fu_pool_new(
    char const *name,
    fu_capabilities_t allowed,
    fu_pool_t *pool_out);
```

The waiter, and whether a spawn spans domains, come from `fu_runtime_capabilities() & allowed`, so clearing bits forces a lesser waiter or the flat non-NUMA pool - handy for benchmarking. Creation takes no topology: __placement__ is decided at spawn, where you hand over the `[fu_topology_t](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_topology_t)`. The pool starts empty; call [`fu_pool_spawn`](#fu_pool_spawn) before use.

- `name` (in): Optional pool name, may be NULL. Truncated to what the platform's thread naming accepts rather than refused - the name exists to tell one pool's threads from another's in a debugger, and a shortened name still does that.
- `allowed` (in): Allow-mask of `[fu_capabilities_t](#fu_capabilities_t)`; [`fu_capabilities_all_k`](#fu_capabilities_all_k) skips filtering.
- `pool_out` (out): Receives the opaque pool handle.

Notes: Thread-safe.

See also: [fu_pool_spawn](#fu_pool_spawn), [fu_pool_capabilities](#fu_pool_capabilities), [fu_pool_delete](#fu_pool_delete).

Statuses: - [`fu_invalid_argument_k`](#fu_invalid_argument_k) A NULL `pool_out`.

## fu_pool_delete

Destroys a pool and frees its resources; the handle is invalid afterward.

```c
void fu_pool_delete(fu_pool_t pool);
```

- `pool` (in): Pool handle, may be NULL - a no-op. Pending tasks must have completed.

Notes: Must not run concurrently with other operations on `pool`.

See also: [fu_pool_terminate](#fu_pool_terminate) to stop workers but keep the handle.

## fu_pool_capabilities

The capabilities an initialized pool actually uses - a subset of comptime & runtime.

```c
fu_status_t fu_pool_capabilities(
    fu_pool_t pool,
    fu_capabilities_t *capabilities_out);
```

- `pool` (in): Pool handle, must not be NULL.
- `capabilities_out` (out): Receives the capabilities the pool runs with.

## fu_pool_spawn

Spawns `threads` workers across the __whole__ machine, readying the pool for dispatch.

```c
fu_status_t fu_pool_spawn(
    fu_topology_t topology,
    fu_pool_t pool,
    size_t threads,
    fu_caller_exclusivity_t exclusivity);
```

Covers every compute domain. If a prior [fu_pool_spawn_on](#fu_pool_spawn_on) left the pool pinned to a single domain, this rebuilds it as a whole-machine pool before spawning. The call returns once every worker has started.

- `topology` (in): Machine topology from [`fu_topology_new`](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_topology_new); read only during this call, so it may be freed as soon as this returns.
- `pool` (in): Pool handle, must not be NULL.
- `threads` (in): Worker count, must be > 0; an inclusive pool counts the caller as one.
- `exclusivity` (in): Whether the calling thread also executes tasks.

Notes: Not thread-safe; call once per pool, or again after [`fu_pool_terminate`](#fu_pool_terminate).

See also: [fu_pool_spawn_on](#fu_pool_spawn_on) to pin to one compute domain, `[fu_logical_cores_count](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_logical_cores_count)` for the count.

Statuses: - [`fu_invalid_argument_k`](#fu_invalid_argument_k) A NULL handle, an unnamed exclusivity, or `threads` of 0.
- [`fu_already_spawned_k`](#fu_already_spawned_k) While the pool still holds workers.
- [`fu_unsupported_k`](#fu_unsupported_k) A caller-exclusive or multi-thread pool when OS threads are unavailable in this build.

## fu_pool_spawn_on

Spawns `threads` workers pinned to a single compute domain.

```c
fu_status_t fu_pool_spawn_on(
    fu_topology_t topology,
    fu_pool_t pool,
    size_t compute_domain_index,
    size_t threads,
    fu_caller_exclusivity_t exclusivity);
```

Placement lives here, not in creation: [fu_pool_new](#fu_pool_new) allocates the handle, and this binds it to a single compute domain, so its workers and their memory-domain-local allocations stay on that domain. Spawn one pool per compute domain and coordinate them with the generation-token API; [fu_pool_spawn](#fu_pool_spawn) instead spans __all__ compute domains. Without NUMA, only compute domain 0 is valid. It returns once every worker started pinned to `compute_domain_index`.

- `topology` (in): Machine topology from [`fu_topology_new`](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_topology_new); read only during this call.
- `pool` (in): Pool handle, must not be NULL.
- `compute_domain_index` (in): Target compute domain, in [0, `fu_compute_domains_count()`).
- `threads` (in): Worker count, must be > 0.
- `exclusivity` (in): Whether the calling thread also executes tasks.

Notes: Not thread-safe; call once per pool.

See also: [fu_pool_spawn](#fu_pool_spawn), [fu_compute_domains_count](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_compute_domains_count), [fu_logical_cores_count_in](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_logical_cores_count_in).

Statuses: - [`fu_invalid_argument_k`](#fu_invalid_argument_k) A NULL handle, an unnamed exclusivity, `threads` of 0, or a `compute_domain_index` at or past [`fu_compute_domains_count`](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_compute_domains_count).
- [`fu_already_spawned_k`](#fu_already_spawned_k) While the pool still holds workers.
- [`fu_unsupported_k`](#fu_unsupported_k) A caller-exclusive or multi-thread pool in a build without OS threads.

## fu_pool_caller_exclusivity

Whether the calling thread executes a slice of each dispatch.

```c
fu_status_t fu_pool_caller_exclusivity(
    fu_pool_t pool,
    fu_caller_exclusivity_t *exclusivity_out);
```

On inclusive pools the caller's slice runs only inside [`fu_pool_unsafe_join`](#fu_pool_unsafe_join), so [`fu_pool_is_complete`](#fu_pool_is_complete) cannot be reached by polling alone.

- `pool` (in): Pool handle, must not be NULL and initialized.
- `exclusivity_out` (out): Receives whether the caller runs a slice.

Notes: Not synchronized. Reflects the most recent [`fu_pool_spawn`](#fu_pool_spawn), so it survives a re-spawn.

See also: [fu_pool_spawn](#fu_pool_spawn), [fu_pool_is_complete](#fu_pool_is_complete).

Statuses: - [`fu_invalid_argument_k`](#fu_invalid_argument_k) A NULL handle or a NULL `exclusivity_out`.
- [`fu_not_spawned_k`](#fu_not_spawned_k) A pool with no workers.

## fu_pool_threads_count_in

The worker count in one compute domain of the pool.

```c
fu_status_t fu_pool_threads_count_in(
    fu_pool_t pool,
    size_t compute_domain_index,
    size_t *threads_out);
```

- `pool` (in): Pool handle, must not be NULL.
- `compute_domain_index` (in): In [0, `fu_pool_compute_domains_count(pool)`), or the call returns [`fu_invalid_argument_k`](#fu_invalid_argument_k).
- `threads_out` (out): Receives the number of threads in that domain.

Notes: Not synchronized.

See also: [fu_pool_compute_domains_count](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_pool_compute_domains_count).

Statuses: - [`fu_invalid_argument_k`](#fu_invalid_argument_k) A NULL handle or a NULL `threads_out`.
- [`fu_not_spawned_k`](#fu_not_spawned_k) A pool with no workers.

## fu_pool_threads_count

The total worker count, including the caller on an inclusive pool.

```c
fu_status_t fu_pool_threads_count(fu_pool_t pool, size_t *threads_out);
```

The count is the one [`fu_pool_spawn`](#fu_pool_spawn) received.

- `pool` (in): Pool handle, must not be NULL.
- `threads_out` (out): Receives the number of threads.

Notes: Not synchronized.

Statuses: - [`fu_invalid_argument_k`](#fu_invalid_argument_k) A NULL handle or a NULL `threads_out`.
- [`fu_not_spawned_k`](#fu_not_spawned_k) A pool with no workers.

## fu_pool_locate_thread_in

Converts a global thread index to a local thread index within a compute_domain.

```c
fu_status_t fu_pool_locate_thread_in(
    fu_pool_t pool,
    size_t global_thread_index,
    size_t compute_domain_index,
    size_t *local_index_out);
```

- `pool` (in): Thread pool handle, must not be NULL.
- `global_thread_index` (in): The global thread index to convert.
- `compute_domain_index` (in): Index of the compute_domain, must be < `fu_pool_compute_domains_count(pool)`, else [`fu_invalid_argument_k`](#fu_invalid_argument_k).
- `local_index_out` (out): Receives the local thread index within the specified compute_domain.

Statuses: - [`fu_invalid_argument_k`](#fu_invalid_argument_k) A NULL handle, a NULL `local_index_out` or a compute domain out of range.
- [`fu_not_spawned_k`](#fu_not_spawned_k) A pool with no workers.

## fu_pool_nap

Parks idle workers on the CPU's address monitor, for short idle periods.

```c
void fu_pool_nap(fu_pool_t pool);
```

Workers stay on their cores and wake with no syscall of their own. Without a monitor they keep spinning on a hint. With enough privileges on Linux and FreeBSD they also drop to the idle scheduling class, and on Linux under `schedutil` to a lower frequency.

- `pool` (in): Pool handle, must not be NULL.

Notes: Not thread-safe; call between task batches. The next dispatch wakes the workers.

## fu_pool_sleep

Blocks idle workers in the kernel, for long idle periods.

```c
void fu_pool_sleep(fu_pool_t pool, size_t micros);
```

Workers leave their cores, at the price of syscalls in the next dispatch. Same as [fu_pool_nap](#fu_pool_nap) where the kernel can't wait on a word, which is Apple's case.

- `pool` (in): Pool handle, must not be NULL.
- `micros` (in): Longest a worker stays blocked before re-checking, or 0 to leave the wake to the next dispatch alone.

Notes: Not thread-safe; call between task batches. The next dispatch wakes the workers.

## fu_pool_terminate

Stops the workers and clears the pool, keeping the handle for a re-spawn.

```c
void fu_pool_terminate(fu_pool_t pool);
```

Joins every worker and releases their resources; call [`fu_pool_spawn`](#fu_pool_spawn) again, e.g. to change the thread count. Use [`fu_pool_delete`](#fu_pool_delete) for permanent teardown.

- `pool` (in): Pool handle, must not be NULL.

Notes: Not thread-safe; call only when no dispatch is in flight, never as a sync point.

See also: [fu_pool_spawn](#fu_pool_spawn), [fu_pool_delete](#fu_pool_delete).

## fu_pool_for_threads

Runs `callback` once on every worker, blocking until all return - OpenMP's __parallel__.

```c
fu_status_t fu_pool_for_threads(
    fu_pool_t pool,
    fu_for_threads_t callback,
    fu_lambda_context_t context);
```

The callback receives `context`, its `thread` index in [0, threads_count), and the `compute_domain` - the NUMA node and QoS level - it runs on.

```
void hello(void *ctx, size_t thread, size_t compute_domain) {
    printf("Hello from thread %zu on compute_domain %zu\n", thread, compute_domain);
}
if (fu_pool_for_threads(pool, hello, NULL) != fu_success_k) return; // ! Never spawned, or busy
```

- `pool` (in): Pool handle, must not be NULL and initialized.
- `callback` (in): Runs once per worker, must not be NULL.
- `context` (in): Shared context passed to every call, may be NULL.

Notes: Returns only after every worker completes; no extra synchronization is needed.

See also: [fu_pool_unsafe_for_threads](#fu_pool_unsafe_for_threads) for the non-blocking form.

Statuses: - [`fu_not_spawned_k`](#fu_not_spawned_k) A pool with no workers.
- [`fu_contended_k`](#fu_contended_k) While the pool runs another dispatch, and then nothing ran.

## fu_pool_for_slices

Splits [0, `n`) into __contiguous__ slices, one per worker, and blocks until all finish.

```c
fu_status_t fu_pool_for_slices(
    fu_pool_t pool,
    size_t n,
    fu_for_range_t callback,
    fu_lambda_context_t context);
```

One contiguous range per worker - ideal for __vectorized__ or per-slice-setup work where sequential access matters, since each worker touches its data in order and only sets up once. Every worker is called exactly once, an idle one with `count` == 0. The callback receives `context`, the run's `first` index, `count` of tasks, `thread` index, and `compute_domain`.

```
void normalize(void *array, size_t first, size_t count, size_t thread, size_t compute_domain) {
    float *data = (float *)array;
    for (size_t i = first; i != first + count; ++i) data[i] = sqrtf(data[i]); // One sequential sweep
}
if (fu_pool_for_slices(pool, length, normalize, values) != fu_success_k) return; // ! Never spawned, or busy
```

- `pool` (in): Pool handle, must not be NULL and initialized.
- `n` (in): Total tasks, may be 0 - a no-op.
- `callback` (in): Runs once per slice, must not be NULL if `n` > 0.
- `context` (in): Shared context, may be NULL.

See also: [fu_pool_for_n](#fu_pool_for_n) for per-index dispatch.

Statuses: - [`fu_not_spawned_k`](#fu_not_spawned_k) A pool with no workers.
- [`fu_contended_k`](#fu_contended_k) While the pool runs another dispatch, and then nothing ran.

## fu_pool_for_n

Runs `callback` for each of `n` __similar-cost__ tasks, blocking until done.

```c
fu_status_t fu_pool_for_n(
    fu_pool_t pool,
    size_t n,
    fu_for_task_t callback,
    fu_lambda_context_t context);
```

Tasks split into equal __contiguous__ chunks per worker, keeping coordination near zero - the right choice when tasks cost about the same, so a static split stays balanced. The callback receives `context`, the `task` index in [0, `n`), the `thread` index, and `compute_domain`.

```
void double_each(void *array, size_t i, size_t thread, size_t compute_domain) {
    int *data = (int *)array;
    data[i] = data[i] * 2; // Uniform O(1) work per index
}
if (fu_pool_for_n(pool, length, double_each, array) != fu_success_k) return; // ! Never spawned, or busy
```

- `pool` (in): Pool handle, must not be NULL and initialized.
- `n` (in): Task count, may be 0 - a no-op.
- `callback` (in): Runs once per task, must not be NULL if `n` > 0.
- `context` (in): Shared context, may be NULL.

See also: [fu_pool_for_n_dynamic](#fu_pool_for_n_dynamic) for uneven workloads, `[fu_pool_for_slices](#fu_pool_for_slices)` for range callbacks.

Statuses: - [`fu_not_spawned_k`](#fu_not_spawned_k) A pool with no workers.
- [`fu_contended_k`](#fu_contended_k) While the pool runs another dispatch, and then nothing ran.

## fu_pool_for_n_dynamic

Runs `callback` per `n` __uneven-cost__ tasks via work-stealing, blocking until done.

```c
fu_status_t fu_pool_for_n_dynamic(
    fu_pool_t pool,
    size_t n,
    fu_for_task_t callback,
    fu_lambda_context_t context);
```

Each worker starts with one task, then claims more from a shared cursor as it finishes, so fast workers absorb the slack and one slow task never stalls the rest - the right choice when task cost is unpredictable or data-dependent. Same callback shape as [fu_pool_for_n](#fu_pool_for_n), but task order is __not__ deterministic.

```
void variable_work(void *ctx, size_t task, size_t thread, size_t compute_domain) {
    complex_computation(task); // May take 1us or 1ms
}
if (fu_pool_for_n_dynamic(pool, count, variable_work, ctx) != fu_success_k) return; // ! Never spawned, or busy
```

- `pool` (in): Pool handle, must not be NULL and initialized.
- `n` (in): Task count, may be 0 - a no-op.
- `callback` (in): Runs once per task, must not be NULL if `n` > 0.
- `context` (in): Shared context, may be NULL.

See also: [fu_pool_for_n](#fu_pool_for_n) for balanced workloads.

Statuses: - [`fu_not_spawned_k`](#fu_not_spawned_k) A pool with no workers.
- [`fu_contended_k`](#fu_contended_k) While the pool runs another dispatch, and then nothing ran.

## fu_pool_unsafe_for_threads

Dispatches `callback` on every worker __without__ blocking; returns a generation token.

```c
fu_status_t fu_pool_unsafe_for_threads(
    fu_pool_t pool,
    fu_for_threads_t callback,
    fu_lambda_context_t context,
    fu_generation_t *generation_out);
```

On __inclusive__ pools the caller's own slice runs inside [`fu_pool_unsafe_join`](#fu_pool_unsafe_join), so the pool cannot complete until the caller joins.

```
fu_generation_t generation;
if (fu_pool_unsafe_for_threads(pool, callback, context, &generation) != fu_success_k) return;
prepare_next_batch();
fu_pool_unsafe_join(pool, generation);
```

- `pool` (in): Pool handle, must not be NULL and initialized.
- `callback` (in): Runs on each thread, must not be NULL.
- `context` (in): Shared context, must stay valid until the join, may be NULL.
- `generation_out` (out): Receives the token for [`fu_pool_unsafe_join`](#fu_pool_unsafe_join).

Notes: Returns immediately. Call no other pool operation until you join this generation.

See also: [fu_pool_unsafe_join](#fu_pool_unsafe_join), [fu_pool_for_threads](#fu_pool_for_threads) for the blocking form.

Statuses: - [`fu_contended_k`](#fu_contended_k) With the zero token while the pool runs another dispatch, and then nothing ran.

## fu_pool_is_complete

Whether `generation` has finished - a __non-blocking__ poll.

```c
fu_status_t fu_pool_is_complete(
    fu_pool_t pool,
    fu_generation_t generation,
    fu_bool_t *complete_out);
```

- `pool` (in): Pool handle, must not be NULL and initialized.
- `generation` (in): Token from [`fu_pool_unsafe_for_threads`](#fu_pool_unsafe_for_threads).
- `complete_out` (out): Receives non-zero once complete, which also publishes every contributor's writes.

Notes: On __inclusive__ pools this turns non-zero only after [`fu_pool_unsafe_join`](#fu_pool_unsafe_join) runs the caller's slice, so poll-then-join is for __exclusive__ pools only.

## fu_pool_unsafe_join

Blocks until `generation` completes, running the caller's slice on an __inclusive__ pool.

```c
void fu_pool_unsafe_join(fu_pool_t pool, fu_generation_t generation);
```

The synchronization point for the non-blocking API: on return every contributor is done, their writes are visible, and the pool is ready for the next dispatch. __Idempotent__ on a stale or already-joined generation.

```
fu_generation_t generation;
fu_bool_t complete = 0;
if (fu_pool_unsafe_for_threads(pool, callback, context, &generation) != fu_success_k) return;
while (!complete && fu_pool_is_complete(pool, generation, &complete) == fu_success_k)
    do_other_work(); // ! Exclusive pools only
fu_pool_unsafe_join(pool, generation);
```

- `pool` (in): Pool handle, must not be NULL and initialized.
- `generation` (in): Token from [`fu_pool_unsafe_for_threads`](#fu_pool_unsafe_for_threads).

See also: [fu_pool_unsafe_for_threads](#fu_pool_unsafe_for_threads), [fu_pool_for_threads](#fu_pool_for_threads).

## FORKUNION_POOL_NAME_CAPACITY

Capacity of a pool name including its null terminator; a longer name is clipped to fit.

```c
#define FORKUNION_POOL_NAME_CAPACITY 16
```

## FORKUNION_CAPABILITIES_NAME_CAPACITY

Buffer size [`fu_name_capabilities`](#fu_name_capabilities) never overruns, including its null terminator.

```c
#define FORKUNION_CAPABILITIES_NAME_CAPACITY 512
```

