Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

isolate

import isolate

High-throughput parallel concurrency backed by a pool of operating-system isolate threads.

Architectural Model: Shared-Nothing Isolates

Modern multi-core computing demands concurrency models that scale linearly with CPU core count while safeguarding applications against data races and memory corruption. Zuri addresses this by adopting a Shared-Nothing Isolate architecture (similar to the Actor model and Dart/Erlang isolates) rather than shared-memory green threads.

In Zuri, every isolate thread executes within its own self-contained Virtual Machine (VM) instance. Each isolate possesses an independent generational garbage-collected heap, nursery allocator, call frame stack, register array, and global module namespace. Isolates run concurrently across physical CPU cores without sharing heap pointers or mutable structures with other isolates or the main thread.

Inter-isolate communication is accomplished strictly across defined boundary barriers. When values, closures, or data structures are transmitted via spawn(), join(), or Channel queues, Zuri traverses the object graph and constructs an isomorphic copy on the destination isolate’s heap. This transfer barrier faithfully preserves cyclic graphs, shared object identity within the payload, and class prototypes without sharing mutable memory across thread boundaries.

Memory Footprint & Task Density

High-concurrency systems frequently face a dilemma between heavy operating system threads and complex coroutine runtimes. Zuri reconciles high task density with OS-level multi-core execution through a two-tier scheduling design:

Instead of allocating a dedicated OS thread or a dedicated runtime stack for every dispatched task, calling isolate.spawn() creates a lightweight Task descriptor (the callee and its arguments, already captured into a heap-independent snapshot) and pushes it onto a shared, mutex-guarded queue.

  • Isolate Pooling & Reuse: A fixed pool of isolate OS threads, configured via configure() and defaulting to the system’s available CPU core count is pre-warmed at startup. These isolate threads continuously drain the task queue, executing tasks sequentially within their long-lived isolate contexts. - Small, Fixed-Size Task Descriptors: Because a queued task is a captured value snapshot rather than a suspended OS thread, applications can enqueue large task bursts (e.g. 100,000+ jobs) without paying for a dedicated stack or heap per task. The fixed part of a task (its queue slot plus its join handle) is a few hundred bytes; a task’s actual total footprint on top of that scales with whatever the callee’s own arguments and captured state need to carry across the boundary, same as any other value transfer described above.

Garbage Collection & Scalability Under Load

A frequent performance bottleneck in multi-threaded runtimes is garbage collection cross-talk. When multiple threads share a single global heap, an allocation spike in one thread can trigger global Stop-The-World (STW) pauses, lock contention on shared allocation arenas, or heavy write-barrier synchronization that degrades throughput across all CPU cores.

Zuri eliminates GC interference through complete heap segregation:

  • Local Generational Collections: Every isolate isolate manages its own nursery and old-generation heaps. Young-generation minor collections and major mark-sweep cycles execute entirely on the local isolate thread. - Zero Stop-The-World Pauses Across Threads: A garbage collection cycle on Isolate A never halts, pauses, or synchronizes with Isolate B or the main thread. Compute-heavy, allocation-intensive workloads (such as parallel tree building, matrix algebra, and image rendering) achieve near-linear multi-core speedup without GC contention.

Data Safety & Race Conditions

Concurrency bugs in shared-memory architectures such as data races, torn reads, and lock inversion deadlocks often arise from uncontrolled concurrent access to mutable heap memory.

  • Data-Race Free by Construction: Because memory is physically partitioned into isolated heaps, two isolates can never concurrently mutate the same object in memory. Data races on heap objects are fundamentally impossible, eliminating the need for mutex locks or synchronized wrappers around user data structures. - Snapshot Closure Semantics: When a closure is passed to an isolate, its captured local variables and referenced global definitions are snapshot at dispatch time. The isolate receives an isolated clone of the captured state; subsequent mutations performed by the caller never affect the isolate, and isolate mutations never leak back to the caller.

Comparison with Go Goroutines

Both Go and Zuri provide lightweight concurrency abstractions, but they optimize for different runtime trade-offs:

