Modules

Io

moduleextensionexperimentalSource

Available on macOS, Linux, Windows

#import "Extensions/Io";

Fibers and non-blocking IO. io_run starts a scheduler on the calling thread; inside it spawn starts fibers, which wait for sleeps, channels, sockets and files without blocking the thread. make_channel, Wait_Group and Fiber_Mutex coordinate fibers, tcp_listen, accept and connect do TCP, and Reader and Writer give files, sockets and pipes one shape. A Line_Reader reads any of them a line at a time.

Example↗
Io :: #import "Extensions/Io";

results: *Io.Channel(int); // a fiber's procedure cannot capture locals: share through a global or pass data

main :: () {
    Io.io_run(() { // the scheduler runs on this thread
        results = Io.make_channel(int, 4);
        defer Io.free_channel(results);

        for 1..4 Io.spawn((n: int) { Io.sleep(10 * n); Io.send(results, n); }, it);

        total := 0;

        for 1..4 {
            value, err := Io.recv(results);
            assert(err == .NONE);
            total += value;
        }

        assert(total == 10);
    });
}

A fiber is a lightweight thread that the program switches itself. It has its own small stack and runs until it blocks (sleep, yield, a channel, a Wait_Group, a Fiber_Mutex), when the scheduler runs another one. All fibers of an io_run share one OS thread, so they never run at the same time and need no locks to share data. A blocking call returns .CANCELED once cancel has been called for its fiber.

WebAssembly cannot switch stacks, so importing this module for wasm is a compile error.

Module parameters#

STACK_BYTES := 256 * 1024
The stack of each fiber, in bytes, including its guard page. Address space only: the OS commits pages as the fiber touches them (Windows commits it all up front).
POOL_LIMIT := 1024
How many finished fibers keep their stack for reuse.
TEMPORARY_BYTES := 2048
The temporary storage each fiber gets, in bytes; 0 gives none.

Procedures#

Scheduler#

io_runexperimental2 overloadsSource
(proc: $P)
(proc: $P, data: $T)

Runs proc as the first fiber of a new scheduler on this thread and returns when it has returned. Fibers it started that are still alive then are canceled, and run until they end. Everything that waits (sleep, accept, read, channels) runs inside it, and only the calling fiber waits, never the thread. The scheduler's memory is released when the call returns; the caller keeps what proc allocated. Do not call it from inside another io_run on the same thread.

Io :: #import "Extensions/Io";

done: bool;

main :: () {
    Io.io_run(() {
        Io.sleep(1);
        done = true;
    });

    assert(done);
}
↗
spawnexperimental2 overloadsSource
(proc: $P) -> Fiber_Handle
(proc: $P, data: $T) -> Fiber_Handle

Starts proc as a new fiber on the current scheduler and returns its handle. It first runs when the calling fiber blocks or yields, and not at all if the scheduler ends first. The new fiber starts with a copy of the caller's context and temporary storage of its own. Fibers share one OS thread, so they can use shared data without locks as long as they do not block in the middle of updating it.

Io :: #import "Extensions/Io";

main :: () {
    Io.io_run(() {
        results := Io.make_channel(int, 1);
        defer Io.free_channel(results);

        Io.spawn((out: *Io.Channel(int)) { Io.send(out, 42); }, results);

        value, error := Io.recv(results); // waits for the spawned fiber
        assert(error == .NONE && value == 42);
    });
}
↗
yieldexperimentalSource
() -> Io_Error

Lets the other ready fibers run first. .CANCELED if this fiber was canceled.

Io :: #import "Extensions/Io";

Io.io_run(() {
    log: [..] int;
    Io.spawn((out: *[..] int) { array_add(out, 2); }, *log);
    array_add(*log, 1);
    Io.yield();            // the spawned fiber runs now
    array_add(*log, 3);
    assert(log[0] == 1 && log[1] == 2 && log[2] == 3);
});
↗
sleepexperimentalSource
(milliseconds: s64) -> Io_Error

Blocks this fiber, not the thread, for at least milliseconds. Ends early with .TIMED_OUT when a with_deadline limit comes first.

Io :: #import "Extensions/Io";

Io.io_run(() {
    error := Io.sleep(5);
    assert(error == .NONE);
});
↗
cancelexperimentalSource
(handle: Fiber_Handle)

Makes the blocking calls of fiber return .CANCELED, now and from now on. The call it is blocked in, if any, returns at once. Does nothing for a fiber that has ended.

Io :: #import "Extensions/Io";

Io.io_run(() {
    sleeper := Io.spawn(() {
        error := Io.sleep(60_000);
        assert(error == .CANCELED);
    });

    Io.yield();             // let it start sleeping
    Io.cancel(sleeper);
});
↗
is_canceledexperimentalSource
() -> bool

Whether the running fiber was canceled, for loops that do not block.

Io :: #import "Extensions/Io";

Io.io_run(() {
    assert(!Io.is_canceled());
});
↗
release_unused_stackexperimentalsince 0.30.0Source
()

Gives the memory of the running fiber's stack that no frame uses any more back to the system: the pages below the current frames that earlier, deeper calls touched. Call it before a long wait (an idle connection) so those pages do not stay resident; it costs a system call, so not in a hot path. The stack's contents below the caller are lost, which nothing can rely on anyway. Does nothing outside a fiber or on a platform without a way to discard pages.

Sync#

make_channelexperimentalSource
($T: Type, capacity := 0) -> *Channel(T)

A channel that holds up to capacity values, made with context.allocator.

Io :: #import "Extensions/Io";

Io.io_run(() {
    channel := Io.make_channel(int, 2);
    defer Io.free_channel(channel);

    Io.send(channel, 1);
    value, error := Io.recv(channel);
    assert(error == .NONE && value == 1);
});
↗
sendexperimentalSource
(channel: *Channel($T), value: T) -> Io_Error

Puts value into the channel, blocking while it is full. .CLOSED if the channel is (or becomes) closed, .CANCELED if the fiber is canceled while it waits.

Io :: #import "Extensions/Io";

Io.io_run(() {
    channel := Io.make_channel(string, 1);
    defer Io.free_channel(channel);

    assert(Io.send(channel, "first") == .NONE);

    Io.close(channel);
    assert(Io.send(channel, "second") == .CLOSED);
});
↗
try_sendexperimentalSource
(channel: *Channel($T), value: T) -> bool

A send that gives up instead of blocking: whether the value was taken.

Io :: #import "Extensions/Io";

Io.io_run(() {
    channel := Io.make_channel(int, 1);
    defer Io.free_channel(channel);

    assert(Io.try_send(channel, 1));
    assert(!Io.try_send(channel, 2)); // full
});
↗
recvexperimentalSource
(channel: *Channel($T)) -> (T, Io_Error)

Takes the oldest value, blocking while the channel is empty. .CLOSED once it is closed and drained, .CANCELED if the fiber is canceled while it waits.

Io :: #import "Extensions/Io";

Io.io_run(() {
    channel := Io.make_channel(int, 1);
    defer Io.free_channel(channel);

    Io.spawn((c: *Io.Channel(int)) { Io.send(c, 7); }, channel);

    value, error := Io.recv(channel); // waits for the spawned fiber
    assert(error == .NONE && value == 7);
});
↗
try_recvexperimentalSource
(channel: *Channel($T)) -> (T, bool)

A recv that gives up instead of blocking: the value and whether there was one.

Io :: #import "Extensions/Io";

Io.io_run(() {
    channel := Io.make_channel(int, 1);
    defer Io.free_channel(channel);

    _, found := Io.try_recv(channel);
    assert(!found);

    Io.send(channel, 5);
    value, found2 := Io.try_recv(channel);
    assert(found2 && value == 5);
});
↗
closeexperimental7 overloadsSource
(channel: *Channel($T))
(stream: Tcp_Stream, now := false) -> Io_Error
(listener: Tcp_Listener, now := false) -> Io_Error
(stream: *Pipe_Stream, now := false) -> Io_Error
(file: *Io_File, now := false) -> Io_Error
(reader: Reader, now := false) -> Io_Error
(writer: Writer, now := false) -> Io_Error

No more values will be sent. Fibers waiting to send get .CLOSED; receivers get what is buffered, then .CLOSED.

Io :: #import "Extensions/Io";

Io.io_run(() {
    channel := Io.make_channel(int, 2);
    defer Io.free_channel(channel);

    Io.send(channel, 1);
    Io.close(channel);

    value, error := Io.recv(channel);  // what was buffered still arrives
    assert(error == .NONE && value == 1);

    _, error = Io.recv(channel);
    assert(error == .CLOSED);
});
↗
lockexperimentalSource
(mutex: *Fiber_Mutex) -> Io_Error

Takes the lock, waiting for whoever holds it. Pair it with unlock.

Io :: #import "Extensions/Io";

Io.io_run(() {
    mutex: Io.Fiber_Mutex;

    assert(Io.lock(*mutex) == .NONE);
    Io.sleep(1);               // safe to wait while holding it
    Io.unlock(*mutex);
});
↗
try_lockexperimentalSource
(mutex: *Fiber_Mutex) -> bool

Takes the lock if it is free, without waiting; returns whether it did.

Io :: #import "Extensions/Io";

mutex: Io.Fiber_Mutex;
assert(Io.try_lock(*mutex));
assert(!Io.try_lock(*mutex));
Io.unlock(*mutex);
↗
unlockexperimentalSource
(mutex: *Fiber_Mutex)

Releases the lock, handing it to the fiber that has waited longest.

notify_oneexperimentalsince 0.27.0Source
(condition: *Condition)

Wakes the fiber that has waited longest, if any.

Io :: #import "Extensions/Io";

Io.io_run(() {
    changed: Io.Condition;

    Io.spawn((changed: *Io.Condition) { Io.notify_one(changed); }, *changed);

    assert(Io.wait(*changed) == .NONE); // the spawned fiber notifies
});
↗

Net#

address_v4experimentalSource
(a: u8, b: u8, c: u8, d: u8, port: u16) -> Address

An IPv4 address from its four octets, a.b.c.d, and a port in host order. address_to_string turns it back into text.

Io :: #import "Extensions/Io";

address := address_v4(127, 0, 0, 1, 8080);

assert(address.family == .IPV4);
assert(address.bytes[0] == 127 && address.bytes[3] == 1);
assert(address_to_string(address) == "127.0.0.1:8080");
↗
(text: string) -> (Address, bool)

Reads an address literal: 1.2.3.4:80, :8080 (any IPv4 address), [::1]:80, 1.2.3.4, ::1. It does not look names up; resolve does.

