Concurrency

Threads, tasks, channels, locks, worker pools and atomics.

Generated by bin/build_library_doc.py from the kernel sources. Do not edit by hand: change the generator, or the doc comments in kernel/src/, and re-run it.

Tasks and threads

CLASS Task

User-visible handle for a CONCURRENT-spawned task.

────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────

Task is the per-task handle carried as the currentTask reference inside a CONCURRENT block. User code can only obtain a Task through the compiler-injected currentTask identifier — Tasks cannot be stored in fields, assigned to variables, or passed as arguments (analyzer-enforced; see concurrent_scope_diagnostics.py rules R1– R5, with R6 rejecting bare CREATE Task(...)).

────────────────────────────────────────────────────────────────── NOT USER-CONSTRUCTIBLE ──────────────────────────────────────────────────────────────────

Task has no public INIT. The only constructor is INTERNAL and is invoked by the CONCURRENT-block lowering (_ev_task_scope::spawn in EV_task_native.hpp): one Task per spawned child, installed as the new OS thread's thread_local _ev_current_task slot before the body runs and cleared on body exit.

────────────────────────────────────────────────────────────────── CANCELLATION ──────────────────────────────────────────────────────────────────

Cancellation is cooperative (Decision #2, threads-research §6.8). The enclosing CONCURRENT scope calls task->cancel() (INTERNAL) on every spawned Task whenever any sibling throws or the parent scope cancels. Body code polls currentTask->isCancelled() in long- running loops and returns at its own pace. There is no async tear- down at V1; the _ev_task_scope destructor still joins each spawned OS thread before unwinding.

────────────────────────────────────────────────────────────────── LIFETIME ──────────────────────────────────────────────────────────────────

The Task instance is heap-allocated by _ev_task_scope::spawn and lives until the enclosing scope's destructor runs. Task does NOT own its RawThread — the scope guard does, so the join order is driven by scope unwinding, not by Task CLEANUP.

kernel/src/Task.ev:62

Constructors

INIT(REFERENCE String name)

Methods

METHOD name() RETURNS String

PUBLIC API

METHOD isCancelled() RETURNS boolean

Returns TRUE once the enclosing CONCURRENT scope has flipped this task's cancellation flag. Body code is expected to call this in any long-running loop and exit cooperatively.

METHOD sleep(int64 millis) RETURNS void

Sleep the calling OS thread for millis milliseconds. Lowers to FOREIGN::ev_thread_sleep_ms(millis) → POSIX nanosleep in EV_unsafe_concurrency_native.cpp.

The receiver MUST be the current task: the analyzer rules against storing/passing Task handles ensure currentTask is the only reachable receiver. sleep does NOT observe the cancellation flag — callers that want cancellable sleep check isCancelled() before and after the call.

CLASS Future

Eventually-ready cell holding a worker's result.

────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────

Future[T] is the handle returned by WorkerPool[J, R].submit(...) . The pool spawns a Task that runs the supplied function on the job; when the function returns, the kernel calls future->resolve(result). The submitter holds the Future and reads the result by calling future->await().

────────────────────────────────────────────────────────────────── NOT USER-CONSTRUCTIBLE ──────────────────────────────────────────────────────────────────

Future has no user-callable constructor. The only legitimate construction site is the kernel: WorkerPool.submit allocates one Future per submitted job, hands it to the worker Task, and returns it to the submitter. The analyzer rejects bare CREATE Future[T](...) in user .ev source (concurrent_scope_diagnostics.py R7, H.2.3 follow-up); the kernel construction path bypasses the analyzer via emit-time new ENVZN::Future<T>(...) and isn't constrained by R7.

────────────────────────────────────────────────────────────────── CROSS-THREAD SAFETY ──────────────────────────────────────────────────────────────────

Future is one of the kernel-managed exceptions to Envzn's "user code cannot share instances across threads" rule (sibling to Mutex / Channel / Broker). The worker thread mutates the Future via resolve() / fail(); the submitter thread reads via isReady() / await(). All access goes through the internal Mutex, which provides the happens- before ordering, and the partner-locked ThreadCondition handles the blocking wait without busy-polling.

The kernel guarantees exactly one resolve() OR fail() call per Future — the WorkerPool / scope-guard pairs the construction with a single completion path. The Future itself doesn't enforce this; a second resolve() silently overwrites (harmless since notifyAll already fired on the first call). T-aliasing across threads is avoided because resolve() takes T by value (move at the C++ level) and await() returns a fresh clone (see "Value semantics" below).

────────────────────────────────────────────────────────────────── VALUE SEMANTICS ──────────────────────────────────────────────────────────────────

Future[T] retains the resolved value across multiple await() calls. For class-T, await() returns a .value->clone() so the submitter thread never aliases the worker's payload. For primitive T (int32 etc.), the := value-copy is sufficient and the WHEN T IMPLEMENTS Cloneable split picks the ELSE branch automatically.

Per Constitution §12 paragraph 5: a resolved Future retains its value if the enclosing CONCURRENT scope is later cancelled — only unresolved Futures resolve to FAILURE("scope cancelled") on cancellation. The scope guard arranges this by calling fail() only on Futures whose isResolved field is still FALSE at cancel time.

────────────────────────────────────────────────────────────────── STATUS ──────────────────────────────────────────────────────────────────

Mutex + ThreadCondition substrate via the existing UNSAFE.MUTEX / UNSAFE.CONDVAR FFI; no new _cxx* reserved types.

kernel/src/Future.ev:86

Constructors

INIT()

Methods

METHOD isReady() RETURNS boolean

PUBLIC API Non-blocking check. Returns TRUE if the Future has been resolved (with a value) OR failed (with a STATUS reason); FALSE while the worker is still in flight. Takes the mutex briefly to avoid a torn read of .isResolved.

MODIFY METHOD await() RETURNS | T

Blocking await. Returns the resolved value on success; returns a FAILURE STATUS if the worker threw or the enclosing CONCURRENT scope cancelled before resolution.

Pipe-XOR per Constitution §13.1: the caller MATCHes on the return to dispatch the SUCCESS / FAILURE branches. Spurious wakeups from pthread are tolerated by the WHILE-not-IF loop around readyCond->wait().

Multiple await() calls on the same Future are safe — the value is preserved (cloned out for class T, value-copied for primitive T) and subsequent awaits return the same result without re-blocking.

Communication

CLASS Channel

IMPLEMENTS Shareable

Typed, bounded, point-to-point message channel.

────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────

Channel[T] is the blessed primitive for point-to-point handoff of typed values between CONCURRENT-spawned tasks. It is bounded by default — the constructor demands an explicit capacity and a BackpressurePolicy, so "what happens when the consumer falls behind" is a decision the author makes, never a silent unbounded-growth default.

Channel[int32] work := CREATE Channel[int32](4, BackpressurePolicy.BLOCK)

CONCURRENT {
    PARALLEL {                                  // producer
        work->send(1)
        work->send(2)
        work->close()
    }
    PARALLEL {                                  // consumer
        WHILE work->receive() DO {
            printline($RETURNED INTO String)
        }
    }
}

────────────────────────────────────────────────────────────────── BACKPRESSURE ──────────────────────────────────────────────────────────────────

When the buffer is at capacity, send() consults the policy fixed at construction:

────────────────────────────────────────────────────────────────── CROSS-THREAD SAFETY ──────────────────────────────────────────────────────────────────

Channel[T] is one of the kernel-managed exceptions to Envzn's "user code cannot share instances across threads" rule (sibling to Mutex / Broker / Future). Sharing IS the point — a producer task and a consumer task hold the same Channel and coordinate through it. The internal Lock serialises every queue mutation and provides the happens-before ordering; the two partner-locked ThreadConditions (notEmpty / notFull) handle the blocking waits without busy-polling.

────────────────────────────────────────────────────────────────── CLOSE SEMANTICS ──────────────────────────────────────────────────────────────────

close() is sticky. After close: - receive() drains any buffered values (still SUCCESS), then returns FAILURE once empty — so a WHILE ch->receive() DO { … } consumer loop exits cleanly. - send() returns FAILURE immediately. A Channel also closes implicitly when its owning variable goes out of scope (CLEANUP closes before tearing down the buffer).

────────────────────────────────────────────────────────────────── STATUS ──────────────────────────────────────────────────────────────────

Composes Queue[T] storage + Lock + ThreadCondition substrate. capacity is a count; callers pass a positive value.

kernel/src/Channel.ev:96

Constructors

INIT(int64 capacity, BackpressurePolicy policy)

Methods

MODIFY METHOD send(T value) RETURNS STATUS

PUBLIC API Enqueue value. Pipe-XOR STATUS. The bare-named (owning) form consumes the source — the value is moved into the buffer. At capacity, behaviour follows the BackpressurePolicy. Returns FAILURE if the channel is closed.

MODIFY METHOD receive() RETURNS | T

Remove and return the front value. Pipe-XOR: SUCCESS populates value; blocks while the buffer is empty and the channel is open. Returns FAILURE once the channel is closed AND drained, so a WHILE ch->receive() DO { … } loop terminates on close.

MODIFY METHOD tryReceive() RETURNS | T

Non-blocking receive. Pipe-XOR: SUCCESS returns the front value if one is buffered; FAILURE immediately if the buffer is empty (never blocks). A WHILE ch->tryReceive() DO { … } loop drains whatever is currently buffered and then exits.

MODIFY METHOD close() RETURNS void

Sticky shutdown. Wakes every blocked sender and receiver so they re-test their predicate and exit. Idempotent.

METHOD shareableKind() RETURNS String

Shareable marker — short type tag for debug printers / logs.

METHOD isClosed() RETURNS boolean

Non-blocking state read. TRUE once close() (or scope-exit CLEANUP) has run.

METHOD size() RETURNS int64

Current buffered count. Snapshot under the lock — advisory only (the value may change the instant the lock is released).

CLASS Broker

Typed broadcast pub/sub.

────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────

Broker[T] fans a published value out to every current subscriber. One broker per typed message. Each subscriber gets its own buffered Channel[T], so a slow subscriber is governed by its own BackpressurePolicy and does not block a fast one.

Broker[Match] broker := CREATE Broker[Match]()
CONCURRENT {
    PARALLEL {                                   // a subscriber task
        Subscription[Match] sub := broker->subscribe(16, BackpressurePolicy.DROP_OLDEST)
        WHILE sub->receive() DO { handle($RETURNED) }
    }
    PARALLEL { broker->publish(m) }              // a publisher task
}

────────────────────────────────────────────────────────────────── OWNERSHIP / LIFETIME ──────────────────────────────────────────────────────────────────

Each subscriber's Channel is co-owned: the broker holds one strong (reference-counted) handle in an OwnedList[Channel[T]] (the move-only, non-Cloneable collection — standard collections reject a non-Cloneable Channel), and the returned Subscription holds a SHARED MUTABLE REFERENCE that, because Channel is shared-eligible (§9.1), is a strong handle too. Unsubscribe is lazy: the Subscription's CLEANUP closes its channel, and publish skips closed channels. A channel is freed when its last owner drops — so a Subscription may safely outlive the broker (it keeps its channel alive and can still drain it), and the broker need not outlive its subscriptions. This is the ARC guarantee that closes the prior write-after-free.

The broker is itself shared across tasks by PARALLEL capture (like any channel/mutex), not by a SHARED MUTABLE REFERENCE field — so it is an ordinary class, internally synchronized by .lock.

kernel/src/Broker.ev:57

Constructors

INIT()

Methods

MODIFY METHOD publish(REFERENCE T value) RETURNS void

Fan value out to every live subscriber. Each subscriber gets its own clone (class T) or copy (primitive T); a closed (unsubscribed) channel is skipped. Walks the owned registry with a MUTABLE REFERENCE local cursor — ownership grants mutable access, so no SHARED carve-out is needed on the broker side.

MODIFY METHOD subscribe(int32 capacity, BackpressurePolicy policy) RETURNS Subscription[T]

Register a new subscriber with its own buffered channel. Returns a scope-bound Subscription holding a SHARED MUTABLE REFERENCE to the channel. The Subscription is created from the channel handle BEFORE the handle is moved into the registry — the heap channel object is not moved by the handle transfer, so the reference stays valid.

CLASS Subscription

A scope-bound handle to a Broker subscription.

Returned by Broker[T].subscribe(...). It holds a SHARED MUTABLE REFERENCE to its Channel[T] — the carve-out (§9.6) that lets the subscription drive the channel's MODIFY methods (receive/tryReceive), sound because Channel is a SHARED CLASS monitor. Because Channel is shared-eligible (§9.1) the reference is a strong (reference-counted) handle: the subscription co-owns the channel with the broker's registry.

Lifecycle: scope-bound. When the subscription's declaring scope ends, CLEANUP closes the channel; the broker skips closed channels on future publishes (lazy unsubscribe). The channel is freed when its last owner drops — so a subscription may outlive the broker and still drain its (closed) channel safely; there is no dangling reference.

kernel/src/Subscription.ev:27

Fields

Constructors

INIT(MUTABLE REFERENCE Channel[T] channel)

Methods

MODIFY METHOD receive() RETURNS | T

Blocking receive of the next published value. Pipe-XOR: SUCCESS yields the value; FAILURE once the channel is closed and drained.

MODIFY METHOD tryReceive() RETURNS | T

Non-blocking receive. Pipe-XOR: SUCCESS if a value is buffered; FAILURE immediately otherwise. A WHILE sub->tryReceive() DO { … } loop drains whatever is currently buffered and exits.

Synchronisation

CLASS Mutex

kernel/src/Mutex.ev:80

Fields

Constructors

INIT(REFERENCE T initial)

Methods

MODIFY METHOD acquire() RETURNS void

Block until the lock is acquired. Mirrors Lock.acquire(). Use through SYNCHRONIZED in user code — manual acquire is the C-mutex anti-pattern (forget release, leak the lock).

MODIFY METHOD release() RETURNS void

Release the lock. Mirrors Lock.release(). Pairs with a previous acquire() on the same Mutex from the same thread.

MODIFY METHOD tryAcquire() RETURNS boolean

Non-blocking attempt to acquire. Mirrors Lock.tryAcquire(). Returns TRUE if the caller now holds the lock (and is responsible for calling release() exactly once); FALSE if another thread currently holds it.

CLASS Lock

Kernel mutual-exclusion primitive.

────────────────────────────────────────────────────────────────── PURE SYNCHRONIZATION ──────────────────────────────────────────────────────────────────

Lock is the bare OS-level mutual-exclusion primitive — a thin Envzn wrapper over UNSAFE.MUTEX + pthread shims. It synchronises access; it does not bundle protected data. The user-facing Mutex[T] (see Mutex.ev) composes a Lock with a typed data field for the "lock + protected state" pattern; kernel-internal sites that need pure synchronisation (Channel's buffer lock, Future.value, ThreadCondition partner) hold a Lock directly.

────────────────────────────────────────────────────────────────── USAGE ──────────────────────────────────────────────────────────────────

Use through the SYNCHRONIZED block. The block guarantees the lock is released when control leaves the block — even via RETURN, BREAK, or PANIC from inside:

SYNCHRONIZED .myLock {
    // critical section
    IF .someCondition THEN {
        RETURN     // lock still released — guaranteed by block scope
    }
    .field = newValue
}

The compiler emits an ENVZN::_ev_lock_guard (defined in EV_unsafe_concurrency_native.hpp) over the Lock's .native handle; release happens in the guard's destructor on every exit path.

────────────────────────────────────────────────────────────────── RAW ACQUIRE / RELEASE — narrow uses only ──────────────────────────────────────────────────────────────────

The acquire() / release() / tryAcquire() methods are exposed for the rare cases where the lock-and-unlock paths can't be expressed as a single scope:

Outside those cases: prefer SYNCHRONIZED. Manual acquire/release is the C-mutex anti-pattern (forget release, leak the lock).

kernel/src/Lock.ev:70

Constructors

INIT()

Methods

MODIFY METHOD acquire() RETURNS void

Block until the lock is acquired. Used directly by ThreadCondition (and similar primitives that need explicit acquire/release pairing); everywhere else, prefer the SYNCHRONIZED block which guarantees release on every exit path.

MODIFY METHOD release() RETURNS void

Release the lock. Pairs with a previous acquire() on the same Lock from the same thread. Calling release() without first holding the lock is undefined behaviour at the OS level — pthread does not require the implementation to detect or report this misuse.

MODIFY METHOD tryAcquire() RETURNS boolean

Non-blocking attempt to acquire the lock. Returns TRUE if the caller now holds the lock (and is responsible for calling release() exactly once); FALSE if another thread currently holds it. Useful for optimistic-locking patterns where the caller would rather bail than wait.

Worker pools

CLASS WorkerPool

V1 Part H.4.1: a pool of worker threads that run submitted jobs.

────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────

A WorkerPool owns a fixed set of worker threads that drain a shared, bounded job queue. A job is any TaskStarter — a self-contained unit of work whose start() runs on a worker thread. Jobs are moved in (the pool takes ownership), so a job carries its inputs as value fields and reports results by writing to an output destination it holds — typically a SHARED MUTABLE REFERENCE Channel[R] the caller owns and drains. The pool is non-parametric: it is polymorphic over TaskStarter, not WorkerPool[T].

WorkerPool pool := CREATE WorkerPool(4, 64, BackpressurePolicy.BLOCK)
pool->submit(CREATE RenderJob(tile, resultChannel))
pool->submit(CREATE RenderJob(tile2, resultChannel))
pool->awaitIdle()      // block until both jobs have finished
pool->shutdown()       // stop the workers (also runs at scope exit)

────────────────────────────────────────────────────────────────── STRUCTURE ──────────────────────────────────────────────────────────────────

The pool owns a WorkerPoolCore (the thread-safe engine + job queue) and one shared WorkerBody (the drain loop), and spawns N OS threads via RawThread. Each thread runs the shared body, which loops on the core. The threads are OS-detached at spawn; clean shutdown is driven by the core's live-worker count and exitCond, not by joining std::thread handles — so the pool keeps no RawThread handles after spawning.

The body holds a SHARED MUTABLE REFERENCE to the core (not to the pool), which is why the engine is a separate owned sub-object: the pool hands out a reference to something it owns, never to itself.

────────────────────────────────────────────────────────────────── V1 SCOPE (DETACHED + LONG_LIVED) ──────────────────────────────────────────────────────────────────

STATUS: V1 Part H.4.1 (2026-05-29). First real client of RawThread.

kernel/src/WorkerPool.ev:75

Constructors

INIT(int32 workerCount, int32 capacity, BackpressurePolicy policy)

Methods

MODIFY METHOD submit(TaskStarter job) RETURNS STATUS

Enqueue a job for a worker to run. Pipe-XOR STATUS. The bare-named (owning) form consumes the source — the pool takes ownership of the job. At queue capacity, behaviour follows the BackpressurePolicy fixed at construction. Returns FAILURE if the pool has been shut down.

MODIFY METHOD awaitIdle() RETURNS void

Block until the pool is idle — the queue is empty and no job is running. Does not stop the pool; the workers stay alive for more work. Useful as a batch barrier between submit waves.

MODIFY METHOD shutdown() RETURNS void

Sticky shutdown. Queued-but-unstarted jobs still drain; once the queue empties, every worker leaves its loop and this returns. Idempotent — a second call is a no-op.

METHOD size() RETURNS int64

Number of worker threads in this pool.

CLASS WorkerPoolCore

IMPLEMENTS Shareable

WorkerPoolCore is the thread-safe heart of a WorkerPool: a bounded, move-only job queue plus the synchronisation that lets N worker threads drain it concurrently while submitters hand work in. It is a SHARED CLASS (monitor) — the carve-out (§9.6) that lets the worker threads and the submitting thread drive its MODIFY methods through shared references without owning it.

The public WorkerPool façade owns one WorkerPoolCore and hands a SHARED MUTABLE REFERENCE to it to the worker bodies. Splitting the engine out of the façade is what avoids a self-reference: the façade passes a reference to an owned sub-object (the same idiom Channel uses to hand its bufferLock to its ThreadConditions), never to itself.

THE JOB QUEUE

Jobs are TaskStarter instances, moved in (never cloned), so the queue cannot be Channel[T] (which requires T IS Cloneable). It is a singly-linked chain of WorkerJobNodes behind a head sentinel: submit appends at the tail, processNext splices off the front (FIFO). Append is O(n) in queue depth — acceptable for a bounded pool; see the perf note in WorkerPool.ev.

LIFECYCLE COUNTERS

STATUS: V1 Part H.4.1 (2026-05-29). Composes WorkerJobNode storage + Lock + ThreadCondition substrate. DETACHED + LONG_LIVED behaviour.

kernel/src/WorkerPoolCore.ev:49

Constructors

INIT(int32 capacity, BackpressurePolicy policy)

Methods

MODIFY METHOD registerWorker() RETURNS void

Register a worker about to be spawned. Called synchronously from WorkerPool.INIT, once per worker, BEFORE any shutdown can be requested — so liveWorkers is exactly the worker count before the first awaitWorkersExit() can observe it.

MODIFY METHOD workerExited() RETURNS void

A worker has left its drain loop. Decrements liveWorkers and, when the last worker exits, wakes awaitWorkersExit().

MODIFY METHOD submit(TaskStarter job) RETURNS STATUS

Submit a job. Pipe-XOR STATUS. The bare-named (owning) form consumes the source — the job is moved into the queue. At capacity, behaviour follows the BackpressurePolicy. Returns FAILURE if the pool is shut down.

MODIFY METHOD processNext() RETURNS boolean

Run one job, blocking until work is available. Returns TRUE after running a job (the caller should loop and call again); FALSE once the pool is shut down AND the queue is drained (the worker should leave its loop). The job runs OUTSIDE the lock so other workers can dequeue and submit() does not stall behind a long-running job.

MODIFY METHOD beginShutdown() RETURNS void

Begin a sticky shutdown. Wakes every blocked worker and submitter so they re-test their predicate; queued-but-unstarted jobs still drain (workers exit only once the queue is empty). Idempotent.

MODIFY METHOD awaitWorkersExit() RETURNS void

Block until every worker has left its drain loop. Called by WorkerPool teardown after beginShutdown(), so the worker bodies and this core are guaranteed untouched by any thread before they are destroyed.

MODIFY METHOD awaitIdle() RETURNS void

Block until the pool is idle — no queued jobs and none running. Does NOT shut the pool down; the workers stay alive for more work.

METHOD shareableKind() RETURNS String

Shareable marker — short type tag for debug printers / logs.

Atomics

CLASS AtomicBoolean

INTERNAL kernel class wrapping a single lock-free atomic boolean.

Replaces the parametric Atomic[boolean] instantiation. Brian's choice to ship a concrete-types-per-primitive atomic family (mirroring Rust's core::sync::atomic::AtomicBoolean + siblings) sidesteps the parametric FOREIGN BIND machinery and keeps the C-target migration path open (each AtomicX has a single concrete C11 _Atomic lowering, no templates).

Storage: UNSAFE.HANDLE native — a heap-allocated _Atomic bool owned across the FFI boundary. Lock-free on every platform Envzn supports (bool is the simplest lock-free atomic type).

Surface (all three lock-free patterns): - load() single-step indivisible read - store(v) single-step indivisible write - compareExchange(expected, desired) test-and-set; returns TRUE on successful swap

KERNEL-ONLY (INTERNAL). User code reaches atomics through higher- level patterns (Switchboard, Channel, future Future[T] / Pool / etc.) — never directly.

kernel/src/AtomicBoolean.ev:42

Constructors

INIT(boolean initialValue)

Methods

METHOD load() RETURNS boolean
METHOD store(boolean newValue) RETURNS void
METHOD compareExchange(boolean expected, boolean desired) RETURNS boolean

CLASS AtomicBinary

INTERNAL kernel class wrapping a single lock-free atomic octet. Part of the concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.

Holds a binary — an opaque octet, NOT a number. AtomicUint8 is the numeric counterpart; the two are distinct Envzn types over the same underlying C shim, which is why the C entry points still spell themselves ev_atomic_byte_*.

kernel/src/AtomicBinary.ev:27

Constructors

INIT(binary initialValue)

Methods

METHOD load() RETURNS binary
METHOD store(binary newValue) RETURNS void
METHOD compareExchange(binary expected, binary desired) RETURNS boolean

CLASS AtomicChar8

INTERNAL kernel class wrapping a single lock-free atomic char8 (UTF-8 code unit). Part of the V1 Part E Phase 8 concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.

FFI signatures use uint8 (E2090 forbids char8 at the C boundary); the AtomicChar8 method surface uses char8 and the conversion is a free bit-cast since char8 ≡ uint8 at storage.

kernel/src/AtomicChar8.ev:27

Constructors

INIT(char8 initialValue)

Methods

METHOD load() RETURNS char8
METHOD store(char8 newValue) RETURNS void
METHOD compareExchange(char8 expected, char8 desired) RETURNS boolean

CLASS AtomicChar16

INTERNAL kernel class wrapping a single lock-free atomic char16 (UTF-16 code unit). Part of the V1 Part E Phase 8 concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.

FFI signatures use uint16 (E2090 forbids char16 at the C boundary); the AtomicChar16 method surface uses char16 and the conversion is a free bit-cast since char16 ≡ uint16 at storage.

kernel/src/AtomicChar16.ev:27

Constructors

INIT(char16 initialValue)

Methods

METHOD load() RETURNS char16
METHOD store(char16 newValue) RETURNS void
METHOD compareExchange(char16 expected, char16 desired) RETURNS boolean

CLASS AtomicChar32

INTERNAL kernel class wrapping a single lock-free atomic char32 (UTF-32 code point). Part of the V1 Part E Phase 8 concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.

FFI signatures use uint32 (E2090 forbids char32 at the C boundary); the AtomicChar32 method surface uses char32 and the conversion is a free bit-cast since char32 ≡ uint32 at storage.

kernel/src/AtomicChar32.ev:27

Constructors

INIT(char32 initialValue)

Methods

METHOD load() RETURNS char32
METHOD store(char32 newValue) RETURNS void
METHOD compareExchange(char32 expected, char32 desired) RETURNS boolean

CLASS AtomicInt8

kernel/src/AtomicInt8.ev:22

Constructors

INIT(int8 initialValue)

Methods

METHOD load() RETURNS int8
METHOD store(int8 newValue) RETURNS void
METHOD compareExchange(int8 expected, int8 desired) RETURNS boolean

CLASS AtomicInt16

kernel/src/AtomicInt16.ev:21

Constructors

INIT(int16 initialValue)

Methods

METHOD load() RETURNS int16
METHOD store(int16 newValue) RETURNS void
METHOD compareExchange(int16 expected, int16 desired) RETURNS boolean

CLASS AtomicInt32

kernel/src/AtomicInt32.ev:21

Constructors

INIT(int32 initialValue)

Methods

METHOD load() RETURNS int32
METHOD store(int32 newValue) RETURNS void
METHOD compareExchange(int32 expected, int32 desired) RETURNS boolean

CLASS AtomicInt64

kernel/src/AtomicInt64.ev:21

Constructors

INIT(int64 initialValue)

Methods

METHOD load() RETURNS int64
METHOD store(int64 newValue) RETURNS void
METHOD compareExchange(int64 expected, int64 desired) RETURNS boolean

CLASS AtomicUint8

INTERNAL kernel class wrapping a single lock-free atomic uint8. Part of the concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.

The NUMERIC octet. AtomicBinary is the opaque-octet counterpart; the two are distinct Envzn types over the same underlying C shim.

kernel/src/AtomicUint8.ev:25

Constructors

INIT(uint8 initialValue)

Methods

METHOD load() RETURNS uint8
METHOD store(uint8 newValue) RETURNS void
METHOD compareExchange(uint8 expected, uint8 desired) RETURNS boolean

CLASS AtomicUint16

kernel/src/AtomicUint16.ev:21

Constructors

INIT(uint16 initialValue)

Methods

METHOD load() RETURNS uint16
METHOD store(uint16 newValue) RETURNS void
METHOD compareExchange(uint16 expected, uint16 desired) RETURNS boolean

CLASS AtomicUint32

INTERNAL kernel class wrapping a single lock-free atomic uint32. Part of the concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.

Shares the C ev_atomic_u32_* machinery with AtomicChar (char ≡ uint32 code point) — separate Envzn types, same underlying C shim.

kernel/src/AtomicUint32.ev:25

Constructors

INIT(uint32 initialValue)

Methods

METHOD load() RETURNS uint32
METHOD store(uint32 newValue) RETURNS void
METHOD compareExchange(uint32 expected, uint32 desired) RETURNS boolean

CLASS AtomicUint64

kernel/src/AtomicUint64.ev:21

Constructors

INIT(uint64 initialValue)

Methods

METHOD load() RETURNS uint64
METHOD store(uint64 newValue) RETURNS void
METHOD compareExchange(uint64 expected, uint64 desired) RETURNS boolean

Contracts

GROUP Concurrency

kernel/src/interfaces.ev:141

INTERFACE Shareable

Shareable — the marker interface for a SHARED CLASS monitor: an interior-mutable, self-synchronizing object that may be aliased by reference across concurrent tasks. Per Constitution §771, a SHARED CLASS MUST IMPLEMENTS Shareable (omitting it is E2115), and Shareable is the marker that admits a type into the two places the borrow model otherwise forbids aliasing: - a SHARED MUTABLE REFERENCE field (§9.6), and - the move-only OwnedList[T] collection (owned and moved, never cloned — so it needs no Cloneable bound; Shareable is the explicit constraint atom that admits the non-Cloneable monitor).

The kernel's SHARED CLASS monitors are Channel[T] (the canonical one) and WorkerPoolCore. These are the only kernel types that implement Shareable, and the only element type used in an OwnedList is Channel (the Broker's subscriber registry).