Go utilizes Communicating Sequential Processes (CSP) built on M:N green threads multiplexed across OS threads with a single shared global heap. Zuri utilizes Thread-Pooled Shared-Nothing Isolates with deep-copy transfer barriers.

FeatureGo GoroutinesZuri Isolates
Concurrency ParadigmM:N green threads on a single shared heapThread-pooled shared-nothing isolates / actors
Memory SafetyShared memory; data races possible without mutexesData-race free by design via physical isolation
GC ImpactShared global GC cycles and allocator write barriersIndependent per-isolate GC; zero STW cross-talk
Task Density~2 KB per goroutine stacka few hundred bytes of fixed queue/handle overhead per task; pooled thread reuse
CommunicationShared-memory pointer channels or locksDeep-copied value graphs or moved native resources
Primary StrengthsUltra-high-concurrency non-blocking I/O multiplexingCompute-heavy parallel processing & task pipelines

Values That Can Cross Isolate Boundaries

Zuri’s value transfer engine supports virtually all language data types across isolate boundaries, including:

  • Primitives: nil, bool, number, bigint, string, bytes, and range. - Collections: list and dict (including nested, multidimensional, and cyclic structures). - Object-Oriented Structures: Class instances, class declarations (preserving static fields, superclasses, and method tables), and bound methods. - Executable Code: Functions, closures (capturing upvalues), anonymous lambdas, and top-level scripts.
  • Modules: imported modules and module bindings, including one a closure captured.
  • Native Handles: Ptr handles (moved linearly), Channel handles, and Isolate handles.

A module is reloaded on the receiving isolate rather than copied, so the recipient gets that isolate’s own instance of it, with its own top-level state.

Non-Transferable Types

Attempting to transmit an active operating system file handle (file(...)) across a isolate boundary raises an IsolateError.

Native Resources & Ownership Transfer

Operating system resources encapsulated within native pointers (Ptr)—such as database connections, network sockets, or compression streams—cannot be safely duplicated across threads.

Zuri enforces linear ownership transfer (move semantics) for Ptr values: - When a Ptr is sent through spawn(), join(), or a Channel, ownership is transferred to the recipient, and the sender’s handle is permanently invalidated.

  • Subsequent attempts to read or use an invalidated Ptr will raise an error. - In contrast, synchronization primitives like Channel and Isolate handles are thread-safe and may be freely shared among multiple isolates.

Failure Isolation & Fault Tolerance

A software defect or unhandled exception within one isolate is strictly contained and will never terminate peer isolates, corrupt the pool, or crash the host process.

  • Structured Error Propagation: Raised exceptions in a isolate task are captured and re-raised as IsolateError (complete with original source file and line stack traces) when join() or try_join() is invoked. - Automatic Isolate Recycling: In the rare event of an unrecoverable isolate panic, the affected isolate thread is safely retired and replaced with a fresh isolate, maintaining full pool capacity. - Un-Joined Failure Diagnostics: If an isolate fails and its handle is dropped without being joined, a diagnostic warning is emitted to ensure errors are never silently swallowed.

Cancellation Semantics

Applications can cancel running or queued isolates via Isolate.cancel().

  • Preemptive Blocking Interruption: If an isolate is blocked on a channel operation (Channel.send(), Channel.recv()), an isolate join (Isolate.join()), or a multiplexer (select(), wait_any(), wait_all()), cancel() interrupts the blocking call promptly with a IsolateCancelledError. - Cooperative Compute Polling: During active computational loops, code can cooperatively poll isolate.is_cancelled() to gracefully terminate execution.

Structured Concurrency (isolate.scope())

Unbounded background concurrency can lead to orphan tasks, leaked resources, and missed errors if spawned isolates outlive their intended context. Zuri provides Structured Concurrency through isolate.scope(body) and the Scope handle.

  • Lifetime Bounded to Lexical Scope: Any isolate spawned via s.spawn() within a scope() block is guaranteed to complete before scope() returns. - Fail-Fast Automatic Cancellation: If the scope’s body raises an exception or if any child isolate fails, all other sibling isolates in that scope are automatically sent a cancellation signal (cancel()), preventing wasted computation. - Clean Teardown & Error Prioritization: scope() waits for all cancelled siblings to finish shutting down before propagating the root failure to the caller.