Io :: #import "Extensions/Io";

address, ok := Io.parse_address("[::1]:8080");
assert(ok && address.family == .IPV6 && address.port == 8080);
↗
() -> s32

The system's error number behind the last failed IO call of the running fiber.

tcp_listenexperimental2 overloadsSource
(text: string, backlog := 1024, reuse_port := false) -> (Tcp_Listener, Io_Error)
(address: Address, backlog := 1024, reuse_port := false) -> (Tcp_Listener, Io_Error)

Listens on address (a literal such as ":8080", port 0 picks a free one; see local_address). reuse_port lets several listeners, one per thread, share the port; the kernel then spreads the connections between them.

Io :: #import "Extensions/Io";

main :: () {
    Io.io_run(() {
        listener, error := Io.tcp_listen("127.0.0.1:0");
        assert(error == .NONE);
        defer Io.close(listener);

        address, _ := Io.local_address(listener);
        assert(address.port != 0); // the system picked a free port

        _, bad := Io.tcp_listen("not an address");
        assert(bad == .INVALID);
    });
}
↗
acceptexperimentalSource
(listener: Tcp_Listener) -> (Tcp_Stream, Io_Error)

Waits for the next connection.

Io :: #import "Extensions/Io";

Io.io_run(() {
    listener, _ := Io.tcp_listen("127.0.0.1:0");
    defer Io.close(listener);

    address, _ := Io.local_address(listener);
    client, _ := Io.connect(address);
    server, _ := Io.accept(listener);
    defer Io.close(client);
    defer Io.close(server);

    assert(Io.write(client, "hi") == 2);

    buffer: [8] u8;
    count, error := Io.read(server, buffer);
    assert(error == .NONE && string.{ count, buffer.data } == "hi");
});
↗
unix_listenexperimentalsince 0.27.0Source
(path: string, backlog := 128, replace := false) -> (Tcp_Listener, Io_Error)

Listens on the socket file path. The file must not exist yet; with replace an existing file at path is removed first (whatever it is, so use it only for a path you own). The socket file stays after close: remove it when the server is done.

Io :: #import "Extensions/Io";

#import "File";

Io.io_run(() {
    listener, error := Io.unix_listen("/tmp/jacket-io-unix.sock", replace = true);
    assert(error == .NONE);
    Io.close(listener);
});
file_delete("/tmp/jacket-io-unix.sock");

Doesn't run in the browser (only on linux, macos).

connect_unixexperimentalsince 0.27.0Source
(path: string) -> (Tcp_Stream, Io_Error)

Connects to the Unix-domain socket at path.

Io :: #import "Extensions/Io";

#import "File";

Io.io_run(() {
    listener, _ := Io.unix_listen("/tmp/jacket-io-unix2.sock", replace = true);
    defer Io.close(listener);

    client, error := Io.connect_unix("/tmp/jacket-io-unix2.sock");
    assert(error == .NONE);
    Io.close(client);
});
file_delete("/tmp/jacket-io-unix2.sock");

Doesn't run in the browser (only on linux, macos).

peer_addressexperimentalSource
(stream: Tcp_Stream) -> (Address, Io_Error)

The far end of a connected stream. On the accepting side this is the client's address and its ephemeral port; on the connecting side it is the server's.

Io :: #import "Extensions/Io";

main :: () {
    Io.io_run(() {
        listener, _ := Io.tcp_listen("127.0.0.1:0");
        defer Io.close(listener);

        address, _ := Io.local_address(listener);
        client, connect_error := Io.connect(address);
        assert(connect_error == .NONE);
        defer Io.close(client);

        server, accept_error := Io.accept(listener);
        assert(accept_error == .NONE);
        defer Io.close(server);

        far, _ := Io.peer_address(client);
        assert(far.port == address.port); // the client's peer is the listener
        near, _ := Io.local_address(client);
        seen, _ := Io.peer_address(server);
        assert(seen.port == near.port); // the server sees the client's own port
    });
}
↗
readexperimental4 overloadsSource
(stream: Tcp_Stream, buffer: [] u8) -> (s64, Io_Error)
(stream: *Pipe_Stream, buffer: [] u8) -> (s64, Io_Error)
(file: *Io_File, buffer: [] u8) -> (s64, Io_Error)
(reader: Reader, buffer: [] u8) -> (s64, Io_Error)

Waits for data and reads what is there, up to buffer.count bytes. 0 means the peer closed its end. An error comes with 0 bytes.

Io :: #import "Extensions/Io";

Io.io_run(() {
    listener, _ := Io.tcp_listen("127.0.0.1:0");
    defer Io.close(listener);

    address, _ := Io.local_address(listener);
    client, _ := Io.connect(address);
    server, _ := Io.accept(listener);
    defer Io.close(client);
    defer Io.close(server);

    Io.write(client, "hello");

    buffer: [16] u8;
    count, error := Io.read(server, buffer);
    assert(error == .NONE && string.{ count, buffer.data } == "hello");
});
↗
wait_readableexperimental2 overloadsSource
(stream: Tcp_Stream) -> Io_Error
(reader: Reader) -> Io_Error

Waits until read would not wait: bytes have arrived, the peer closed its end, or the connection failed (read then says which). Nothing is consumed and no buffer is needed, so a fiber can wait for a quiet connection and take its memory only when there is something to read. Ends with .TIMED_OUT or .CANCELED like any other wait, and .CLOSED if the socket is closed meanwhile.

Io :: #import "Extensions/Io";

Io.io_run(() {
    listener, _ := Io.tcp_listen("127.0.0.1:0");
    defer Io.close(listener);

    address, _ := Io.local_address(listener);
    client, _ := Io.connect(address);
    server, _ := Io.accept(listener);
    defer Io.close(client);
    defer Io.close(server);

    Io.write(client, "x");

    assert(Io.wait_readable(server) == .NONE); // data is waiting, nothing consumed
    buffer: [4] u8;
    count, _ := Io.read(server, buffer);
    assert(count == 1);
});
↗
read_fullexperimental4 overloadsSource
(stream: Tcp_Stream, buffer: [] u8) -> (s64, Io_Error)
(stream: *Pipe_Stream, buffer: [] u8) -> (s64, Io_Error)
(file: *Io_File, buffer: [] u8) -> (s64, Io_Error)
(reader: Reader, buffer: [] u8) -> (s64, Io_Error)

Reads until buffer is full. .END_OF_FILE if the peer closed first, with the bytes read.

Io :: #import "Extensions/Io";

Io.io_run(() {
    listener, _ := Io.tcp_listen("127.0.0.1:0");
    defer Io.close(listener);

    address, _ := Io.local_address(listener);
    client, _ := Io.connect(address);
    server, _ := Io.accept(listener);
    defer Io.close(client);
    defer Io.close(server);

    Io.write(client, "abcd");

    buffer: [4] u8;
    count, error := Io.read_full(server, buffer);
    assert(error == .NONE && count == 4);
});
↗
writeexperimental8 overloadsSource
(stream: Tcp_Stream, data: [] u8) -> (s64, Io_Error)
(stream: Tcp_Stream, text: string) -> (s64, Io_Error)
(stream: *Pipe_Stream, data: [] u8) -> (s64, Io_Error)
(stream: *Pipe_Stream, text: string) -> (s64, Io_Error)
(file: *Io_File, data: [] u8) -> (s64, Io_Error)
(file: *Io_File, text: string) -> (s64, Io_Error)
(writer: Writer, data: [] u8) -> (s64, Io_Error)
(writer: Writer, text: string) -> (s64, Io_Error)

Sends all of data, waiting as often as it takes. On an error it returns how many bytes went out.

Io :: #import "Extensions/Io";

Io.io_run(() {
    listener, _ := Io.tcp_listen("127.0.0.1:0");
    defer Io.close(listener);

    address, _ := Io.local_address(listener);
    client, _ := Io.connect(address);
    server, _ := Io.accept(listener);
    defer Io.close(client);
    defer Io.close(server);

    count, error := Io.write(client, u8.[1, 2, 3]);
    assert(error == .NONE && count == 3);
});
↗
shutdown_writeexperimental2 overloadsSource
(stream: Tcp_Stream) -> Io_Error
(stream: *Pipe_Stream) -> Io_Error

Closes the sending side; the peer reads the end of the stream once it has what was sent.

Io :: #import "Extensions/Io";

Io.io_run(() {
    listener, _ := Io.tcp_listen("127.0.0.1:0");
    defer Io.close(listener);

    address, _ := Io.local_address(listener);
    client, _ := Io.connect(address);
    server, _ := Io.accept(listener);
    defer Io.close(client);
    defer Io.close(server);

    Io.write(client, "done");
    Io.shutdown_write(client);

    data, _ := Io.read_all(Io.reader_of(server));
    assert(cast(string) data == "done");
    free(data.data);
});
↗
resolveexperimentalSource
(host: string, port: u16) -> (Address, Io_Error)