NOT Shareable — the other concurrency types are ordinary classes, not monitors, and deliberately do NOT implement this marker: - Broker / Mutex — shared across tasks by PARALLEL capture plus an internal Lock, not by a SHARED MUTABLE REFERENCE field, so they need no carve-out. - Subscription — a scope-bound handle that merely HOLDS a SHARED MUTABLE REFERENCE Channel; it rides on Channel's shareability rather than being a monitor itself. - Future — an ordinary result cell, not aliased mutably.

A type opts in via IMPLEMENTS Shareable. Envzn forbids empty interfaces (E6001), so the marker carries one descriptive method — shareableKind() returns a short human-readable tag for the concrete type (used by debug printers / kernel logs), mirroring the Inoperative.inoperativeReason() idiom. OwnedList itself never calls it; the interface exists for the type-bound.

kernel/src/interfaces.ev:213

Methods

METHOD shareableKind() RETURNS String

INTERFACE TaskStarter

TaskStarter — body interface for spawnable threads.

A class that IMPLEMENTS TaskStarter can be passed to CREATE Thread(...). When the user calls Thread.start() (the outer kickoff), the runtime spawns an OS thread, sets up that thread's Channel and current-thread accessors, and then invokes body->start() (this interface's method) on the new thread. The dev never calls TaskStarter.start() directly — the symmetric naming mirrors CREATE → INIT.

Example:

CLASS Worker IMPLEMENTS TaskStarter { PRIVATE int32 jobId INIT(int32 jobId) { .jobId = jobId } METHOD start() RETURNS STATUS { Channel->current()->subscribe("jobs") // ... body runs on the spawned OS thread ... RETURN (SUCCESS) } }

Thread t := CREATE Thread("worker", CREATE Worker(42)) t->start() // outer kickoff — runtime calls Worker.start() inside

V1 ban: CREATE Thread(LAMBDA { ... }) is a parse error (Bug #43); thread bodies must be a TaskStarter class. V2 will add the LAMBDA shortcut as a strict superset (move-semantic captures only, REFERENCE captures rejected); existing V1 TaskStarter code continues to work unchanged.

kernel/src/interfaces.ev:705

Methods

METHOD start() RETURNS STATUS

ENUM BackpressurePolicy

BackpressurePolicy — what a Channel[T] / Broker[T] subscriber does when its bounded buffer is full at send/publish time. V1 Part H (ENVZN_CONSTITUTION.md §12). The Channel constructor and the Broker.subscribe call both take an explicit policy; there is no default — every site declares its choice so the failure mode is visible.

kernel/src/enums.ev:152

Case Description
? —
? —
? —
? —