Examples

Basic Task Spawning

import isolate

def calculate_primes(limit) {
  var count = 0
  iter var n = 2; n <= limit; n++ {
    var is_prime = true
    iter var d = 2; d * d <= n; d++ {
      if n % d == 0 {
        is_prime = false
        break
      }
    }
    if is_prime count++
  }
  return count
}

var task = isolate.spawn(calculate_primes, 100_000)
echo "Primes found: ${task.join()}"

Parallel Batch Mapping

import isolate

def square(n) {
  return n * n
}

var numbers = [1, 2, 3, 4, 5, 6, 7, 8]
var results = isolate.map(square, numbers)
echo "Squares: ${results}"

Structured Concurrency (scope)

import isolate

var total = isolate.scope(def(s) {
  var w1 = s.spawn(def() { return 10 * 2 })
  var w2 = s.spawn(def() { return 20 * 2 })
  return w1.join() + w2.join()
})
echo "Total: ${total}"

Publish-Subscribe (Broadcast)

import isolate

var hub = isolate.broadcast()
var sub1 = hub.subscribe()
var sub2 = hub.subscribe()

isolate.spawn(def(c) { echo "Isolate 1: ${c.recv()}" }, sub1)
isolate.spawn(def(c) { echo "Isolate 2: ${c.recv()}" }, sub2)

hub.send("Hello Subscribers!")
hub.close()

The isolate API

Every public name in isolate, wherever it is declared. Each links to the page that documents it.

NameKindSummary
isolate.BroadcastclassA one-to-many publish-subscribe message distribution hub.
isolate.ChannelclassA thread-safe, multi-producer multi-consumer (MPMC) message queue.
isolate.IsolateclassA handle to an isolate, returned by spawn().
isolate.IsolateCancelledErrorclassRaised from Isolate.join(), Channel.send(), Channel.recv(), isolate.wait_any(), isolate.wait_all(),…
isolate.IsolateErrorclassRaised when an isolate’s function raises an uncaught error, a Channel operation fails, or a value cannot…
isolate.IsolateTimeoutErrorclassRaised when Isolate.join(), Channel.send(), or Channel.recv() is given a timeout and it elapses…
isolate.ScopeclassA structured concurrency supervisor and task nursery.
isolate.active_countfunction
isolate.broadcastfunctionCreates a new one-to-many Broadcast distribution hub.
isolate.channelfunctionCreates a new thread-safe Channel.
isolate.configurefunctionSets how many isolate threads the pool may run at once.
isolate.cpu_countfunction
isolate.is_cancelledfunctionChecks whether the currently running isolate has been asked to stop via its handle’s cancel().
isolate.is_shutdownfunction
isolate.mapfunctionRuns fn once per item in items, in parallel across the isolate pool, and returns the results in the same…
isolate.pool_sizefunctionHow many isolate threads the pool may run at once.
isolate.queued_countfunction
isolate.scopefunctionExecutes body(s) within a structured concurrency scope.
isolate.selectfunctionMultiplexes across multiple channels, blocking until at least one channel is ready to deliver a value or is…
isolate.shutdownfunctionStops the pool from accepting any further spawn() calls, then blocks until every isolate already spawned;…
isolate.spawnfunction
isolate.spawn_namedfunctionSame as spawn(), but gives the isolate a name; purely a debugging label, read back via Isolate.name()…
isolate.started_countfunctionHow many isolate threads the pool has started so far.
isolate.wait_allfunctionBlocks until every one of isolates has finished, then returns their results in the same order as isolates.
isolate.wait_anyfunctionBlocks until at least one of isolates finishes, and returns that one.

Submodules