Looks host up (an address literal, or a name for the system's resolver, which runs on a helper thread so only this fiber waits) and gives its first address, IPv4 preferred.

Io :: #import "Extensions/Io";

Io.io_run(() {
    address, error := Io.resolve("127.0.0.1", 8080);
    assert(error == .NONE && address.port == 8080);

    host, _ := Io.resolve("localhost", 80);
    assert(host.family != .NONE);
});
↗

Pipe#

make_pipeexperimentalSource

Two connected ends. What one writes the other reads. close both when done (they free themselves once both are closed).

Io :: #import "Extensions/Io";

Io.io_run(() {
    client, server := Io.make_pipe();
    defer Io.close(client);
    defer Io.close(server);

    Io.write(client, "ping");

    buffer: [8] u8;
    count, error := Io.read(server, buffer);
    assert(error == .NONE && string.{ count, buffer.data } == "ping");
});
↗

Files#

open_fileexperimentalSource
(path: string, mode := File_Mode.READ) -> (Io_File, Io_Error)

Opens path. Close the file with close.

Io :: #import "Extensions/Io";
#import "File";

Io.io_run(() {
    file, error := Io.open_file("/tmp/jacket-io-open.txt", .WRITE);
    assert(error == .NONE);
    Io.write(*file, "hello");
    Io.close(*file);

    _, error = Io.open_file("/tmp/jacket-io-missing/none.txt");
    assert(error == .NOT_FOUND);
});
file_delete("/tmp/jacket-io-open.txt");

Doesn't run in the browser (only on linux, macos).

read_atexperimentalSource
(file: *Io_File, buffer: [] u8, offset: s64) -> (s64, Io_Error)

Reads at offset; the file's position does not move.

Io :: #import "Extensions/Io";
#import "File";

Io.io_run(() {
    Io.write_file("/tmp/jacket-io-at.txt", "0123456789");

    file, _ := Io.open_file("/tmp/jacket-io-at.txt");
    defer Io.close(*file);

    buffer: [3] u8;
    Io.read_at(*file, buffer, 4);
    assert(string.{ 3, buffer.data } == "456");
});
file_delete("/tmp/jacket-io-at.txt");

Doesn't run in the browser (only on linux, macos).

write_atexperimental2 overloadsSource
(file: *Io_File, data: [] u8, offset: s64) -> (s64, Io_Error)
(file: *Io_File, text: string, offset: s64) -> (s64, Io_Error)

Writes all of data at offset; the file's position does not move.

Io :: #import "Extensions/Io";
#import "File";

Io.io_run(() {
    file, _ := Io.open_file("/tmp/jacket-io-write-at.txt", .READ_WRITE);
    defer Io.close(*file);

    Io.write_at(*file, "abcdef", 0);
    Io.write_at(*file, "XY", 2);

    data, _ := Io.read_file("/tmp/jacket-io-write-at.txt");
    assert(cast(string) data == "abXYef");
    free(data.data);
});
file_delete("/tmp/jacket-io-write-at.txt");

Doesn't run in the browser (only on linux, macos).

file_sizeexperimentalSource
(file: *Io_File) -> (s64, Io_Error)

The size of the file in bytes.

Io :: #import "Extensions/Io";
#import "File";

Io.io_run(() {
    Io.write_file("/tmp/jacket-io-size.txt", "12345");

    file, _ := Io.open_file("/tmp/jacket-io-size.txt");
    defer Io.close(*file);

    size, error := Io.file_size(*file);
    assert(error == .NONE && size == 5);
});
file_delete("/tmp/jacket-io-size.txt");

Doesn't run in the browser (only on linux, macos).

read_fileexperimentalSource
(path: string) -> ([] u8, Io_Error)

The whole content of a file, in memory from context.allocator (free it with free).

Io :: #import "Extensions/Io";
#import "File";

Io.io_run(() {
    Io.write_file("/tmp/jacket-io-whole.txt", "contents");

    data, error := Io.read_file("/tmp/jacket-io-whole.txt");
    assert(error == .NONE && cast(string) data == "contents");
    free(data.data);
});
file_delete("/tmp/jacket-io-whole.txt");

Doesn't run in the browser (only on linux, macos).

write_fileexperimental2 overloadsSource
(path: string, data: [] u8) -> Io_Error
(path: string, text: string) -> Io_Error

Creates or replaces a file with data.

Io :: #import "Extensions/Io";
#import "File";

Io.io_run(() {
    assert(Io.write_file("/tmp/jacket-io-wf.txt", "hello") == .NONE);
});
file_delete("/tmp/jacket-io-wf.txt");

Doesn't run in the browser (only on linux, macos).

Stream#

(reader: Reader) -> bool

Whether wait_readable works on reader, which is when its source supplies a wait_proc (a TCP stream does; a file or a pipe end does not). When it is false, wait_readable returns .UNSUPPORTED.

Io :: #import "Extensions/Io";

main :: () {
    Io.io_run(() {
        near, far := Io.make_pipe();
        defer Io.close(near);
        defer Io.close(far);

        assert(!Io.can_wait_readable(Io.reader_of(near)));
    });
}
↗
read_allexperimentalSource
(reader: Reader, size_hint := 0) -> ([] u8, Io_Error)

Reads to the end of the source into memory from context.allocator (free it with free). size_hint is the expected length, when it is known. On any error the bytes read so far are freed.

Io :: #import "Extensions/Io";

Io.io_run(() {
    near, far := Io.make_pipe();
    defer Io.close(near);
    defer Io.close(far);
    Io.write(Io.writer_of(far), "everything");
    Io.shutdown_write(far);

    data, error := Io.read_all(Io.reader_of(near));
    assert(error == .NONE && cast(string) data == "everything");
    free(data.data);
});
↗
reader_ofexperimental3 overloadsSource
(file: *Io_File) -> Reader
(stream: Tcp_Stream) -> Reader
(stream: *Pipe_Stream) -> Reader

The file as a Reader. Reading advances the file's position. The reader points at the Io_File, which must outlive it.

Io :: #import "Extensions/Io";
#import "File";

path := "/tmp/jacket-io-reader-of.txt";
Io.io_run(() {
    Io.write_file("/tmp/jacket-io-reader-of.txt", "hello");

    file, _ := Io.open_file("/tmp/jacket-io-reader-of.txt");
    defer Io.close(*file);

    data, error := Io.read_all(Io.reader_of(*file));
    assert(error == .NONE && cast(string) data == "hello");
    free(data.data);
});
file_delete(path);

Doesn't run in the browser (only on linux, macos).

writer_ofexperimental3 overloadsSource
(file: *Io_File) -> Writer
(stream: Tcp_Stream) -> Writer
(stream: *Pipe_Stream) -> Writer

The file as a Writer. Writing advances the file's position. The writer points at the Io_File, which must outlive it.

Io :: #import "Extensions/Io";
#import "File";

path := "/tmp/jacket-io-writer-of.txt";
Io.io_run(() {
    file, _ := Io.open_file("/tmp/jacket-io-writer-of.txt", .WRITE);
    defer Io.close(*file);

    count, error := Io.write(Io.writer_of(*file), "hello");
    assert(error == .NONE && count == 5);
});
file_delete(path);

Doesn't run in the browser (only on linux, macos).

Lines#

(source: Reader, limit := DEFAULT_LINE_LIMIT) -> Line_Reader

A line reader over source that accepts lines of up to limit bytes (not counting the line ending). The limit stops a peer that never sends a newline from filling memory.

Io :: #import "Extensions/Io";

Io.io_run(() {
    near, far := Io.make_pipe();
    defer Io.close(near);
    defer Io.close(far);
    Io.write(Io.writer_of(far), "one\r\ntwo\n");
    Io.shutdown_write(far);

    lines := Io.make_line_reader(Io.reader_of(near), limit = 1024);
    defer Io.free_line_reader(*lines);

    line, error := Io.read_line(*lines);
    assert(error == .NONE && line == "one");
});
↗
read_lineexperimentalSource
(reader: *Line_Reader) -> (string, Io_Error)

The next line without its line ending (\n or \r\n), or the error that ended the reading: .END_OF_FILE once the source has ended and every line was handed out, .INVALID for a line longer than the limit, or whatever the source's read reported. After an error, stop reading and free the reader.

Io :: #import "Extensions/Io";

Io.io_run(() {
    near, far := Io.make_pipe();
    defer Io.close(near);
    defer Io.close(far);
    Io.write(Io.writer_of(far), "GET a\nGET b\nlast");
    Io.shutdown_write(far);

    lines := Io.make_line_reader(Io.reader_of(near));
    defer Io.free_line_reader(*lines);

    whole := 0;

    while true {
        line, error := Io.read_line(*lines);
        if error != .NONE break;
        if lines.unterminated break; // "last" was cut off: do not act on it
        whole += 1;
    }

    assert(whole == 2);
});
↗

JSON#

read_jsonexperimentalSource
(reader: Reader, $T: Type, options := Json.Json_Options.{}) -> (T, Json.Json_Error)

Reads a document to the end of reader and decodes it as a T: the same value or error as from_json(T, text) for the same bytes, however they arrive (a pipe written in small pieces, a socket). The pieces are gathered in one buffer that is freed before this returns. A reader that fails gives a .READ error naming the Io_Error; a document over options.max_size is a .LIMIT error and the rest of the stream is left unread. The reader is not closed.

Io :: #import "Extensions/Io";
#import "Extensions/Json";

Point :: struct { x: int; y: int; }

Io.io_run(() {
    near, far := Io.make_pipe();
    defer Io.close(near);
    defer Io.close(far);

    Io.write(Io.writer_of(far), "{\"x\": 1, \"y\": 2}");
    Io.shutdown_write(far);

    point, error := Io.read_json(Io.reader_of(near), Point);
    assert(error.kind == .NONE && point.x == 1 && point.y == 2);
});
↗

Runtime#

(milliseconds: s64, body: Code)

The one way to bound a wait. Limits every IO call (and sleep) in body to milliseconds from now: one that is still waiting then returns .TIMED_OUT, and so does a call made after the limit, even when data is ready. 0 is a limit that has already passed. Deadlines nest; the earlier one wins, and the previous deadline is back when body ends. A negative milliseconds adds no limit (NO_DEADLINE), so a timeout setting that may be off needs no second code path. Channel and lock operations are not IO and ignore it.

Io :: #import "Extensions/Io";

main :: () {
    Io.io_run(() {
        client, server := Io.make_pipe();
        buffer: [8] u8;
        error: Io.Io_Error;

        Io.with_deadline(20, #code {
            _, error = Io.read(client, buffer);       // nothing is ever sent
        });

        assert(error == .TIMED_OUT);
    });
}
↗
with_graceexperimentalsince 0.27.0Source
(milliseconds: s64, body: Code)

Runs body clear of the running fiber's deadline and cancel, for cleanup that has to happen after one of them fired: telling a server to stop a query, a goodbye message. body has its own limit of milliseconds (negative: none), and with_deadline inside it nests as usual. When body ends, the outer deadline is back, and the fiber is canceled if it was before or a cancel came during body.

Io :: #import "Extensions/Io";

main :: () {
    Io.io_run(() {
        client, server := Io.make_pipe();
        buffer: [8] u8;
        error: Io.Io_Error;
        sent: s64;

        Io.with_deadline(0, #code {
            _, error = Io.read(client, buffer);                // .TIMED_OUT: the deadline has passed
            Io.with_grace(1000, #code { sent, _ = Io.write(client, "stop"); });
        });

        assert(error == .TIMED_OUT);
        assert(sent == 4);
    });
}
↗
backendexperimentalSource
() -> Backend

The backend the running scheduler uses: .IO_URING, .EPOLL, .KQUEUE, .IOCP, .IORING, or .NONE where IO is not implemented. Set JAI_IO_BACKEND to io_uring, epoll, kqueue, iocp or ioring before io_run to ask for one; a backend the system cannot offer is replaced (see backend_reason).

Io :: #import "Extensions/Io";


Io.io_run(() {
    assert(Io.backend() != .NONE);
    assert(Io.backend_name().count > 0);
});
↗
backend_nameexperimentalSource
() -> string

backend() as text: "io_uring", "epoll", "kqueue", "iocp", "ioring" or "none".

