Zig
The library is written in Zig, and its package exports the module fastipc: Listener, Queue, Service and Conn over your own std.Io, with Zig error sets in place of result codes and std.Io.Timeout in place of milliseconds. It is the same implementation as the C API, compiled into your program, so there is no shared library to ship.
zig fetch --save git+https://github.com/fastipc/fastipcInstall and build
Zig 0.17.0 · x86-64-v3 · ARM64 Linux · Apple Siliconzig fetch --save adds the package to your build.zig.zon as fipc; for a checkout of your own, list it by path instead: .fipc = .{ .path = "../fastipc" }. Your build.zig then imports the dependency's module fastipc into each program that uses it.
The module is the library's own source (small enough to read), built with your target and optimize mode. It links libc and the system libraries it needs, which the package's build adds. It needs Zig 0.17.0 and runs where the library does: x86-64-v3 CPUs, Linux with glibc 2.34+ (x86-64 and ARM64), Windows 11 and Server 2022+, and macOS 14.4+ on Apple Silicon (platform support).
const dep = b.dependency("fipc", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("fastipc", dep.module("fastipc"));.dependencies = .{
.fipc = .{ .path = "../fastipc" },
},Server and client
two processes, one nameEach file is a program of its own, an executable in your build.zig; these two are the repository's examples/zig, a project that builds them. Start the two in either order: fastipc.streamConnect waits up to its timeout for a server to create the stream and accept. The client sends the message ping, and the server answers in upper case: the exchange every example on this site makes, so either program works with the other half in any language (every pair of languages is tested).
const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
// rings of 1 MiB each way
const listener = try fastipc.streamCreate(
init.gpa,
"my_channel",
1 << 20,
);
defer listener.close();
// waits for a client
const conn = try listener.accept(.none);
defer conn.close();
var buf: [256]u8 = undefined;
// .none: no timeout
const len = try conn.recv(&buf, .none);
const reply = std.ascii.upperString(&buf, buf[0..len]);
try conn.send(reply, .none);
}const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
const five_s: std.Io.Timeout = .{ .duration = .{
.raw = .fromSeconds(5),
.clock = .awake,
} };
const conn = try fastipc.streamConnect(
init.gpa,
"my_channel",
five_s,
);
defer conn.close();
try conn.send("ping", .none);
var buf: [256]u8 = undefined;
const len = try conn.recv(&buf, .none);
std.debug.print("{s}\n", .{buf[0..len]}); // PING
}The rules
the C API's, in Zig termsTimeouts
Always the last parameter. .none waits as long as it takes, a zero .duration doesn't wait, and a .deadline is a point in time. A call that runs out returns error.Timeout having changed nothing.
Names and rings
A name is 1 to 245 characters of [A-Za-z0-9_.-], not starting with . or -, local to the user (Linux, macOS) or the desktop session (Windows). The name's creator picks the ring size: for a stream, a power of two from 1 KiB to 2 GiB.
One sender, one receiver
On a connection, one thread at a time sends and one receives, and the two may run at once; a service client's call does both. cancel works from any thread; no call may overlap close. To stop a waiting thread: cancel, join it, then close.
Messages
A message holds at least one byte and arrives in order (a work queue promises no order; a service's replies arrive in the order of their requests). send and recv take any size; a long message travels in pieces and is delivered whole or not at all.
Buffers too small
A recv into a buffer that is too small takes nothing: the message stays queued. recvAlloc takes it at any size, into memory it allocates for exactly its length.
The peer's end
Final for that connection: sends return it at once, receives after every completed message, and ended() turns true. Close the connection, then accept or connect a new one.
Messages
bytes of any size, no headersend takes a message of any size and sends it in pieces of up to maxPiece() bytes when it is longer; its timeout bounds the wait for room for the first piece, and a started message completes unless a cancel or the peer's end stops it, which drops it whole. recv fills your buffer and returns the length; recvAlloc returns a new slice that you free. This server echoes every message back until its client leaves, which ends its next receive with error.Disconnected.
const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
// rings of 1 MiB each way: a power of two, 1 KiB to 2 GiB
const listener = try fastipc.streamCreate(
init.gpa,
"demo",
1 << 20,
);
defer listener.close();
// waits for a client
const conn = try listener.accept(.none);
defer conn.close();
while (true) {
// a message of any size, in memory from gpa
const message = conn.recvAlloc(
init.gpa,
.none,
) catch |err| switch (err) {
error.Disconnected => break, // the client left
else => return err,
};
defer init.gpa.free(message);
try conn.send(message, .none); // echo it
}
std.debug.print("client gone\n", .{});
}const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
const five_s: std.Io.Timeout = .{ .duration = .{
.raw = .fromSeconds(5),
.clock = .awake,
} };
const conn = try fastipc.streamConnect(
init.gpa,
"demo",
five_s,
);
defer conn.close();
var buf: [64]u8 = undefined;
for ([_][]const u8{ "hello", "shared", "memory" }) |word| {
try conn.send(word, .none);
const len = try conn.recv(&buf, five_s);
std.debug.print("{s}\n", .{buf[0..len]});
}
}Errors and timeouts
error sets for fipc_result_tEach error has the name of the C result code it stands for (the codes). The four creates (streamCreate, broadcastCreate, queueCreate, serviceCreate) return fastipc.CreateError, accept returns fastipc.AcceptError, streamConnect and the joins return fastipc.Error, a queue's and a service's waitForMembers return fastipc.WaitError, and the data calls (and a publisher's waitForMembers) return fastipc.DataError; recvAlloc and call add std.mem.Allocator.Error.
| Error | C code | Returned by | Meaning |
|---|---|---|---|
error.Timeout | FIPC_TIMEOUT | every call that waits | The timeout ran out; with a zero duration, there was nothing to do without waiting. |
error.Disconnected | FIPC_DISCONNECTED | the data calls | The peer closed the connection or its process ended. |
error.Cancelled | FIPC_CANCELLED | every call that waits | The handle's cancel was called, and the call would have had to wait. A data call whose task an Io.Threaded cancels returns it too. |
error.TooLarge | FIPC_TOO_LARGE | recv, acquire, recvAcquire | The message doesn't fit the buffer, or one piece for zero-copy and on a broadcast, a queue or a service. A message being received stays queued, for recvAlloc to take at any size. |
error.Invalid | FIPC_INVALID | any call | A bad argument, a call out of order (an accept while the last client's connection is open), a peer of another version or user, corrupt ring content. |
error.NoMemory | FIPC_NO_MEMORY | the creates, accept, streamConnect, the joins | Memory or another OS resource ran out while setting a connection, a broadcast, a queue or a service up. |
error.AddrInUse | FIPC_ADDR_IN_USE | the creates | Another stream, broadcast, queue or service holds the name. |
error.Overrun | FIPC_OVERRUN | recv, recvAlloc | A lossy broadcast subscriber fell a lap behind and lost messages; lostMessages says how many, and the next receive goes on from a recent message. |
error.Unsubscribed | FIPC_UNSUBSCRIBED | recv, recvAlloc, recvAcquire | A gated broadcast subscriber held the publisher back for the broadcast's max_stall_ms and was dropped from the broadcast: every later receive returns it, so close the connection. |
error.Abandoned | FIPC_ABANDONED | recv, recvAlloc, recvAcquire, call | A service's client: the server that took the request ended before it replied. The request counts as answered, and the next receive returns the next reply; it may or may not have been carried out. |
const Timeout = std.Io.Timeout;
const forever: Timeout = .none;
const no_wait: Timeout = .{ .duration = .{
.raw = .zero,
.clock = .awake,
} };
const five_s: Timeout = .{ .duration = .{
.raw = .fromSeconds(5),
.clock = .awake,
} };
const this_frame: Timeout = .{ .deadline = .fromNow(io, .{
.raw = .fromMilliseconds(16),
.clock = .awake,
}) };const hundred_ms: std.Io.Timeout = .{ .duration = .{
.raw = .fromMilliseconds(100),
.clock = .awake,
} };
var buf: [4096]u8 = undefined;
if (conn.recv(&buf, hundred_ms)) |len| {
handle(buf[0..len]);
} else |err| switch (err) {
error.Timeout => {}, // nothing yet
else => return err, // error.Disconnected: the peer is gone
}The data calls (send, recv, recvAlloc, acquire, recvAcquire, call) wait in whole milliseconds and round a timeout up to the next one.
Zero-copy
messages of one piece: up to conn.maxPiece() bytesacquire returns room in the ring as a []u8; write the message there and commit the bytes you wrote, at least one. recvAcquire returns the next message where it lies, as a []const u8 that stays valid until release, the next receive or close. maxPiece() is the ring's capacity less 64 bytes. A longer message goes through send, and recvAcquire returns error.TooLarge for a message of several pieces, which stays queued for recv.
const out = try conn.acquire(64, .none); // room in the ring
const text = try std.mem.print(out, "tick {d}", .{tick});
try conn.commit(text.len); // publishes text.len bytes// the next message, read-only, in the ring
const message = try conn.recvAcquire(.none);
handle(message);
conn.release(); // frees its roomThreads
cancel, join, closeaccept and streamConnect set a connection up on the thread that calls them, so a client and its server run on different threads or processes. broadcastJoin, queueJoin and serviceJoin set a member up on the thread that calls them too, and the owner's process admits it on a task of its own, so an owner may join its own broadcast, queue or service from any thread, the one that created it included. Then every connection runs a task on the library's own threads, which watches the peer: the library needs no Io of yours.
Listener, Conn, Queue and Service are handles, copied by value. To stop a thread that waits, call cancel() from any thread: every call of that handle that would wait, then or later, returns error.Cancelled, and calls that needn't wait still work. Then join the thread and close. The waits of accept, streamConnect and the joins are the OS's, not an Io's: cancel() ends an accept, a timeout bounds a connect or a join. When an Io.Threaded cancels a task that waits in a data call (the Io that std.process.Init hands to main is one), the call returns error.Cancelled and the cancelation stays pending for the task's next cancelation point.
const Reader = struct {
fn run(c: fastipc.Conn) void {
var buf: [4096]u8 = undefined;
// until error.Cancelled (or error.Disconnected)
while (c.recv(&buf, .none)) |len| {
handle(buf[0..len]);
} else |_| {}
}
};
const reader = try std.Thread.spawn(.{}, Reader.run, .{conn});
// ...
conn.cancel(); // wakes the reader: error.Cancelled
reader.join();
conn.close();Broadcast
one publisher, many subscribersA broadcast sends every message of one publisher to each of its subscribers, in any processes of the user; each message is stored once, in memory every subscriber maps. Messages are of one piece, up to a quarter of the capacity. fastipc.broadcastCreate returns the publisher's connection; the publisher's process admits the subscribers on a task of its own, so no thread of yours waits for them. The publisher waits for the first one (publisher.waitForMembers) before it sends tick 1 to tick 3; publisher.close() ends the broadcast and frees the name. The subscriber joins gated (fastipc.broadcastJoin with .gated), so it misses nothing (the publisher waits for it), and receives until error.Disconnected, which comes after the last message. A lossy subscriber (.lossy) never holds the publisher back: one that falls a lap behind gets error.Overrun, and conn.lostMessages() says how many it lost. A gated one that holds the publisher back for the broadcast's max_stall_ms is unsubscribed (error.Unsubscribed).
const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
// 64 KiB of messages, up to 8 subscribers;
// one that holds the publisher back 5 s is dropped
const publisher = try fastipc.broadcastCreate(
init.gpa,
"my_broadcast",
1 << 16,
8,
5000,
);
defer publisher.close(); // ends the broadcast
// waits for the first subscriber
_ = try publisher.waitForMembers(1, .none);
for (1..4) |i| {
var buf: [16]u8 = undefined;
const tick = try std.mem.print(&buf, "tick {d}", .{i});
try publisher.send(tick, .none);
}
}const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
const five_s: std.Io.Timeout = .{ .duration = .{
.raw = .fromSeconds(5),
.clock = .awake,
} };
// gated: misses nothing (the publisher waits for it)
const subscriber = try fastipc.broadcastJoin(
init.gpa,
"my_broadcast",
.gated,
five_s,
);
defer subscriber.close();
var buf: [256]u8 = undefined;
while (true) {
const len = subscriber.recv(&buf, .none) catch |err| switch (err) {
error.Disconnected => return, // the publisher closed
else => return err,
};
std.debug.print("{s}\n", .{buf[0..len]}); // tick 1, ...
}
}Work queue
many producers, each message to one consumerA work queue takes messages from up to 63 producers and hands each to one of up to 64 consumers, in any processes of the user. Each producer has a ring of its own; a consumer takes a message of any producer, and the queue promises no order. Messages are of one piece, up to a quarter of the capacity, and wait in the queue until a consumer takes them. Each message goes to one consumer, and again, to another, only if that consumer's process ends before acknowledging it (delivery, below). Here the producer creates the queue (fastipc.queueCreate: a Queue) and joins it as its producer with fastipc.queueJoin, as any process does: the owner's process admits every joiner on a task of its own, so the thread that created the queue may join it. It sends job 1 to job 3, waits for the consumer (queue.waitForMembers) and closes the queue, which ends it: the consumer receives what is left, then error.Disconnected. Other processes join with fastipc.queueJoin (.producer, .consumer); a member's close never ends the queue.
const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
const five_s: std.Io.Timeout = .{ .duration = .{
.raw = .fromSeconds(5),
.clock = .awake,
} };
// 4 KiB of messages per producer, 1 producer and 1 consumer:
// this process owns the queue and is its producer
const queue = try fastipc.queueCreate(
init.gpa,
"my_queue",
4096,
1,
1,
);
defer queue.close(); // ends the queue
const producer = try fastipc.queueJoin(
init.gpa,
"my_queue",
.producer,
five_s,
);
defer producer.close();
for (1..4) |i| {
var buf: [16]u8 = undefined;
const job = try std.mem.print(&buf, "job {d}", .{i});
try producer.send(job, .none); // waits in the queue for a consumer
}
_ = try queue.waitForMembers(.consumer, 1, .none); // waits for the consumer to join
}const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
const five_s: std.Io.Timeout = .{ .duration = .{
.raw = .fromSeconds(5),
.clock = .awake,
} };
const consumer = try fastipc.queueJoin(
init.gpa,
"my_queue",
.consumer,
five_s,
);
defer consumer.close();
var buf: [256]u8 = undefined;
while (true) {
const len = consumer.recv(&buf, .none) catch |err| switch (err) {
error.Disconnected => return, // the queue ended
else => return err,
};
std.debug.print("{s}\n", .{buf[0..len]}); // job 1, ...
}
}Delivery. Each message goes to one consumer. It goes again, to another consumer, only if the process of the consumer that took it ends before acknowledging it, and when a consumer acknowledges is its own choice, not the queue's. A message received in place is acknowledged at its release (or the consumer's next receive, or its close).
| How the consumer receives | When it acknowledges | If its process ends | What the application gets |
|---|---|---|---|
recv or recvAlloc (copying) | inside the call, before it returns | inside the call: the message goes to another consumer, and this one never saw it; after the call returned: it doesn't go again | at most once: no duplicates |
recvAcquire, then release at once, then the work | at the release, before the work | as above | at most once: no duplicates |
recvAcquire, the work, then release | at the release, after the work | before the release: the message goes to another consumer | at least once: a duplicate is possible, so the work must be idempotent |
- Choose by the work, per consumer. Work that may safely run twice (rendering, a computation, re-indexing, converting a file) acknowledges after it; work whose effects must never repeat (a payment, an email, a motor command) acknowledges before it.
- Not covered: a consumer that hangs while its process runs (there are no timeouts, by design); the owner's process, with which the queue ends; a reboot. A consumer holds at most one unacknowledged message at a time, the one received in place.
- A message that goes again comes in no set order, after messages its producer sent later: the queue promises no order anyway.
- A message held in place holds back its producer's room until its release: a consumer that acknowledges after a long job keeps that message's room, and so the room of the producer's messages after it, until then.
- A message that crashes every consumer that takes it, acknowledged after the work, goes to each consumer in turn, with no limit: acknowledge before the work where that can happen.
Service
many clients, a pool of servers, with repliesA service takes requests from up to 64 clients and hands each to one of up to 64 servers, in any processes of the user; the server that took a request replies to it, and a client receives its replies in the order of its requests. Here the server creates the service (fastipc.serviceCreate: a Service) and joins it as its server with fastipc.serviceJoin (.server), answers three requests with their uppercase and closes the service, which ends it; the client joins (.client) and calls ping 1 to ping 3: call sends a request and returns its reply, in memory from the allocator, and the client prints each. A request whose server ended before it replied gets error.Abandoned.
const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
const five_s: std.Io.Timeout = .{ .duration = .{
.raw = .fromSeconds(5),
.clock = .awake,
} };
// 4 KiB per client, requests and replies of up to 256 bytes, 1 client
// and 1 server: this process owns the service and is its server
const service = try fastipc.serviceCreate(
init.gpa,
"my_service",
4096,
256,
1,
1,
);
defer service.close(); // ends the service
const server = try fastipc.serviceJoin(
init.gpa,
"my_service",
.server,
five_s,
);
defer server.close();
var buf: [256]u8 = undefined;
for (0..3) |_| { // three requests, then the end
const len = try server.recv(&buf, .none); // the next request of any client
const reply = std.ascii.upperString(&buf, buf[0..len]);
try server.send(reply, .none); // goes to its client, never waits
}
}const std = @import("std");
const fastipc = @import("fastipc");
pub fn main(init: std.process.Init) !void {
const five_s: std.Io.Timeout = .{ .duration = .{
.raw = .fromSeconds(5),
.clock = .awake,
} };
const client = try fastipc.serviceJoin(
init.gpa,
"my_service",
.client,
five_s,
);
defer client.close();
var buf: [16]u8 = undefined;
for (1..4) |i| {
const request = try std.mem.print(&buf, "ping {d}", .{i});
// sends the request and returns its reply, which the caller
// frees; error.Abandoned if the server ended before it replied
const reply = try client.call(init.gpa, request, .none);
defer init.gpa.free(reply);
std.debug.print("{s}\n", .{reply}); // PING 1, ...
}
}The calls. A service's connections are Conns, and what each call does depends on the role. Requests and replies are of one piece, 1 to max_message bytes, the owner's choice at serviceCreate (1 to a quarter of the capacity); maxPiece() returns it on both roles.
| Call | On a client | On a server |
|---|---|---|
send, acquire · commit | Sends a request. Waits up to its timeout for room in the client's ring. | Replies to the request it holds; never waits. error.Invalid with no request held, error.TooLarge over maxPiece(). |
recv, recvAlloc | The reply to its oldest request whose reply it hasn't received, waiting for it if it hasn't come; error.Abandoned if that request's server ended before it replied. | The next request of any client, copied; the server then holds it until it replies. |
call | Sends a request and returns its reply, in memory from gpa that you free, within one timeout; error.Abandoned if its server ended before it replied. | error.Invalid, as on any connection other than a service's client. |
recvAcquire · release | The next reply in place, until release, the next receive or close. | The next request in place, valid until the reply's send or commit; release does nothing. |
close | Leaves the service: requests no server took are dropped, and the replies to those already taken are lost. | Leaves the service; a request it still holds is answered error.Abandoned. |
- Replies come back in the order of the requests, so the n-th reply a client receives answers its n-th request, and a request carries no id. A client may send several requests before it receives, as many as its ring has room for, and the servers work on them at once. A receive that times out or is cancelled leaves its reply to come: the next receive returns it. So a client that sends again after a receive that timed out receives that earlier reply first: replies come in request order.
call(gpa, request, timeout)never returns another request's reply. A call checks its request first:error.Invalidfor an empty one,error.TooLargeovermaxPiece(), at once, even while a reply is owed. Then a call on a cancelled connection returnserror.Cancelledat once, whatever its timeout, and sends nothing: it could never receive the reply. The connection keeps count of the replies still owed: a call whose receive timed out or was cancelled (or whose reply it couldn't allocate) leaves its reply owed, and the next call first receives and drops that reply (at most one is owed, since every call drops it first; anerror.Abandonedin its place counts too), within its own timeout and before it sends. A call that had to drop an owed reply first sends its request only if its timeout has time left: once a deadline or a positive duration has run out, it returnserror.Timeoutwithout sending, so no server sees that request; with a duration of zero no step waits, and it sends if there is room. After a call that gave up, a plain receive returns that call's late reply (orerror.Abandoned) first: replies come in request order. A call returnserror.Invalidwhile a request sent withsend(orcommit) still has its reply to be received: that reply is yours, so receive it first; on any connection other than a service's client it returnserror.Invalidtoo. A call sends and receives, so no other thread may send or receive on the connection meanwhile.- A server never waits for a client. A request takes room in its client's ring for itself and for its reply: its length rounded up to 64 bytes, plus
max_messagerounded up to 64, from its send until its reply has been received. A client that keeps sending without receiving waits for room its own replies hold. - A server holds one request at a time: until it replies, its receives return
error.Invalid. It may receive on one thread and reply on another. A pool is several servers, each aConnof its own; each takes the next request of any client. A reply to a client that has left is dropped and succeeds. - A request is answered once: by its reply, or with
error.Abandonedif the server that took it ended before it replied. An abandoned request may or may not have been carried out; the service never sends it again, and the client decides whether to. Not covered: a server that hangs while its process runs (there are no timeouts), and the owner's process, with which the service ends. - The
Serviceis the service: itscloseends it. Then clients' sends and servers' receives geterror.Disconnected, and a client receives the replies that came before the end, in order, thenerror.Disconnected. A member's close never ends the service.
Reference
src/zig/root.zig| Call | What it does |
|---|---|
streamCreate(gpa, name, capacity) | Server: claims the name for a stream and listens on it with rings of capacity bytes, and returns its Listener. Doesn't wait. error.AddrInUse if another stream, broadcast, queue or service holds the name. |
listener.accept(timeout) | Waits for a client, sets its connection up on the calling thread and returns its Conn. One client at a time: close it before the next accept. A zero timeout polls. |
listener.cancel() | Every accept that waits, now or later, returns error.Cancelled, dropping a client whose setup began. Any thread. |
listener.close() | Stops listening and frees the listener; the connections it accepted stay open. |
streamConnect(gpa, name, timeout) | Client: connects to the stream on the name, waiting up to the timeout for a server to create it and accept. |
broadcastCreate(gpa, name, capacity, max_subscribers, max_stall_ms) | Publisher: claims the name for a broadcast and returns the publisher's Conn, whose sends go to every subscriber; its process admits the subscribers on a task of its own. publisher.close() ends the broadcast and frees the name. Doesn't wait. |
broadcastJoin(gpa, name, role, timeout) | Subscriber: joins the broadcast on the name as .lossy or .gated (misses nothing; SubscriberRole), waiting up to the timeout for the publisher to create it, to have a free slot and to admit it; it receives the messages sent from its admission on. |
publisher.waitForMembers(at_least, timeout) | A publisher's count of its joined subscribers, waiting until at least at_least are; 0 returns the count at once, also after an error.Timeout or error.Cancelled. Any thread. |
queueCreate(gpa, name, capacity, max_producers, max_consumers) | Owner: claims the name for a work queue and returns its Queue; its process admits the members on a task of its own. Doesn't wait. |
queueJoin(gpa, name, role, timeout) | Producer or consumer: joins the work queue on the name as .producer or .consumer (QueueRole), waiting up to the timeout for the owner to create it, for a free place and to admit it. The owner joins its own queue this way too, from any thread. |
queue.waitForMembers(role, at_least, timeout) | The owner's count of the joined members of role (null: both), waiting until at least at_least are; error.Disconnected once the queue has ended. Any thread. |
queue.cancel() | Every waitForMembers that waits, now or later, returns error.Cancelled; the queue goes on. Any thread. |
queue.close() | Ends the queue (producers get error.Disconnected; consumers receive what is left, then error.Disconnected) and frees it; the members' connections stay open until their close. |
serviceCreate(gpa, name, capacity, max_message, max_clients, max_servers) | Owner: claims the name for a service and returns its Service; its process admits the members on a task of its own. Doesn't wait. |
serviceJoin(gpa, name, role, timeout) | Client or server: joins the service on the name as .client or .server (ServiceRole), waiting up to the timeout for the owner to create it, for a free place and to admit it. The owner joins its own service this way too, from any thread. |
service.waitForMembers(role, at_least, timeout) | The owner's count of the joined members of role (null: both), waiting until at least at_least are; error.Disconnected once the service has ended. Any thread. |
service.cancel() | Every waitForMembers that waits, now or later, returns error.Cancelled; the service goes on. Any thread. |
service.close() | Ends the service (clients' sends and servers' receives get error.Disconnected; clients receive the replies already sent, then error.Disconnected) and frees it; the members' connections stay open until their close. |
conn.send(message, timeout) | Sends one message of any size, in pieces if needed. |
conn.recv(buf, timeout) | Receives the next message into buf and returns its length; error.TooLarge if it doesn't fit (it stays queued). |
conn.recvAlloc(gpa, timeout) | Receives the next message into memory from gpa, which you free. |
conn.call(gpa, request, timeout) | A service's client: sends the request and returns its reply, in memory from gpa, which you free; error.Abandoned if its server ended before it replied. It never returns another request's reply (above). |
conn.acquire(len, timeout) · conn.commit(len) | Zero-copy send: room for len bytes in the ring · sends the first len bytes written there as one message. |
conn.recvAcquire(timeout) · conn.release() | Zero-copy receive: the next message in place · frees its room. |
conn.lostMessages() | A lossy broadcast subscriber's count of the messages its last error.Overrun lost: at least 1; 0 before the first, and on any other connection. |
conn.maxPiece() | The longest message the zero-copy calls take: the capacity less 64 bytes; a broadcast's or a queue's, a quarter of its capacity, its longest message; a service's, its max_message. |
conn.cancel() | Every call of the connection that would wait, now or later, returns error.Cancelled. Any thread. |
conn.ended() | Whether the peer ended the session, the publisher closed (a subscriber's), or the queue or the service ended (a member's); its calls report error.Disconnected once nothing is left to receive. |
conn.close() | Ends the connection (the peer's calls report error.Disconnected), unmaps its rings and frees it. No other thread may be inside a call on it. |