ModuleReached asSummary
isolate.broadcastisolate.broadcast.*One-to-many (fan-out) publish-subscribe message distribution for isolate isolates.
isolate.channelisolate.channel.*Thread-safe, multi-producer multi-consumer (MPMC) communication queues for passing values and messages across…
isolate.errorisolate.error.*IsolateError, kept in its own leaf module (no imports of its own) so both index and channel can depend…

Functions

spawn()

isolate.spawn(fn: function, ...args: list)

spawn_named()

isolate.spawn_named(name: string, fn: function, ...args: list) -> Isolate

Same as spawn(), but gives the isolate a name; purely a debugging label, read back via Isolate.name() and included in the “unobserved failure” warning (see the module docs’ “Failure isolation” section) if this one fails and is never join()ed or try_join()ed.

Parameters

  • name (string)
  • fn (function) — a closure, bound method, or class method.
  • args (...any)

Returns Isolate

Raises TypeError if fn is not callable

Raises IsolateError if fn is a native function or class, fn/args cannot cross an isolate boundary, or the pool has been shutdown()

scope()

isolate.scope(body: function)

Executes body(s) within a structured concurrency scope.

isolate.scope() establishes a structured task boundary, guaranteeing that all isolates spawned via s.spawn() or s.spawn_named() finish before scope() returns.

Lifecycle & Error Semantics

  1. Completion Guarantee: scope() blocks until body(s) returns and all child isolates have terminated. 2. Automatic Cancellation: If body raises an exception or if any child isolate fails, all other still-running children in the scope are automatically sent a cancellation signal (cancel()). 3. Graceful Teardown: scope() waits for all cancelled isolates to cleanly finish their teardown before returning or re-raising. 4. Error Priority: If body raised an exception, body’s exception is re-raised. Otherwise, the earliest child failure (excluding secondary IsolateCancelledError cascades) is re-raised.
import isolate

var result = isolate.scope(def(s) {
  var task1 = s.spawn(def() { return 100 })
  var task2 = s.spawn(def() { return 200 })
  return task1.join() + task2.join()
})
echo "Total: ${result}" # 300

Parameters

  • body (function) — A callback function def(s) that receives a fresh Scope.

Returns — any: The return value of body(s).

Raises IsolateError if a child isolate failed and body did not raise.

Raises any: Any exception raised by body(s).

configure()

isolate.configure(threads: int)

Sets how many isolate threads the pool may run at once. Has no effect if the pool has already started; its size is fixed once the first isolate runs.

The size is a ceiling. A thread is started only when an isolate is waiting and every thread already started is busy, so a pool sized for peak load costs only the threads the load actually needed.

Parameters

  • threads (int)

Returns — bool: true if applied, false if the pool had already started.

Raises ValueError if threads is less than 1

pool_size()

isolate.pool_size() -> int

How many isolate threads the pool may run at once. Starts the pool, with whatever size configure() set or the machine’s CPU count otherwise, if it has not started yet.

Returns int

started_count()

isolate.started_count() -> int

How many isolate threads the pool has started so far. Threads start as isolates need them, so this grows with the most isolates ever waiting at once and never passes pool_size().

Returns int

cpu_count()

isolate.cpu_count()

Returns — int: the number of logical CPUs this machine reports.

shutdown()

isolate.shutdown(timeout: ?number)

Stops the pool from accepting any further spawn() calls, then blocks until every isolate already spawned; queued or already running; has finished. Nothing in flight is abandoned.

Permanent: once called, every spawn() for the rest of the process raises IsolateError, even if this call itself later times out.

Parameters

  • timeout (number) — seconds to wait before giving up. Waits indefinitely if omitted.

Raises IsolateTimeoutError if timeout elapses with isolates still in flight

active_count()

isolate.active_count()

Returns — int: isolates an isolate is actively running right now. Does not include ones still queued_count().

queued_count()

isolate.queued_count()

Returns — int: isolates spawned but not yet picked up by a isolate.

is_shutdown()

isolate.is_shutdown()

Returns — bool: true if shutdown() has been called.

wait_any()

isolate.wait_any(isolates: list, timeout: ?number)