() -> string

Why the running scheduler does not use the backend that was asked for or is the first choice on this system (for example "io_uring is disabled here"). Empty when it does.

io_run_threadsexperimental2 overloadsSource
(count: s64, proc: $P)
(count: s64, proc: $P, data: $T)

Starts count schedulers, each on a thread of its own, runs proc as the first fiber of every one, and returns when all have finished. Fibers never move between them. With reuse_port listeners (tcp_listen(..., reuse_port = true)) each thread accepts its own connections.

Io :: #import "Extensions/Io";
Atomics :: #import "Atomics";

started: s32;

main :: () {
    Io.io_run_threads(2, () {
        Atomics.atomic_add(*started, 1);
    });

    assert(started == 2);
}
↗
(key: *void) -> *void

The value a library stored for key on the running scheduler, or null. Keys are addresses of something the library owns (a global works). This is the per-thread variable the language does not have: the Http client keeps its connection pool here.

Io :: #import "Extensions/Io";


Io.io_run(() {
    counter: int;
    Io.set_scheduler_local(*counter, *counter, null);
    assert(Io.scheduler_local(*counter) == *counter);
});
↗
(key: *void, value: *void, drop: Local_Drop)

Stores value for key on the running scheduler; drop (if not null) gets it when the scheduler ends. Replaces an earlier value for the key without dropping it.

Interrupt#

()

Makes SIGINT and SIGTERM set the flag interrupted reads, instead of ending the process. The handler is installed once for the whole program; calling this again does nothing.

()

Sets the flag by hand, as a signal would (tests and programs that stop themselves).

Stacks#

page_bytesexperimental2 overloadssince 0.31.0Source
() -> s64
() -> s64

Windows pages are 4 KiB on x64 and arm64 alike.

map_stackexperimental2 overloadssince 0.31.0Source
(size: s64, guard: s64) -> *void
(size: s64, guard: s64) -> *void
unmap_stackexperimental2 overloadssince 0.31.0Source
(base: *void, size: s64)
(base: *void, size: s64)
discard_pagesexperimental2 overloadssince 0.31.0Source
(address: *void, size: s64)
(address: *void, size: s64)
stack_allocateexperimentalsince 0.31.0Source
(bytes: s64) -> Stack

A stack of at least bytes bytes (rounded up to whole pages), or a zero base.

stack_discard_belowexperimentalsince 0.31.0Source
(stack: Stack, frame: *void)

Gives the memory of the stack's whole pages that lie DISCARD_MARGIN bytes or more below frame back to the system. The pages stay mapped (and the guard untouched); their next use finds zeros on macOS and Linux and unspecified contents on Windows. frame is an address in the frame of the running procedure: everything below it must be dead, and the margin is left for the calls that do this.

VirtualAllocforeignexperimentalWindows onlySource
(address: *void, size: u64, allocation_type: u32, protect: u32) -> *void
VirtualProtectforeignexperimentalWindows onlySource
(address: *void, size: u64, new_protect: u32, old_protect: *u32) -> s32
VirtualFreeforeignexperimentalWindows onlySource
(address: *void, size: u64, free_type: u32) -> s32
mmapforeignexperimentalmacOS, Linux onlySource
(address: *void, length: u64, protection: s32, flags: s32, descriptor: s32, offset: s64) -> *void
mprotectforeignexperimentalmacOS, Linux onlySource
(address: *void, length: u64, protection: s32) -> s32
munmapforeignexperimentalmacOS, Linux onlySource
(address: *void, length: u64) -> s32
madviseforeignexperimentalmacOS, Linux onlySource
(address: *void, length: u64, advice: s32) -> s32

Overflow#

Platform#

checkedexperimentalmacOS, Linux onlysince 0.31.0Source
(result: s64) -> s64

-errno when a call returned -1, else result.

sys_nonblockingexperimentalmacOS, Linux onlysince 0.31.0Source
(fd: s32) -> s64

Makes fd non-blocking and close-on-exec.

prepare_socketexperimentalmacOS, Linux onlysince 0.31.0Source
(fd: s32, nonblocking: bool) -> s64

Makes fd a socket the engine can use: close-on-exec, no SIGPIPE, and non-blocking when the backend wants readiness (epoll, kqueue). io_uring does its own waiting and wants sockets that block.

sys_socketexperimental2 overloadssince 0.31.0Source
(family: s32, nonblocking: bool) -> s64
(family: s32, nonblocking: bool) -> s64
sys_set_optionexperimental2 overloadssince 0.31.0Source
(fd: s64, level: s32, name: s32, value: s32) -> s64
(fd: s64, level: s32, name: s32, value: s32) -> s64
sys_listenexperimental2 overloadssince 0.31.0Source
(fd: s64, backlog: s32) -> s64
(fd: s64, backlog: s32) -> s64
sys_shutdownexperimental2 overloadssince 0.31.0Source
(fd: s64, how: s32) -> s64
(fd: s64, how: s32) -> s64
sys_socket_errorexperimental2 overloadssince 0.31.0Source
(fd: s64) -> s64
(fd: s64) -> s64

The pending error of a socket (SO_ERROR), as a positive errno or 0.

sys_openexperimental2 overloadssince 0.31.0Source
(path: *u8, flags: s32, mode: u32) -> s64
(path: *u8, flags: s32, mode: u32) -> s64
sys_closeexperimental2 overloadssince 0.31.0Source
(fd: s64) -> s64
(fd: s64) -> s64
sys_pipeexperimental2 overloadssince 0.31.0Source
(fds: *[2] s32, nonblocking: bool) -> s64
(fds: *[2] s32, nonblocking: bool) -> s64

A pipe whose read end is non-blocking when nonblocking (the backend waits for readiness); the write end always blocks, which a one-byte wake-up never does for long.

sys_close_handleexperimental2 overloadssince 0.31.0Source
(handle: Handle) -> s64
(handle: Handle) -> s64

Closes a handle of any kind (sockets and files share one call on POSIX).

sys_prepare_listenerexperimental2 overloadssince 0.31.0Source
(fd: s64, reuse_port: bool) -> s64
(fd: s64, reuse_port: bool) -> s64

The options a listening socket gets before bind: the address may be reused after a restart, and with reuse_port several listeners (one per thread) may share it.

env_textexperimental2 overloadssince 0.31.0Source
(name: *u8, buffer: *[256] u8) -> string
(name: *u8, buffer: *[256] u8) -> string

The value of an environment variable, or "" if it is unset. buffer is scratch space that the Windows version fills.

sys_recvexperimental2 overloadssince 0.31.0Source
(fd: s64, buffer: *void, length: s64) -> s64
(fd: s64, buffer: *void, length: s64) -> s64
sys_peekexperimental2 overloadssince 0.31.0Source
(fd: s64) -> s64
(fd: s64) -> s64

Whether a sys_recv would not wait, without taking anything: 0 if a byte, the end of the stream or an error is there (the receive reports which), -E_AGAIN if not.

sys_sendexperimental2 overloadssince 0.31.0Source
(fd: s64, buffer: *void, length: s64) -> s64
(fd: s64, buffer: *void, length: s64) -> s64
sys_readexperimental2 overloadssince 0.31.0Source
(fd: s64, buffer: *void, length: s64, offset: s64) -> s64
(fd: s64, buffer: *void, length: s64, offset: s64) -> s64

Reads at offset, or at the current position when it is negative.

sys_writeexperimental2 overloadssince 0.31.0Source
(fd: s64, buffer: *void, length: s64, offset: s64) -> s64
(fd: s64, buffer: *void, length: s64, offset: s64) -> s64
c_socketforeignexperimentalmacOS, Linux onlySource
(family: s32, kind: s32, protocol: s32) -> s32

libc is declared in stacks.jai.

c_bindforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: u32) -> s32
c_listenforeignexperimentalmacOS, Linux onlySource
(fd: s32, backlog: s32) -> s32
c_connectforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: u32) -> s32
c_acceptforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: *u32) -> s32
c_getsocknameforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: *u32) -> s32
c_getpeernameforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: *u32) -> s32
c_setsockoptforeignexperimentalmacOS, Linux onlySource
(fd: s32, level: s32, name: s32, value: *void, length: u32) -> s32
c_getsockoptforeignexperimentalmacOS, Linux onlySource
(fd: s32, level: s32, name: s32, value: *void, length: *u32) -> s32
c_shutdownforeignexperimentalmacOS, Linux onlySource
(fd: s32, how: s32) -> s32
c_sendforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64, flags: s32) -> s64
c_recvforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64, flags: s32) -> s64
c_readforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64) -> s64
c_writeforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64) -> s64
c_preadforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64, offset: s64) -> s64
c_pwriteforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64, offset: s64) -> s64
c_openforeignexperimentalmacOS, Linux onlySource
(path: *u8, flags: s32, args: ..Any) -> s32

open and fcntl are variadic in C, which matters on Apple arm64 (variadic arguments go on the stack).

c_closeforeignexperimentalmacOS, Linux onlySource
(fd: s32) -> s32
c_fcntlforeignexperimentalmacOS, Linux onlySource
(fd: s32, command: s32, args: ..Any) -> s32
c_pipeforeignexperimentalmacOS, Linux onlySource
(fds: *s32) -> s32
c_lseekforeignexperimentalmacOS, Linux onlySource
(fd: s32, offset: s64, whence: s32) -> s64
c_errnoforeignexperimental2 overloadsmacOS, Linux onlySource
() -> *s32
() -> *s32
c_accept4foreignexperimentalLinux onlySource
(fd: s32, address: *void, length: *u32, flags: s32) -> s32

Engine#

engine_submitexperimentalsince 0.31.0Source
(scheduler: *Scheduler, op: *Op) -> bool

Starts op. Returns true if it already finished (its result is set, nothing will be reported later), false if the engine will report it through finish_op.

engine_cancelexperimentalsince 0.31.0Source
(scheduler: *Scheduler, op: *Op)

Asks the engine to end op early. The operation still completes, with the system's cancel error as its result, and only then does its fiber wake.

engine_waitexperimentalsince 0.31.0Source
(scheduler: *Scheduler, timeout: s64)

Submits what was queued and waits up to timeout nanoseconds (forever if negative, not at all if 0) for completions, which wake their fibers.

finish_opexperimentalsince 0.31.0Source
(scheduler: *Scheduler, op: *Op, result: s64, flags: u32 = 0, more := false)

The engine reports op as complete.

cancel_opexperimentalsince 0.31.0Source
(fiber: *Fiber, cause: Io_Error)

