GizStack
View .mdOpen in ChatGPTOpen in Claude

gizlang:task

Structured tasks. A task runs a closure alongside the rest of the program on the same thread (thread runs one on an OS thread of its own); sleeping, joining and I/O suspend only the calling task. A task never outlives the task that spawned it: when its work returns, its unjoined children are cancelled and awaited. Cancellation reaches a task at its next suspension point as fault 12.

gizlang
import * as Task from "gizlang:task";

Types

Fault

gizlang
type Fault = { code: i64; }

The runtime fault that ended a task: 12 when it was cancelled, 13 when it waited for something that could never happen.

  • code: i64

Task

gizlang
type Task<T>

A task that runs to a value of type T; await gives the value.

Opaque: create and use it through this module's functions.

Thread

gizlang
type Thread<T>

Work running on an OS thread of its own (thread).

Opaque: create and use it through this module's functions.

Channel

gizlang
type Channel<T>

A bounded first-in, first-out queue of values between tasks. It belongs to the task that made it and closes, dropping what it still holds, when that task ends.

Opaque: create and use it through this module's functions.

Cell

gizlang
type Cell<T>

One value shared by threads (thread), ArcSwap's role: load gives the latest stored and store replaces it, and neither waits.

Opaque: create and use it through this module's functions.

Lease

gizlang
type Lease = { generation: i64; until: i64; }

A claim fence is monotone even when its wall clock moves backwards. Persistence and authorization remain the caller's responsibility.

  • generation: i64
  • until: i64

Queue

gizlang
type Queue<T>

An unbounded queue for the tasks of any thread while the task that made it lives: any thread pushes, its reader takes everything at once and may put a batch back in front. A short lock guards it (no suspension inside); a push after close is dropped. Values are shared as on a crossing channel and may not hold Refs.

Opaque: create and use it through this module's functions.

LoadOrder

gizlang
type LoadOrder = "relaxed" | "acquire" | "seqCst"

The memory order of a load, as C11 and Rust name them. seqCst (the default) orders it with every other seqCst operation; acquire makes the writes released before the value it reads visible; relaxed orders nothing.

StoreOrder

gizlang
type StoreOrder = "relaxed" | "release" | "seqCst"

The memory order of a store: release makes the writes before it visible to an acquire load of its value.

Order

gizlang
type Order = "relaxed" | "acquire" | "release" | "acqRel" | "seqCst"

The memory order of a read-modify-write (fetchAdd, compareExchange).

Atomic

gizlang
type Atomic

One i64 shared by the tasks of every thread, changed with C11 atomics (sequentially consistent unless an order is given): no lock, no allocation after create. It uses 8 bytes until the process ends, and copies of it are the same cell.

Opaque: create and use it through this module's functions.

Words

gizlang
type Words

A fixed number of i64 words that the tasks of every thread share and change in place, with no lock: a load sees every store made before the store that wrote what it reads (acquire and release), and compareExchange is sequentially consistent. Copies of it are the same words; the last copy to go frees them.

Opaque: create and use it through this module's functions.

Pair

gizlang
type Pair<A, B> = { first: A; second: B; }

The two values of an independent pair of tasks.

Opaque: create and use it through this module's functions.

Functions

spawn

gizlang
function spawn<F>(work: F): Task<ReturnType<F>>

Start work as a task; await gives its result.

join

gizlang
function join<T>(task: Task<T>): T throws Fault

The task's value, or the fault that ended it. A value is taken once.

parallel

gizlang
function parallel<F>(count: i64, body: F): Vec<ReturnType<F>>

body(i) for every i in 0..count on the process's threads (as many as its CPUs), the results in index order. The body may only compute and fail (no I/O, console or shared state); what it captures is read-only while it runs. A fault in any call is this call's fault.

fill

gizlang
function fill<F>(total: i64, chunk: i64, body: F): ReturnType<F>

One Vec of total items made on the process's threads: body(start) gives the chunk items from start (the last chunk the rest), and each thread copies its chunk into place, so the parts are never joined by one serial copy. The items own nothing (numbers, bools, records of them); a chunk of another length faults 1. The body is held to what parallel's is.

thread

gizlang
function thread<F>(work: F): Thread<ReturnType<F>>