Blocks until at least one of isolates finishes, and returns that one. Useful for reacting to whichever of several tasks completes first, rather than joining them one at a time in a fixed order.

Parameters

  • isolates ([Isolate]) — must not be empty.
  • timeout (number) — seconds to wait before giving up. Waits indefinitely if omitted.

Returns — Isolate: whichever one finished first.

Raises ValueError if isolates is empty

Raises IsolateTimeoutError if timeout elapses before any of them finish

Raises IsolateCancelledError if the CALLING isolate is cancelled while blocked here

wait_all()

isolate.wait_all(isolates: list, timeout: ?number) -> list

Blocks until every one of isolates has finished, then returns their results in the same order as isolates.

Parameters

  • isolates ([Isolate]) — may be empty (returns []).
  • timeout (number) — seconds to wait for all of them combined, not per isolate. Waits indefinitely if omitted.

Returns list

Raises IsolateError from the first one (in isolates order) whose function raised, once every one of them has finished

Raises IsolateTimeoutError if timeout elapses before all of them finish

Raises IsolateCancelledError if the CALLING isolate is cancelled while blocked here

map()

isolate.map(fn: function, items: list, timeout: ?number) -> list

Runs fn once per item in items, in parallel across the isolate pool, and returns the results in the same order as items. Equivalent to spawning an isolate per item and passing the handles to wait_all().

Parameters

  • fn (function) — a closure, bound method, or class method.
  • items (list)
  • timeout (number) — seconds to wait for the whole batch combined. Waits indefinitely if omitted.

Returns list

Raises IsolateError from the first item (in items order) whose call raised, once every call has finished

Raises IsolateTimeoutError if timeout elapses before every call finishes

Raises IsolateCancelledError if the CALLING isolate is cancelled while blocked here

is_cancelled()

isolate.is_cancelled() -> bool

Checks whether the currently running isolate has been asked to stop via its handle’s cancel(). Meant to be polled periodically by long-running or looping isolate code so it can return early.

Calling this outside an isolate (from the main script, or from another thread) always returns false.

Returns bool

Classes

Isolate

class isolate.Isolate

A handle to an isolate, returned by spawn().

Constructor

isolate.Isolate(ptr)

Isolate.join()

isolate.Isolate.join(timeout: ?number) -> any

Blocks until the isolate finishes and returns its result. May be called more than once; later calls return immediately with the same result.

Parameters

  • timeout (number) — seconds to wait before giving up. Waits indefinitely if omitted.

Returns any

Raises IsolateError if the isolate’s function raised an uncaught error

Raises IsolateTimeoutError if timeout elapses first

Raises IsolateCancelledError if the CALLING isolate (not this one) is cancelled while blocked here

Isolate.cancel()

isolate.Isolate.cancel()

Requests that the isolate stop. Has no effect on an isolate that has already finished.

If the isolate is currently blocked in Channel.send()/ recv(), Isolate.join(), wait_any(), wait_all(), or select(), that call is interrupted within roughly 50ms and raises IsolateCancelledError.

Otherwise; plain, non-blocking code; cancellation is cooperative: it does not interrupt code that’s already running. The isolate’s own function must check isolate.is_cancelled() and return on its own.

Isolate.is_cancelled()

isolate.Isolate.is_cancelled()

Returns — bool: true if cancel() has been called on this isolate.

Isolate.name()

isolate.Isolate.name()

Returns — ?string: the name given via spawn_named(), or nil if this isolate was started with plain spawn().

Isolate.try_join()

isolate.Isolate.try_join() -> any

Returns the isolate’s result, or nil if it has not finished yet. Does not block.

Returns any

Raises IsolateError if the isolate’s function raised an uncaught error

Note: an isolate that returns nil is indistinguishable from one still running. Use is_done() to tell them apart.

Isolate.is_done()

isolate.Isolate.is_done()

Returns — bool: true if the isolate has finished.

Isolate.status()

isolate.Isolate.status()