Cancels the engine operation fiber is blocked in. Its fiber wakes when the operation completes.

await_opexperimentalsince 0.31.0Source
(op: *Op) -> Io_Error

Runs op for the running fiber and blocks it until the operation completes, is canceled, or the fiber's deadline passes. The result stays in op.result.

error_from_resultexperimentalsince 0.31.0Source
(result: s64) -> Io_Error

The error of a plain system call that returned result (negative: -errno), for the running fiber, and remembers the code for platform_code.

internal_completionexperimentalsince 0.31.0Source
(scheduler: *Scheduler, op: *Op)

Completions that belong to the engine itself rather than a fiber: the wake-up pipe resolver threads write to (resolver.jai) and the eventfd of the file pool (files_pool.jai).

c_getenvforeignexperimentalmacOS, Linux onlySource
(name: *u8) -> *u8

Engine poll#

poll_waitexperimentalsince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine, fd: s64, op: *Op) -> bool

Puts the kernel in charge of waking op, which is queued on fd. True if that failed and the operation finished with the error; false if it waits.

poll_wants_writeexperimentalsince 0.31.0Source
(op: *Op) -> bool

Whether op waits for the descriptor to become writable.

poll_attemptexperimentalsince 0.31.0Source
(op: *Op, ready := false) -> bool

One non-blocking try. True: finished, op.result is set. False: it must wait for readiness (op.wants_write says which).

poll_readyexperimentalsince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine, fd: s64, readable: bool, writable: bool)

Serves the operations waiting on fd now that it may be ready, then arms it again if any remain.

poll_failexperimentalsince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine, fd: s64, result: s64, readers: bool, writers: bool)

Ends every operation waiting on fd with result.

poll_closeexperimentalsince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine, fd: s64)

fd is about to be closed: fail what waits on it and forget what the kernel was told.

poll_armexperimentalsince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine, fd: s64) -> s64

Makes sure the kernel will report fd when someone is waiting on it. 0, or the negative error number if the kernel refused (the waiters are left for the caller to fail).

epoll_startexperimental2 overloadssince 0.31.0Source
(engine: *Engine)
(engine: *Engine)

Whether epoll can be had at all; it always can on a Linux this module supports.

epoll_armexperimental2 overloadssince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine, fd: s64) -> s64
(scheduler: *Scheduler, engine: *Engine, fd: s64) -> s64

Registers fd for what its waiters need, once and edge-triggered: the registration stays and only grows (a MOD to add a direction, which also reports the descriptor if it is ready already, so nothing is missed between the failed try and here).

raw_syscallforeignexperimentalLinux onlySource
(number: s64, a1: s64, a2: s64, a3: s64, a4: s64, a5: s64, a6: s64) -> s64

Engine uring#

uring_startexperimental2 overloadsSource
(engine: *Engine) -> string
(engine: *Engine) -> string

Tries to make the ring; returns why it could not, or "" if engine now uses io_uring.

uring_entryexperimentalLinux onlySource
(scheduler: *Scheduler, engine: *Engine) -> *Uring.io_uring_sqe

A free submission entry. With none free, the queued ones go to the kernel first, and completions are reaped so that a full completion queue cannot hold submission back.

uring_closeexperimentalLinux onlySource
(scheduler: *Scheduler, engine: *Engine, op: *Op)

Operations still waiting on a descriptor would keep the file alive past the close, so they are canceled first. The cancel is hard-linked so that finding nothing to cancel does not stop the close.

uring_waitexperimental2 overloadsSource
(scheduler: *Scheduler, engine: *Engine, timeout: s64)
(scheduler: *Scheduler, engine: *Engine, timeout: s64)
uring_acceptexperimentalLinux onlySource
(handle: Handle) -> (bool, s64, Io_Error)

accept through one multishot request per listener instead of one request per connection. Returns false (and does nothing) when this call has to use a one-shot accept instead: another fiber is already waiting on the listener, or the request is being canceled.

uring_listener_closingexperimental2 overloadsSource
(scheduler: *Scheduler, engine: *Engine, fd: s64)
(scheduler: *Scheduler, engine: *Engine, fd: s64)

The listener fd is being closed: sockets accepted ahead of time are closed, and the slot goes when the kernel has ended its request (the close cancels it).

uring_listener_abandonedexperimental2 overloadsSource
(scheduler: *Scheduler, engine: *Engine, fd: s64)
(scheduler: *Scheduler, engine: *Engine, fd: s64)

close(now = true): the descriptor is closed without waiting, so the multishot request has to be canceled by hand or it would keep the socket open.

Engine IOCP#

iocp_postexperimental2 overloadsSource
(port: s64, key: u64)
(port: s64, key: u64)

Wakes the scheduler that owns port from any thread.

iocp_failexperimentalWindows onlySource
(op: *Op, error: s64) -> bool

Finishes op at once with the Win32 error error (negated into the result).

iocp_startedexperimentalWindows onlySource
(op: *Op, succeeded: bool, error: s64) -> bool

After an overlapped call: the packet will come if it succeeded or is pending.

iocp_submitexperimental2 overloadsSource
(scheduler: *Scheduler, engine: *Engine, op: *Op) -> bool
(scheduler: *Scheduler, engine: *Engine, op: *Op) -> bool

Starts op. True: it finished now (the result is set). False: its packet will arrive.

iocp_fileexperimentalWindows onlySource
(op: *Op) -> bool

The file offset goes into the OVERLAPPED. A negative one means the end of the file (for appending handles, which only have FILE_APPEND_DATA access) or the start.

iocp_waitexperimental2 overloadsSource
(scheduler: *Scheduler, engine: *Engine, timeout: s64)
(scheduler: *Scheduler, engine: *Engine, timeout: s64)

Takes packets for up to timeout nanoseconds (forever if negative), finishing operations.

iocp_completeexperimentalWindows onlySource
(scheduler: *Scheduler, engine: *Engine, op: *Op, status: s32, bytes: u32)

The packet of op arrived with this NTSTATUS and byte count.

Engine ioring#

ring_probeexperimentalWindows onlySource
() -> string

Whether this system has a usable IoRing: "" if so, else why not. The answer is kept.

ring_self_testexperimentalWindows onlySource
(api: Ring_Api, ring: s64, event: s64) -> bool

Reads the first two bytes of the running executable through ring and waits for the event, then checks that the ring accepts a write entry.

ring_openexperimentalWindows onlySource
(state: *Iocp_State) -> string

Makes this engine's ring. Returns why it cannot, or "" if files now use it.

ring_submitexperimentalWindows onlySource
(scheduler: *Scheduler, engine: *Engine, op: *Op) -> bool

A file read or write in the ring. True: it failed at once. False: its completion will come.

ring_errorexperimentalWindows onlySource
(result: s32) -> s64

The Win32 error an HRESULT from the ring stands for (0 for success).

ring_drainexperimentalWindows onlySource
(scheduler: *Scheduler, engine: *Engine)

Takes the completions that are in the ring, finishes their operations, and lets waiting ones in.

ring_wait_idleexperimentalWindows onlySource
(scheduler: *Scheduler, engine: *Engine)

Blocks the thread until every operation in the ring has finished, for close of a file.

Files pool#

env_numberexperimentalsince 0.31.0Source
(name: *u8, fallback: s64) -> s64

A non-negative integer from the environment, or fallback.

pool_attachexperimentalmacOS, Linux onlysince 0.31.0Source
(engine: *Engine) -> *Pool_Link

Takes the engine's part in the pool, starting the workers if it is the first. Null if the pool cannot be had (the caller then runs the operation inline).

pool_releaseexperimentalmacOS, Linux onlysince 0.31.0Source
()

One scheduler less; the last one ends the workers.

pool_submitexperimental2 overloadssince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine, op: *Op) -> bool
(scheduler: *Scheduler, engine: *Engine, op: *Op) -> bool

Hands the read or write op to a worker. False if the pool is not available.

pool_workerexperimentalmacOS, Linux onlysince 0.31.0Source
(thread: *Thread.Thread) -> s64

A worker: takes operations off the queue, makes the call, reports to the owner.

pool_collectexperimental2 overloadssince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine)
(scheduler: *Scheduler, engine: *Engine)

Finishes what the workers have completed.

pool_drainexperimental2 overloadssince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine)
(scheduler: *Scheduler, engine: *Engine)

Blocks the thread until every operation handed to the pool has been taken back.

pool_detachexperimental2 overloadssince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine)
(scheduler: *Scheduler, engine: *Engine)

The engine is going away: wait for the workers, release the wake-up, leave the pool.

pool_armexperimental2 overloadsmacOS, Linux onlysince 0.31.0Source
(scheduler: *Scheduler, engine: *Engine, link: *Pool_Link)
(scheduler: *Scheduler, engine: *Engine, link: *Pool_Link)

Keeps a READ on the eventfd outstanding while operations are out.

pool_wake_completedexperimental3 overloadssince 0.31.0Source
(scheduler: *Scheduler, op: *Op)
(scheduler: *Scheduler, op: *Op)
(scheduler: *Scheduler, op: *Op)

The READ on the eventfd completed.

c_eventfdforeignexperimentalLinux onlySource
(count: u32, flags: s32) -> s32

Win32#

win_sockets_startexperimentalWindows onlysince 0.31.0Source
() -> bool

Starts Winsock for this process (reference counted; win_sockets_stop undoes it).

win_extensionexperimentalWindows onlysince 0.31.0Source
(socket: s64, guid: Guid) -> *void

The extension function guid names, for a socket of this provider, or null.

win_status_errorexperimentalWindows onlysince 0.31.0Source
(status: s32) -> s64

The Win32 error behind an NTSTATUS, the way a completion packet reports failures.

windows_errorexperimentalWindows onlysince 0.31.0Source
(code: s64) -> Io_Error

Win32 errors with no E_* constant of their own that mean one of the portable errors.