work(sys) on an OS thread of its own, in a World of its own: its own event loop, tasks, handles and memory budget, and sys its own System, as a process's main gets. It runs alongside every task of this thread; what work captures is shared read-only (reference counts change atomically), and it cannot reach this thread's Refs or handles (a captured handle is fault 14 there). When the task that started it ends, it cancels still-running work at its next suspension point and waits for the thread to finish.

cores

gizlang
function cores(): i64

The CPUs this process may use (its affinity): the size of parallel's pool and of GIZLANG_WORKERS=auto. This observes affinity, CPU admission and the environment; it cannot certify an environment-independent cached test.

Effects: read.

stackLeft

gizlang
function stackLeft(): i64

The bytes of stack the current task has left before a cyclic function faults with R009. A recursion whose depth its input sets (a SQL function that calls itself) checks it and returns an error instead of faulting.

Effects: read.

stackTop

gizlang
function stackTop(): u64

The address just above the running task's stack, or 0 on its thread's own stack: a collector that scans its callers' frames conservatively (gizvm) reads up to it.

Effects: read.

cancel

gizlang
function cancel<T>(task: Task<T>): bool

Ask the task to stop: fault 12 at its next wait, so code that never waits, such as a loop of arithmetic, runs on. False when it already finished or was cancelled.

sleep

gizlang
function sleep(milliseconds: i64): bool

Wait milliseconds ms; true when the wait was not cut short.

Effects: fail, io.

pause

gizlang
function pause(): bool

Let every other ready task run once.

Effects: fail, io.

now

gizlang
function now(): i64

Milliseconds on a monotonic clock.

Effects: fail, io.

nanos

gizlang
function nanos(): i64

Nanoseconds on a monotonic clock: what JavaScript's performance.now and process.hrtime read (not replayed).

Effects: read.

claimLease

gizlang
function claimLease(previous: Lease, now: i64, duration: i64): Lease throws string

The lease after PREVIOUS: the next generation, held from NOW for DURATION and never ending before PREVIOUS ends. It fails when a number is out of range.

Effects: fail.

retry

gizlang
function retry<F>(work: F, attempts: i64): ReturnType<F> throws Fault

Run work in a fresh task until it succeeds, at most attempts times; the last fault when every attempt failed.

within

gizlang
function within<F>(milliseconds: i64, work: F): ReturnType<F> throws Fault

Run work in a fresh task for at most milliseconds; a task still running then is cancelled, so the result is fault 12 once it waits (cancel): a loop that never waits is not stopped, and this does not return until it ends.

channel

gizlang
function channel<T>(capacity: i64): Channel<T>

A channel holding at most capacity (at least 1) values.

crossing

gizlang
function crossing<T>(capacity: i64): Channel<T>

A channel between threads: the tasks of any thread (thread) may send, receive and close while the task that made it lives. A value sent is marked shared (counts change atomically), so the receiver may keep or change it (a write copies a buffer someone else still holds); values may not hold Refs. A task waiting on it is never reported as deadlocked: another thread may still send or close.

send

gizlang
function send<T>(queue: Channel<T>, value: T): bool

Move value in, waiting while the channel is full; false (and the value is dropped) when it is closed.

trySend

gizlang
function trySend<T>(queue: Channel<T>, value: T): bool

Move value in without waiting. False drops this value when the channel is full or closed; it never enqueues a value after returning false.

respond

gizlang
function respond<T>(queue: Channel<T>, value: T): bool

Complete an asynchronous response without waiting. False drops the value when the recipient is full, closed or its owning task has ended. Expired recipients grant no access to a reused channel slot. Ordinary sends retain their stale-handle fault contract.

receive

gizlang
function receive<T>(queue: Channel<T>): Option<T>

The oldest value, waiting for one; none once the channel is closed and empty.

tryReceive

gizlang
function tryReceive<T>(queue: Channel<T>): Option<T>

The oldest value without waiting; none when nothing is queued (or the channel is closed and empty). A Ready set that watches a crossing channel (Ready.channel) drains it with this.

handOff

gizlang
function handOff<T>(queue: Channel<T>, value: mut T): bool

Move VALUE in, waiting while the channel is full, and leave VALUE empty (zero) for the caller to assign again before reading it: a whole record (the JS Heap at each coroutine switch) moves without the copy send makes of a value it borrows. False (and the value dropped) when the channel is closed.

receiveMoved

gizlang
function receiveMoved<T>(queue: Channel<T>): T