Unlike try_join(), never marks a failure as “observed”; safe to poll purely to check progress without silencing the warning a failure that’s never actually join()ed/try_join()ed would otherwise get (see the module docs’ “Failure isolation” section).

Returns — string: 'pending' while still running, 'done' once finished successfully, or 'error' once finished with an uncaught error.

Isolate.handle()

isolate.Isolate.handle()

This isolate’s underlying native handle, needed by wait_any() to operate on a plain list of Isolates from outside the class. Not meant for direct use.

Scope

class isolate.Scope

A structured concurrency supervisor and task nursery.

Scope manages the lifecycle of a set of concurrent child isolates spawned within a isolate.scope() block. It guarantees that:

  1. Lifetime Bounding: All isolates spawned via s.spawn() or s.spawn_named() are tracked by the scope and cannot outlive the enclosing scope(body) block. 2. Fail-Fast Error Handling: If any child isolate raises an unhandled error (or if the scope body itself fails), all remaining sibling isolates are automatically cancelled (cancel()), preventing wasted computation. 3. Deterministic Teardown: scope() waits for all active and cancelled children to complete execution before re-raising the root failure or returning the body’s result.

Scope instances cannot be constructed directly; they are created and passed to the callback function by isolate.scope(body).

Examples
Parallel Task Aggregation
import isolate

var summary = isolate.scope(@(s) {
  var task_a = s.spawn(@() { return "Task A complete" })
  var task_b = s.spawn(@() { return "Task B complete" })
  return "${task_a.join()} & ${task_b.join()}"
})
echo summary # "Task A complete & Task B complete"
Fail-Fast Child Cancellation
import isolate

catch {
  isolate.scope(def(s) {
    # Spawn an isolate that fails quickly
    s.spawn(def() {
      raise "critical error in child"
    })
    # Sibling isolate is automatically cancelled as soon as child 1 fails
    s.spawn(def() {
      iter var i = 0; i < 1_000_000; i++ {
        if isolate.is_cancelled() break
      }
    })
  })
} as err {
  echo "Scope caught child failure: ${err.message}"
}

Constructor

isolate.Scope()

Scope.spawn()

isolate.Scope.spawn(fn: function, ...args: list)

Spawns a concurrent isolate task tracked by this scope.

The spawned isolate is bound to this scope’s lifecycle: isolate.scope() will not return until this isolate has completed. If this isolate fails, all sibling isolates in the scope are automatically cancelled.

isolate.scope(def(s) {
  var w = s.spawn(def(x, y) { return x + y }, 10, 20)
  echo "Result: ${w.join()}" # 30
})

Parameters

  • fn (function) — A callable function, closure, or method to execute.
  • args (...any) — Arguments to pass to fn.

Returns — Isolate: A handle to the spawned isolate.

Raises TypeError if fn is not callable.

Raises IsolateError if fn is a native function or class, if fn/args cannot cross isolate boundaries, or if the isolate pool has shut down.

Scope.spawn_named()

isolate.Scope.spawn_named(name: string, fn: function, ...args: list)

Spawns a named concurrent isolate task tracked by this scope.

Operates identically to Scope.spawn(), but assigns an identifier name used in diagnostic error messages and debugging traces.

isolate.scope(def(s) {
  var w = s.spawn_named("fetch-user", fetch_user, user_id)
  return w.join()
})

Parameters

  • name (string) — A human-readable identifier for the isolate.
  • fn (function) — A callable function, closure, or method to execute.
  • args (...any) — Arguments to pass to fn.

Returns — Isolate: A handle to the spawned isolate.

Raises TypeError if fn is not callable.

Raises IsolateError if fn is a native function or class, if fn/args cannot cross isolate boundaries, or if the isolate pool has shut down.

Scope.children()

isolate.Scope.children()

Returns all Isolate handles spawned within this scope.

isolate.scope(def(s) {
  s.spawn(task1)
  s.spawn(task2)

  for child in s.children() {
    echo "Child isolate active"
  }
})

Returns — list: A list of Isolate instances in the order they were spawned.


2026, Richard Ore and Zuri contributors