win_create_portforeignexperimentalWindows onlySource
(file: s64, port: s64, key: u64, threads: u32) -> s64
win_create_fileforeignexperimentalWindows onlySource
(name: *u16, access: u32, share: u32, security: *void, creation: u32, flags: u32, template: s64) -> s64
win_utf8_to_wideforeignexperimentalWindows onlySource
(code_page: u32, flags: u32, text: *u8, bytes: s32, wide: *u16, wide_count: s32) -> s32
win_get_envforeignexperimentalWindows onlySource
(name: *u8, buffer: *u8, size: u32) -> u32
win_create_eventforeignexperimentalWindows onlySource
(security: *void, manual_reset: s32, initial: s32, name: *u16) -> s64
win_wait_for_manyforeignexperimentalWindows onlySource
(count: u32, handles: *s64, wait_all: s32, milliseconds: u32) -> u32
win_socketforeignexperimentalWindows onlySource
(family: s32, kind: s32, protocol: s32, info: *void, group: u32, flags: u32) -> s64
win_bindforeignexperimentalWindows onlySource
(socket: s64, address: *void, length: s32) -> s32
win_listenforeignexperimentalWindows onlySource
(socket: s64, backlog: s32) -> s32
win_socket_nameforeignexperimentalWindows onlySource
(socket: s64, address: *void, length: *s32) -> s32
win_peer_nameforeignexperimentalWindows onlySource
(socket: s64, address: *void, length: *s32) -> s32
win_ioctlforeignexperimentalWindows onlySource
(socket: s64, code: u32, in_buffer: *void, in_size: u32, out_buffer: *void, out_size: u32, returned: *u32, overlapped: *void, completion: *void) -> s32
win_sendforeignexperimentalWindows onlySource
(socket: s64, buffers: *Wsa_Buffer, count: u32, sent: *u32, flags: u32, overlapped: *Overlapped, completion: *void) -> s32

Resolver#

abandon_jobexperimentalsince 0.31.0Source
(fiber: *Fiber)

Stops fiber waiting for its lookup. The job stays: a helper may be running it, and others may wait for it. It is freed when its answer is collected.

wake_growexperimentalsince 0.31.0Source
(state: *Wake_State, wanted: s64)

Starts helpers until there are wanted (at most RESOLVER_MAX_THREADS). A helper that cannot be started is left out; the caller checks helpers_started.

wake_armexperimentalsince 0.31.0Source
(scheduler: *Scheduler)

Makes sure a READ on the pipe is outstanding while lookups are, and takes answers that are already there.

wake_completedexperimentalsince 0.31.0Source
(scheduler: *Scheduler, op: *Op)

The READ on the pipe completed.

wake_collectexperimentalsince 0.31.0Source
(scheduler: *Scheduler)

Takes the finished jobs and wakes their fibers.

wake_stopexperimentalsince 0.31.0Source
(scheduler: *Scheduler)

Waits for the lookups still under way, ends the helpers and closes the pipe.

lookupexperimentalsince 0.31.0Source
(job: *Resolve_Job)

Runs on a helper thread: no context is needed for plain C calls.

Foreign#

VirtualAllocforeignexperimentalWindows onlySource
(address: *void, size: u64, allocation_type: u32, protect: u32) -> *void
VirtualProtectforeignexperimentalWindows onlySource
(address: *void, size: u64, new_protect: u32, old_protect: *u32) -> s32
VirtualFreeforeignexperimentalWindows onlySource
(address: *void, size: u64, free_type: u32) -> s32
mmapforeignexperimentalmacOS, Linux onlySource
(address: *void, length: u64, protection: s32, flags: s32, descriptor: s32, offset: s64) -> *void
mprotectforeignexperimentalmacOS, Linux onlySource
(address: *void, length: u64, protection: s32) -> s32
munmapforeignexperimentalmacOS, Linux onlySource
(address: *void, length: u64) -> s32
madviseforeignexperimentalmacOS, Linux onlySource
(address: *void, length: u64, advice: s32) -> s32
c_socketforeignexperimentalmacOS, Linux onlySource
(family: s32, kind: s32, protocol: s32) -> s32

libc is declared in stacks.jai.

c_bindforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: u32) -> s32
c_listenforeignexperimentalmacOS, Linux onlySource
(fd: s32, backlog: s32) -> s32
c_connectforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: u32) -> s32
c_acceptforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: *u32) -> s32
c_getsocknameforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: *u32) -> s32
c_getpeernameforeignexperimentalmacOS, Linux onlySource
(fd: s32, address: *void, length: *u32) -> s32
c_setsockoptforeignexperimentalmacOS, Linux onlySource
(fd: s32, level: s32, name: s32, value: *void, length: u32) -> s32
c_getsockoptforeignexperimentalmacOS, Linux onlySource
(fd: s32, level: s32, name: s32, value: *void, length: *u32) -> s32
c_shutdownforeignexperimentalmacOS, Linux onlySource
(fd: s32, how: s32) -> s32
c_sendforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64, flags: s32) -> s64
c_recvforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64, flags: s32) -> s64
c_readforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64) -> s64
c_writeforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64) -> s64
c_preadforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64, offset: s64) -> s64
c_pwriteforeignexperimentalmacOS, Linux onlySource
(fd: s32, buffer: *void, length: u64, offset: s64) -> s64
c_openforeignexperimentalmacOS, Linux onlySource
(path: *u8, flags: s32, args: ..Any) -> s32

open and fcntl are variadic in C, which matters on Apple arm64 (variadic arguments go on the stack).

c_closeforeignexperimentalmacOS, Linux onlySource
(fd: s32) -> s32
c_fcntlforeignexperimentalmacOS, Linux onlySource
(fd: s32, command: s32, args: ..Any) -> s32
c_pipeforeignexperimentalmacOS, Linux onlySource
(fds: *s32) -> s32
c_lseekforeignexperimentalmacOS, Linux onlySource
(fd: s32, offset: s64, whence: s32) -> s64
c_errnoforeignexperimental2 overloadsmacOS, Linux onlySource
() -> *s32
() -> *s32
c_accept4foreignexperimentalLinux onlySource
(fd: s32, address: *void, length: *u32, flags: s32) -> s32
c_getenvforeignexperimentalmacOS, Linux onlySource
(name: *u8) -> *u8
raw_syscallforeignexperimentalLinux onlySource
(number: s64, a1: s64, a2: s64, a3: s64, a4: s64, a5: s64, a6: s64) -> s64
c_eventfdforeignexperimentalLinux onlySource
(count: u32, flags: s32) -> s32
win_create_portforeignexperimentalWindows onlySource
(file: s64, port: s64, key: u64, threads: u32) -> s64
win_create_fileforeignexperimentalWindows onlySource
(name: *u16, access: u32, share: u32, security: *void, creation: u32, flags: u32, template: s64) -> s64
win_utf8_to_wideforeignexperimentalWindows onlySource
(code_page: u32, flags: u32, text: *u8, bytes: s32, wide: *u16, wide_count: s32) -> s32
win_get_envforeignexperimentalWindows onlySource
(name: *u8, buffer: *u8, size: u32) -> u32
win_create_eventforeignexperimentalWindows onlySource
(security: *void, manual_reset: s32, initial: s32, name: *u16) -> s64
win_wait_for_manyforeignexperimentalWindows onlySource
(count: u32, handles: *s64, wait_all: s32, milliseconds: u32) -> u32
win_socketforeignexperimentalWindows onlySource
(family: s32, kind: s32, protocol: s32, info: *void, group: u32, flags: u32) -> s64
win_bindforeignexperimentalWindows onlySource
(socket: s64, address: *void, length: s32) -> s32
win_listenforeignexperimentalWindows onlySource
(socket: s64, backlog: s32) -> s32
win_socket_nameforeignexperimentalWindows onlySource
(socket: s64, address: *void, length: *s32) -> s32
win_peer_nameforeignexperimentalWindows onlySource
(socket: s64, address: *void, length: *s32) -> s32
win_ioctlforeignexperimentalWindows onlySource
(socket: s64, code: u32, in_buffer: *void, in_size: u32, out_buffer: *void, out_size: u32, returned: *u32, overlapped: *void, completion: *void) -> s32
win_sendforeignexperimentalWindows onlySource
(socket: s64, buffers: *Wsa_Buffer, count: u32, sent: *u32, flags: u32, overlapped: *Overlapped, completion: *void) -> s32

Types#

Io_ErrorenumexperimentalSource
:: enum

Why a blocking call returned early or failed. platform_code() gives the system's own error number for the last failure of the running fiber, which is how OTHER stays useful.

NONE
CANCELED
cancel was called for this fiber, now or earlier.
CLOSED
The channel was closed (and, for a receive, is empty), or the handle was closed while the fiber waited on it.
TIMED_OUT
The with_deadline limit passed.
END_OF_FILE
read_full hit the end of the stream before it had everything.
CONNECTION_RESET
CONNECTION_REFUSED
CONNECTION_ABORTED
BROKEN_PIPE
NOT_CONNECTED
ADDRESS_IN_USE
ADDRESS_UNAVAILABLE
NETWORK_UNREACHABLE
HOST_UNREACHABLE
NOT_FOUND
ALREADY_EXISTS
PERMISSION_DENIED
INVALID
NO_SPACE
TOO_MANY_OPEN_FILES
IS_A_DIRECTORY
UNSUPPORTED
This system has no implementation yet (IO on Windows), or the operation does not apply.
RESOLVE_FAILED
The name could not be resolved.
OTHER
Anything else; see platform_code().
Fiber_StateenumexperimentalSource
:: enum

Where a fiber is in its life: waiting in the pool, ready, running, blocked on a timer, channel or mutex, or finished. It is the scheduler's own bookkeeping; programs rarely need it.

FREE
Waiting in the pool for spawn to reuse it.
READY
In the ready queue.
RUNNING
BLOCKED
Parked: waiting for a timer, a channel, a mutex...
DEAD
Its entry procedure finished.
FiberstructexperimentalSource
:: struct

One fiber. Treat it as opaque: use Fiber_Handle to refer to one from outside the scheduler.

ctx: Fiber_Context
stack: Stack
canceled: bool
Set by cancel; makes every blocking call return .CANCELED.
scheduler: *Scheduler
id: u64
generation: u64
Bumped each time the fiber ends, so a stale Fiber_Handle is recognised.
next: *Fiber
Ready queue and pool links.
all_previous: *Fiber
All live fibers.
all_next: *Fiber
data: *void
wake_reason: Io_Error
What park returns when the fiber is woken.
waiter: *Waiter
What the fiber is queued on while it is blocked, to take it off again when it is canceled.
timer_slot: s64
op: *Op
The engine operation this fiber is blocked in, and the name lookup, if any.
deadline: s64
Absolute now_nanoseconds() limit for every IO call (0: none), set by with_deadline.
grace_depth: s32
How many with_grace blocks are running; a cancel that comes meanwhile waits in cancel_held until the outermost one ends.
cancel_held: bool
platform_code: s32
The system's error number behind the last failed IO call.
fiber_context: #Context
temporary_storage: Temporary_Storage
temporary_ready: bool
Fiber_HandlestructexperimentalSource
:: struct