The oldest value, waiting for one, moved out to the caller (matching receive's Option copies it); a fault once the channel is closed and empty.

exchange

gizlang
function exchange<T>(to: Channel<T>, back: Channel<T>, value: mut T): bool

handOff of VALUE to TO, then receiveMoved from BACK into VALUE: each a copy of its bytes, where the two calls copy a large record field by field several times (4 µs of each JS await for the 4 KB Heap, 10/17). Faults once BACK is closed and empty. False when TO was closed (VALUE was dropped).

close

gizlang
function close<T>(queue: Channel<T>): bool

No more sends; receivers drain what is queued. False when already closed.

cell

gizlang
function cell<T>(initial: T): Cell<T>

A cell holding initial, for the tasks of any thread while the task that made it lives. As on a crossing channel, values are marked shared and may not hold Refs; a writer's store of a new snapshot never waits for readers.

newQueue

gizlang
function newQueue<T>(): Queue<T>

An empty queue between tasks.

atomic

gizlang
function atomic(initial: i64): Atomic

A new atomic holding initial.

Effects: io.

atomicGlobal

gizlang
function atomicGlobal(name: string): Atomic

The one atomic of name in this process, made with value 0 on first use (thread-safe): every thread gets the same cell. The lookup takes a lock, so keep the result and do not call global on a hot path.

Effects: io.

words

gizlang
function words(count: i64): Words

COUNT new words, each 0.

Effects: fail, io.

zip

gizlang
function zip<F, G>(first: F, second: G): Pair<ReturnType<F>, ReturnType<G>> throws Fault

Start both jobs together and join both, even when one fails. A task fault stays a Fault; a Result returned by a job stays a Result.

Methods

Thread.join

gizlang
function Thread.join<T>(self: Thread<T>): T throws Fault

The thread's value, or the fault that ended it; waiting suspends only the calling task. A value is taken once.

Cell.load

gizlang
function Cell.load<T>(self: Cell<T>): T

The latest value stored (a shared copy: changing it copies what it changes).

Cell.store

gizlang
function Cell.store<T>(self: Cell<T>, value: T): bool

Replace the value; readers that loaded the old one keep it. Like loading, it faults once the task that made the cell has ended.

Cell.close

gizlang
function Cell.close<T>(self: Cell<T>): bool

Drops the value now, not when the task that made the cell ends: a long task that makes cells for data it later retires (a cold segment's columns) gives their memory back. A load or store after it faults. False when already closed.

Queue.push

gizlang
function Queue.push<T>(self: Queue<T>, value: T): bool

VALUE at the end; false (dropped) once the queue is closed.

Queue.take

gizlang
function Queue.take<T>(self: Queue<T>): Vec<T>

Everything queued, oldest first; the queue is empty after.

Queue.putBack

gizlang
function Queue.putBack<T>(self: Queue<T>, values: Vec<T>): void

VALUES back in front, in their order (a reader that took more than it used).

Queue.shut

gizlang
function Queue.shut<T>(self: Queue<T>): Vec<T>

No more pushes: what is queued, which nobody takes after this.

Atomic.load

gizlang
function Atomic.load(self: Atomic, order: LoadOrder = "seqCst"): i64

The current value.

Effects: io.

Atomic.store

gizlang
function Atomic.store(self: Atomic, value: i64, order: StoreOrder = "seqCst"): bool

Replace the value. Always true.

Effects: io.

Atomic.fetchAdd

gizlang
function Atomic.fetchAdd(self: Atomic, delta: i64, order: Order = "seqCst"): i64

Add delta (wrapping on overflow) and return the value from before.

Effects: io.

Atomic.compareExchange

gizlang
function Atomic.compareExchange(self: Atomic, expected: i64, desired: i64, order: Order = "seqCst"): bool

A failed exchange is a load with ORDER's acquire part.

Effects: io.

Words.count

gizlang
function Words.count(self: Words): i64

How many words there are.

Words.load

gizlang
function Words.load(self: Words, i: i64): i64

The value of word I.

Effects: fail, io.

Words.store

gizlang
function Words.store(self: Words, i: i64, value: i64): bool

Sets word I to VALUE. Always true.

Effects: fail, io.

Words.compareExchange

gizlang
function Words.compareExchange(self: Words, i: i64, expected: i64, desired: i64): bool

Sets word I to DESIRED only when it holds EXPECTED; true when it did.

Effects: fail, io.