A name for one fiber, returned by spawn. It stays safe to use after the fiber has ended: the generation tells a reused fiber slot from the one the handle named, so cancel and join on a stale handle do nothing.

fiber: *Fiber
generation: u64
ChannelstructexperimentalSource
:: struct

A bounded queue of T between fibers. send blocks while it is full, recv while it is empty. With capacity 0 a send and a receive meet: each waits for the other.

buffer: *T
capacity: s64
head: s64
count: s64
closed: bool
senders: Waiter
receivers: Waiter
allocator: Allocator
Wait_GroupstructexperimentalSource
:: struct

Waits for a number of tasks, as many as were added and have not yet been done.

count: s64
waiters: Waiter
Io :: #import "Extensions/Io";

Io.io_run(() {
    group: Io.Wait_Group;

    for 1..3 {
        Io.add(*group);
        Io.spawn((group: *Io.Wait_Group) {
            Io.sleep(1);
            Io.done(group);
        }, *group);
    }

    assert(Io.wait(*group) == .NONE); // returns once all three are done
});
↗
Fiber_MutexstructexperimentalSource
:: struct

A lock for fibers. A fiber that cannot take it waits without blocking the thread, so unlike Thread's Mutex it is safe to hold across a sleep or a channel operation. It is not recursive, and waiters get it in the order they asked.

locked: bool
waiters: Waiter
Conditionstructexperimentalsince 0.27.0Source
:: struct

Fibers waiting until another fiber says that something changed. Unlike channels and locks, wait honours with_deadline. A condition carries no value and remembers nothing: a notify with nobody waiting is lost, so wait in a loop that checks the state itself.

waiters: Waiter
Io :: #import "Extensions/Io";

changed: Io.Condition;
ready: bool;

main :: () {
    Io.io_run(() {
        error: Io.Io_Error;

        // Nothing changes `ready` here, so the wait ends at the deadline.
        Io.with_deadline(20, #code {
            while !ready {
                error = Io.wait(*changed);           // .TIMED_OUT, .CANCELED
                if error != .NONE break;
            }
        });

        assert(error == .TIMED_OUT);
    });
}
↗
Address_FamilyenumexperimentalSource
:: enum

The IP version of an Address: IPV4, IPV6, or NONE for an Address that was never filled in (and for what parse_address returns when the text is not an address).

NONE
IPV4
IPV6
Io :: #import "Extensions/Io";

address, ok := parse_address("[::1]:8080");
assert(ok && address.family == .IPV6 && address.port == 8080);

address, ok = parse_address("127.0.0.1:80");
assert(ok && address.family == .IPV4);

address, ok = parse_address("not an address");
assert(!ok && address.family == .NONE);
↗
AddressstructexperimentalSource
:: struct

An IP address and port. bytes is in network order; an IPv4 address uses the first four.

port: u16
In host order.
bytes: [16] u8
scope: u32
IPv6 scope (interface index), 0 if none.
Pipe_HalfstructexperimentalSource
:: struct

One direction of a pipe.

data: [..] u8
start: s64
Bytes of data already read.
finished: bool
The writer has nothing more to send.
abandoned: bool
The reader is gone: writes fail.
readers: Waiter
Fibers waiting for bytes (at most one in practice).
File_ModeenumexperimentalSource
:: enum

How open_file opens a file.

READ
An existing file, read only.
WRITE
Created if missing, emptied if present, write only.
APPEND
Created if missing, writes go to the end.
READ_WRITE
Created if missing, kept as it is, read and write.
Io_FilestructexperimentalSource
:: struct

An open file. read and write use and advance position; read_at and write_at do not. It has a position, so every procedure takes it by pointer.

handle: Handle
position: s64
append: bool
Opened with .APPEND: every write goes to the end, whatever the offset.
Read_ProctypeexperimentalSource
:: #type (data: *void, buffer: [] u8) -> s64, Io_Error

Reads what is there, up to buffer.count bytes, waiting for at least one: 0 means the end.

Write_ProctypeexperimentalSource
:: #type (data: *void, bytes: [] u8) -> s64, Io_Error

Writes at least one byte of bytes (all of it for a sink that never waits) and returns how many.

Wait_ProctypeexperimentalSource
:: #type (data: *void) -> Io_Error

Waits until read would not have to wait: a byte is there, the source ended, or it failed (the read that follows says which). Nothing is consumed, so it needs no buffer.

Close_ProctypeexperimentalSource
:: #type (data: *void, now: bool) -> Io_Error

Closes the source. now asks for a close that does not wait, for code that cannot block.

ReaderstructexperimentalSource
:: struct

A source of bytes: a pointer to its data and the procedures to use on it. reader_of makes one for a file, a TCP stream or a pipe end, and a struct of your own can fill in read_proc (and optionally close_proc and wait_proc). A Reader is a handle: copy it freely, it owns nothing, and the source must stay alive while it is used. close on it closes the source.

data: *void
read_proc: Read_Proc
close_proc: Close_Proc
Null for a source that has nothing to close.
wait_proc: Wait_Proc
Null for a source that cannot say when a read would not wait (wait_readable then returns .UNSUPPORTED). A TCP stream can.
Counter :: struct {
    next: u8;
}

count_up :: (data: *void, buffer: [] u8) -> s64, Io_Error {
    counter := cast(*Counter) data;

    for 0..buffer.count - 1 {
        buffer[it] = counter.next;
        counter.next += 1;
    }

    return buffer.count, .NONE;
}

main :: () {
    counter: Counter;
    source := Reader.{ data = *counter, read_proc = count_up };

    bytes: [4] u8;
    count, error := read(source, bytes);

    assert(error == .NONE && count == 4);
    assert(bytes[0] == 0 && bytes[3] == 3);
}
↗
WriterstructexperimentalSource
:: struct

A sink for bytes, the counterpart of Reader: a pointer to its data, a write_proc and an optional close_proc. writer_of makes one for a file, a TCP stream or a pipe end. It is a handle that owns nothing; close on it closes the sink.

data: *void
write_proc: Write_Proc
close_proc: Close_Proc
Line_ReaderstructexperimentalSource
:: struct

Reads lines from source. Make one with make_line_reader, read with read_line and give its buffer back with free_line_reader. It does not close source.

source: Reader
limit: s64
buffer: [..] u8
start: s64
The line being read starts here; scanned bytes after it are known to hold no newline.
scanned: s64
ended: bool
unterminated: bool
Set by read_line: whether the line it just returned had no line ending.
BackendenumexperimentalSource
:: enum

The system facility a scheduler waits on: IO_URING or EPOLL on Linux, KQUEUE on macOS, IOCP or IORING on Windows. The scheduler picks the best one the machine offers; the JAI_IO_BACKEND environment variable names another (io_uring, epoll, kqueue, iocp, ioring). NONE is a system with no IO, where every operation fails with .UNSUPPORTED. Behaviour seen through the API is the same on all of them; they differ in speed and in the kernel versions they need.

NONE
No IO on this system: every operation fails with .UNSUPPORTED.
IO_URING
EPOLL
KQUEUE
IOCP
Windows: completion ports for sockets and files.
IORING
Windows 11 22H2 and newer: completion ports for sockets, an IoRing for files.
CapabilitiesstructexperimentalSource
:: struct

What the engine found it could use. Backends fill in what applies.

single_issuer: bool
io_uring: the setup flags the kernel accepted.
defer_taskrun: bool
coop_taskrun: bool
ext_arg: bool
io_uring: waits with a timeout in one call (Linux 5.11).
kernel_ops: s64
io_uring: how many operations the kernel's probe lists.
Local_DroptypeexperimentalSource
:: #type (value: *void)

What set_scheduler_local is given to release a value when its scheduler ends. It runs on the scheduler's thread after every fiber has ended, so it must not block: close sockets with close(stream, now = true).

Accept_SlotstructexperimentalSource
:: struct

One listening socket's multishot accept. A fiber in accept waits on it; connections that arrive while nobody waits are queued for the next call.

fd: s64
op: *Op
The armed request, null when the kernel has ended it (or it was never armed).
closing: bool
close ran: queued sockets are closed, and the slot is freed when the request ends.
waiter: *Accept_Wait
queue: [..] s32
head: s64
Accept_WaitstructexperimentalSource
:: struct

The answer a parked accept is waiting for; lives on that fiber's stack.

fiber: *Fiber
result: s64
cause: Io_Error
closing: bool
The listener was closed: the slot may be gone by the time the fiber runs.
Iocp_StatestructexperimentalSource
:: struct

What the Windows engine keeps. The fields exist everywhere so that Engine has one shape.

port: s64
sockets_started: bool
accept_ex: *void
connect_ex: *void
ring: s64
The IoRing, when files use one (0 otherwise), its completion event, and the thread that turns the event into a packet on the port. The thread starts at the first file operation.
ring_event: s64
stop_event: s64
ring_thread: Thread.Thread
ring_thread_started: bool
ring_inflight: s64
ring_dirty: bool
backlog_head: *Op
File operations waiting for room in the ring's queue.
backlog_tail: *Op
Io_Ring_RefstructexperimentalWindows onlySource
:: struct

IORING_HANDLE_REF and IORING_BUFFER_REF: a kind (0: a raw handle or address) and the value.

kind: u32
value: u64
Io_Ring_CqestructexperimentalWindows onlySource
:: struct
user_data: u64
result: s32
information: u64
Io_Ring_CapabilitiesstructexperimentalWindows onlySource
:: struct
max_version: u32
max_submission: u32
max_completion: u32
features: u32
Ring_CreatetypeexperimentalWindows onlySource
:: #type (version: u32, flags: Io_Ring_Flags, submission: u32, completion: u32, ring: *s64) -> s32 #c_call
Ring_ClosetypeexperimentalWindows onlySource
:: #type (ring: s64) -> s32 #c_call
Ring_SubmittypeexperimentalWindows onlySource
:: #type (ring: s64, wait_count: u32, milliseconds: u32, submitted: *u32) -> s32 #c_call
Ring_WritetypeexperimentalWindows onlySource
:: #type (ring: s64, file: Io_Ring_Ref, buffer: Io_Ring_Ref, count: u32, offset: u64, write_flags: u32, user_data: u64, flags: u32) -> s32 #c_call
Ring_ApistructexperimentalWindows onlySource
:: struct

The addresses of the functions, found once per process.

create: *void
close: *void
query: *void
submit: *void
pop: *void
set_event: *void
read: *void
write: *void
StackstructforeignexperimentalSource
:: struct
base: *void
Lowest address of the mapping, which is the start of the guard region.
size: s64
Bytes mapped, guard included.
guard: s64
Sock_StoragestructforeignexperimentalSource
:: struct

A socket address of any family, as the kernel fills it in.

words: [16] u64
Handle_KindenumforeignexperimentalSource
:: enum
NONE
SOCKET
FILE
A regular file: read and written at explicit offsets.
STREAM
Anything else that has a descriptor and waits for readiness: pipes, terminals.
HandlestructforeignexperimentalSource
:: struct

An open descriptor, as the engine sees it. fd is a file descriptor on POSIX and a SOCKET or HANDLE on Windows.

fd: s64
Op_KindenumforeignexperimentalSource
:: enum
ACCEPT
CONNECT
RECV
SEND
READ
Files (at offset) and streams (at the current position).
WRITE
CLOSE
WAIT_READABLE
Completes (with 0) when a RECV would not wait: bytes, the end of the stream or an error are there. Takes no buffer and consumes nothing. Sockets only.
Op_StateenumforeignexperimentalSource
:: enum
FREE
PENDING
Described but not yet submitted to the kernel (io_uring), or waiting for readiness (epoll, kqueue).
DONE
OverlappedstructforeignexperimentalSource
:: struct

The Win32 OVERLAPPED structure. It is the first member of Op, so the address the completion port hands back is the address of the operation.

internal: u64
internal_high: u64
offset: u32
offset_high: u32
event: s64
OpstructforeignexperimentalSource
:: struct

One operation, from submission to completion.

overlapped: Overlapped
Windows completion ports: must stay the first member.
kind: Op_Kind
state: Op_State
handle: Handle
buffer: *void
length: s64
offset: s64
File position; negative: the current one.
address: Sock_Storage
The peer, in and out (connect, accept).
address_length: u32
phase: u8
How far a multi-step operation got (connect: 0 not started, 1 waiting for the result).
wants_write: bool
Which readiness this operation waits for.
fiber: *Fiber
internal: bool
Not a fiber's: reported to internal_completion.
canceling: bool
cancel_cause: Io_Error
Why cancel_op canceled it: .CANCELED, .TIMED_OUT; .NONE if the handle was closed.
aux: s64
Windows: the socket accept made ahead of time (AcceptEx).
uncancelable: bool
A file thread pool worker or an IoRing owns it; it runs to the end and cannot be interrupted.
tag: Internal_Tag
Which internal user an internal operation belongs to.
link: *Pool_Link
The scheduler's end of the file thread pool that a worker reports to.
slot: *Accept_Slot
io_uring: the listener whose multishot accept this is.
result: s64
The completion.
flags: u32
more: bool
More completions follow (multishot operations: the io_uring accept).
buffer_id: s32
next: *Op
The free list, or the list of operations waiting on a descriptor.
previous: *Op
Internal_TagenumforeignexperimentalSource
:: enum

What an internal operation (one that belongs to the engine, not to a fiber) is for.

RESOLVER_WAKE
The pipe that resolver threads write to.
POOL_WAKE
The eventfd that file pool workers write to (epoll).
EnginestructforeignexperimentalSource
:: struct
allocator: Allocator
backend: Backend
fallback_reason: string
Why this is not the backend that was asked for or would have been first choice. Empty if it is.
free_ops: *Op
poll_fd: s32
epoll and kqueue.
fds: [..] Fd_State
changes: [..] Kevent
uring: *Uring_State
iocp: *Iocp_State
file_inline: s64
The file thread pool (epoll, kqueue): reads and writes longer than file_inline bytes go to file_threads workers; 0 threads means everything runs inline.
file_threads: s64
pool: *Pool_Link
Fd_StatestructforeignexperimentalSource
:: struct

What the poll backends remember per descriptor.

read_head: *Op
read_tail: *Op
write_head: *Op
write_tail: *Op
read_armed: bool
kqueue: a filter is registered and has not fired.
write_armed: bool
registered: bool
epoll: registered at all (edge-triggered, for good), and the events it asks for.
mask: u32
read_blocked: bool
epoll: the last try in that direction found nothing to do, and no event has said otherwise since, so the next operation waits without trying.
write_blocked: bool
hup: bool
The peer has shut down or the socket has an error: receives always try, since the end of the stream is not an edge that comes again.
KeventstructforeignexperimentalSource
:: struct
ident: u64
filter: s16
flags: u16
fflags: u32
data: s64
udata: *void
TimespecstructforeignexperimentalSource
:: struct
seconds: s64
nanoseconds: s64
Epoll_EventstructforeignexperimentalSource
:: struct
events: u32
fd: u32
extra: u32
Poll_TimespecstructforeignexperimentalLinux onlySource
:: struct
seconds: s64
nanoseconds: s64
Wsa_DatastructforeignexperimentalWindows onlySource
:: struct

Room for the 408 bytes of WSADATA.

words: [64] u64
Wsa_BufferstructforeignexperimentalWindows onlySource
:: struct
length: u32
data: *void
Overlapped_EntrystructforeignexperimentalWindows onlySource
:: struct

OVERLAPPED_ENTRY, what the completion port returns.

key: u64
overlapped: *Overlapped
internal: u64
bytes: u32
GuidstructforeignexperimentalWindows onlySource
:: struct
data1: u32
data2: u16
data3: u16
data4: [8] u8
Accept_ExtypeforeignexperimentalWindows onlySource
:: #type (listener: s64, accepted: s64, buffer: *void, receive_length: u32, local_length: u32, remote_length: u32, received: *u32, overlapped: *Overlapped) -> s32 ...
Connect_ExtypeforeignexperimentalWindows onlySource
:: #type (socket: s64, address: *void, length: s32, send_buffer: *void, send_length: u32, sent: *u32, overlapped: *Overlapped) -> s32 #c_call
Resolve_JobstructforeignexperimentalSource
:: struct

One getaddrinfo call, shared by every fiber that asked for the same name while it was under way.

allocator: Allocator
host: *u8
host_length: s64
code: s32
0, or the getaddrinfo error code. Written by the helper, read after it is collected.
address: Address
The answer with port 0; each waiter applies its own port.
waiters: *Resolve_Waiter
Fibers waiting for the answer (scheduler thread only).
next: *Resolve_Job
The helper queues (mutex-protected).
inflight_next: *Resolve_Job
Jobs not yet collected, to find one to join (scheduler thread only).
Wake_StatestructforeignexperimentalSource
:: struct
allocator: Allocator
read_handle: Handle
write_fd: s64
op: *Op
The READ on the pipe, while one is outstanding.
buffer: [64] u8
outstanding: s64
Jobs handed out whose answer has not been collected yet, ones nobody waits for included. A job holds a helper from the time it is queued, so this is also the number of helpers needed.
inflight: *Resolve_Job
jobs_made: s64
Jobs made over this scheduler's life (for tests and diagnostics).
mutex: Thread.Mutex
Shared with the helper threads.
queue_head: *Resolve_Job
queue_tail: *Resolve_Job
done_head: *Resolve_Job
done_tail: *Resolve_Job
quitting: bool
helpers_started: s64
delay_ms: s64
For tests: JAI_IO_RESOLVE_DELAY_MS makes every lookup wait that long first, so a test can cancel a lookup that is certainly still under way. Read once per scheduler; 0 normally.
AddrinfostructforeignexperimentalSource
:: struct
flags: s32
family: s32
socket_type: s32
protocol: s32
address_length: u64
canonical_name: *u8
address: *Sock_Storage
next: *Addrinfo
Io_StatisticsstructforeignexperimentalSource
:: struct

What the running scheduler has done so far, for tests and diagnostics (nothing here is needed to use the module). Every field is 0 before the first name lookup.

resolver_jobs: s64
name lookups made
resolver_helpers: s64
helper threads started for them

Constants#

:: 64 * 1024

The default for make_line_reader's limit: the longest line it accepts, in bytes.

RING_ENTRIESexperimentalLinux onlySource
:: 512

Submission queue size. The completion queue is twice that, and overflowing completions are kept by the kernel until reaped.

UNTRACKEDexperimentalLinux onlySource
:: 1

user_data of entries nobody waits for.

SOCK_CLOEXECexperimentalLinux onlySource
:: 0x80000

Accepted sockets block: io_uring waits for them itself.

ACCEPT_QUEUE_LIMITexperimentalLinux onlySource
:: 128

Accepted connections wait here when no fiber is in accept; more than this and the multishot request is canceled until the queue drains.

ENTRIESexperimentalWindows onlySource
:: 64

Completion packets dequeued per call.

RING_VERSION_NEWESTexperimentalWindows onlySource
:: 300

The ring versions to try, newest first: IORING_VERSION_3 is 300 in ioringapi.h. File writes need one of the later versions; the self test builds a write entry to find out which a system accepts.

libcforeignexperimentalmacOS, Linux onlySource
:: #system_library "libc"
E_UNSUPPORTEDforeignexperimentalSource
:: 0x7fff_0001

Internal result for "this system has no implementation". Not an errno.

MAX_TRANSFERforeignexperimentalSource
:: 0x4000_0000

The most any call takes in one go, so that a 32-bit count never overflows.

EPOLLINforeignexperimentalSource
:: 0x1

The epoll event bits the backend keeps in Fd_State.mask. Defined on every OS: the code that reads the mask is shared with kqueue, which leaves it 0.

SYS_epoll_pwait2foreignexperimentalLinux onlySource
:: 441

epoll_pwait2 (Linux 5.11) takes the timeout in nanoseconds; epoll_wait rounds it up to a whole millisecond, which delays a timer 100 us away by up to 1 ms. The raw system call: libc before 2.35 has no wrapper. The number is the same on x86-64 and arm64.

POOL_EVENTforeignexperimentalmacOS onlySource
:: 0x6a61

ident of the one user event of a kqueue.

ws2_32foreignexperimentalWindows onlySource
:: #system_library "ws2_32"

kernel32 is declared in stacks.jai.

KEY_OPERATIONforeignexperimentalWindows onlySource
:: 0

Completion keys of the packets that belong to no operation.

SEND_CHUNKforeignexperimentalWindows onlySource
:: 1 << 16

The most a single WSASend takes. A send of a large buffer only completes when the peer has taken all of it, and one that is canceled half way may report no progress; in pieces of a socket buffer's size the caller can count what went out.

ACCEPT_ADDRESS_BYTESforeignexperimentalWindows onlySource
:: 44

The length AcceptEx wants reserved for each of the two addresses it writes.

RESOLVER_MIN_THREADSforeignexperimentalSource
:: 1

Helper threads at least one lookup needs, and the most a scheduler starts. A helper stays busy until the OS answers, so a lookup that finds every helper busy starts another.