JavaScript
One Node-API addon for Node.js 22+, Bun and Deno 2: createStream and connectStream give streams, connections with copied and zero-copy messages; createBroadcast and joinBroadcast broadcasts; createQueue and joinQueue work queues; createService and joinService services, whose clients' requests each go to one of a pool of servers, which replies. Every call that may wait returns a Promise and leaves the event loop running; a Sync form waits on the calling thread. A call the library refuses fails with a FipcError whose code names the result. TypeScript types included; nothing is compiled when you install it. The package carries the addon and the native library for Windows x64, Linux x64 and ARM64 (glibc 2.34+) and macOS 14.4+ on Apple Silicon.
npm install fipc-jsServer and client
two processes, one nameStart the two in either order, in two terminals (node server.mjs, node client.mjs; bun and deno run -A run them as they are): connectStream waits up to its timeout, in milliseconds, for a server to create the stream and accept; a call without a timeout waits as long as it takes. The pair makes the exchange every example on this site makes, the message ping, answered in upper case: this client works with any language's server, and this server with any language's client.
import { createStream } from "fipc-js";
const listener = createStream("my_channel", 1 << 20); // rings of 1 MiB each way
const conn = await listener.accept(); // waits for a client; the event loop runs meanwhile
const message = await conn.receive();
await conn.send(message.toString().toUpperCase());
conn.close();
listener.close();import { connectStream } from "fipc-js";
const conn = await connectStream("my_channel", 5000); // waits up to 5 s for the server
await conn.send("ping");
console.log((await conn.receive()).toString()); // PING
conn.close();Messages
bytes of any size, no headerA connection carries messages: send takes a message of any size, a string (sent as UTF-8), a Buffer or any TypedArray, a DataView or an ArrayBuffer, and receive resolves to each one whole, as a new Buffer. A message longer than the ring travels in pieces and arrives whole. This server echoes every message back until its client leaves, which ends its next receive with FIPC_DISCONNECTED.
import { createStream } from "fipc-js";
// rings of 1 MiB each way: a power of two, 1 KiB to 2 GiB
const listener = createStream("demo", 1 << 20);
const conn = await listener.accept(); // waits for a client
for (;;) {
let message;
try {
message = await conn.receive();
} catch (error) {
if (error.code === "FIPC_DISCONNECTED") break; // the client is gone
throw error;
}
await conn.send(message); // echo, any size
}
console.log("client gone");
conn.close();
listener.close();import { connectStream } from "fipc-js";
const conn = await connectStream("demo", 5000);
for (const word of ["hello", "shared", "memory"]) {
await conn.send(word);
console.log((await conn.receive()).toString());
}
conn.close();Async and Sync calls
the event loop keeps runningA plain call (accept, connect, send, receive, receiveInto, ...) returns a Promise. It makes the call at once when it needn't wait (a message is there, the ring has room), which costs no more than the Promise; otherwise a thread of the connection's own, one per direction (a listener has one for accepting), waits in the library and settles the promise. libuv's thread pool is never used, so files, DNS and crypto never wait behind FastIPC, and no number of waiting calls starves it.
Calls of one direction run in the order they were made, so sends needn't be awaited one by one. Don't change a buffer you passed to send, or read one you passed to receiveInto, until its promise settles, and don't resize or transfer its ArrayBuffer meanwhile: the call uses the memory in place.
The Sync form (acceptSync, connectStreamSync, sendSync, receiveSync, ...) waits on the calling thread and blocks the event loop meanwhile: the fastest way to make many calls in a tight loop or in a Worker, and with NO_WAIT a way to poll. While an async call of a direction is pending, a Sync call of that direction fails with ERR_FIPC_BUSY.
const timer = setInterval(() => console.log("the event loop runs"), 100);
const message = await conn.receive(); // waits without blocking the timer
clearInterval(timer);// once a frame: take what has arrived, without waiting
for (;;) {
let message;
try {
message = conn.receiveSync(NO_WAIT);
} catch (error) {
if (error.code === "FIPC_TIMEOUT") break; // nothing more this frame
throw error; // "FIPC_DISCONNECTED": the peer is gone
}
handle(message);
}Errors and timeouts
FipcError, its codeA call the library refuses rejects (in the Sync form, throws) a FipcError: its code is "FIPC_TIMEOUT", "FIPC_DISCONNECTED", "FIPC_CANCELLED", "FIPC_TOO_LARGE", "FIPC_INVALID", "FIPC_NO_MEMORY", "FIPC_ADDR_IN_USE", on a broadcast "FIPC_OVERRUN" or "FIPC_UNSUBSCRIBED", and on a service's client "FIPC_ABANDONED" (the server that took the request ended before it replied), the C API's results (result is the number, call the method). A misuse is a TypeError or RangeError, or an Error with the code ERR_FIPC_CLOSED (a closed handle) or ERR_FIPC_BUSY. The timeout comes last, in milliseconds: NO_WAIT (0) doesn't wait, leaving it out (or FOREVER) waits as long as it takes. FIPC_DISCONNECTED is final: close the connection, then accept or connect a new one.
try {
handle(await conn.receive(100)); // waits at most 100 ms
} catch (error) {
if (error.code !== "FIPC_TIMEOUT") throw error; // "FIPC_DISCONNECTED": the peer is gone
}let buffer = Buffer.alloc(4096);
let length;
try {
length = await conn.receiveInto(buffer);
} catch (error) {
if (error.code !== "FIPC_TOO_LARGE") throw error;
buffer = Buffer.alloc(error.length); // the message stays queued: make room, then take it
length = await conn.receiveInto(buffer);
}Zero-copy
up to conn.maxPiece bytesWrite a message into the ring and commit it; read one in the ring and release it. sendAcquire resolves to a Uint8Array over room in the ring; sendCommit publishes its first bytes (all, or fewer). receiveAcquire resolves to a Uint8Array over the message, valid until receiveRelease, the next receive or close. When the bytes stop being valid the view is detached, its length 0, so a stale view can never touch the ring; one that can still be reached keeps its connection open. A message of several pieces fails with FIPC_TOO_LARGE and stays queued for receive. Making a view costs more than copying a short message, so in JavaScript zero-copy pays off for long ones.
const slot = await conn.sendAcquire(5); // room in the ring: a Uint8Array
slot.set(Buffer.from("hello"));
conn.sendCommit(5); // sent; the view is detachedconst view = await conn.receiveAcquire(); // the message in the ring
handle(view); // read it in place
conn.receiveRelease(); // frees its room; view.length is 0 from now onBatches
many messages per call, both endsconn.sendMany(messages, timeout) sends an array of messages (strings or bytes, copied when the call is made) in order and publishes them together, with at most one wake of the receivers, and resolves to how many went; the timeout bounds the whole call (NO_WAIT: only what fits now). Every message is checked before any goes. A failure is the first message that didn't go, with how many went in the error's sent.
conn.receiveMany(maxCount, timeout) waits for the next message, then takes every further one already there (up to maxCount, 64 KiB of them) and resolves to an array of Buffers. Both have a Sync form. A broadcast's subscriber takes those its publisher has moved past; a service's client sends a batch of requests (owed as its sends are) and receives their replies in order; a server's batches fail with FIPC_INVALID. Batch both ends: a receiver that takes one message per call falls behind a batching sender.
const sent = await conn.sendMany(["one", "two", "three"]); // published together
for (const message of await peer.receiveMany(64)) { // every message already there, up to 64
handle(message);
}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 (maxPiece). createBroadcast(name, capacity, maxSubscribers, maxStall) returns the publisher, a connection whose sends go to every joined subscriber and the broadcast's one handle. A subscriber joins with joinBroadcast, on a thread of its own, and the publisher's process admits it on a thread of the library's, so no call of the publisher's waits for it (the publisher's own thread may join too, even with joinBroadcastSync). The publisher waits for its subscriber (publisher.waitForMembers(1) resolves to the count once at least one has joined; at FIPC_TIMEOUT or FIPC_CANCELLED the error's count is the count) before it sends tick 1 to tick 3; closing the publisher ends the broadcast and frees its name. The subscriber joins gated (SUBSCRIBER_GATED), so it misses nothing (the publisher waits for it), and receives until FIPC_DISCONNECTED, which comes after the last message. A gated one that holds the publisher back for the broadcast's maxStall ms is unsubscribed (FIPC_UNSUBSCRIBED); leaving maxStall out never unsubscribes.
import { createBroadcast } from "fipc-js";
// 64 KiB of messages, up to 8 subscribers; one that holds the publisher back 5 s is dropped
const publisher = createBroadcast("my_broadcast", 1 << 16, 8, 5000);
await publisher.waitForMembers(1); // waits for the first subscriber; the event loop runs meanwhile
for (let i = 1; i <= 3; i++) await publisher.send(`tick ${i}`);
publisher.close(); // ends the broadcast: subscribers get FIPC_DISCONNECTED after the ticksimport { SUBSCRIBER_GATED, joinBroadcast } from "fipc-js";
// gated: misses nothing (the publisher waits for it)
const subscriber = await joinBroadcast("my_broadcast", SUBSCRIBER_GATED, 5000);
for (;;) {
let tick;
try {
tick = await subscriber.receive();
} catch (error) {
if (error.code === "FIPC_DISCONNECTED") break; // the publisher closed
throw error;
}
console.log(tick.toString()); // tick 1, tick 2, tick 3
}
subscriber.close();A lossy subscriber (SUBSCRIBER_LOSSY) never holds the publisher back. One that falls a lap behind fails a receive with FIPC_OVERRUN instead of a message, the number of messages it lost in error.lost (a number, exact up to 253), and the next receive goes on from a recent message.
Subscribers receive copies (receive, receiveInto); receiveAcquire fails with FIPC_INVALID on them: the publisher may unsubscribe a gated one at any moment, which makes the memory of a message held in place inaccessible, and reading a view then would crash the process. The publisher may publish in place.
try {
handle(await subscriber.receive());
} catch (error) {
if (error.code !== "FIPC_OVERRUN") throw error; // "FIPC_DISCONNECTED": the broadcast ended
console.log(`${error.lost} messages lost`); // fell a lap behind: the next receive goes on from a recent message
}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 (maxPiece), 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 owns the queue (createQueue(name, capacity, maxProducers, maxConsumers) returns it) and joins it as a producer (joinQueue with PRODUCER): the owner's process admits every joiner on a thread of the library's, its own included, so one thread can own a queue and produce, even with the Sync calls. It sends job 1 to job 3, which wait in the queue, then waits for the consumer (queue.waitForMembers(CONSUMER, 1); a role left out, or 0, counts both), so the two start in either order. Closing the queue ends it: the consumer receives what is left, then FIPC_DISCONNECTED, and a producer's sends fail with FIPC_DISCONNECTED from then on; a producer's or a consumer's close never ends it. The garbage collector never closes a queue: a queue lives until close() or the end of its process (of its worker thread, for one a Worker created), and dropping the object doesn't end it. A consumer receives copied or in place (receiveAcquire).
import { CONSUMER, PRODUCER, createQueue, joinQueue } from "fipc-js";
// 4 KiB of messages per producer, 1 producer and 1 consumer: this process owns the queue and is its producer
const queue = createQueue("my_queue", 4096, 1, 1);
const producer = await joinQueue("my_queue", PRODUCER); // this process admits it, as any member
for (let i = 1; i <= 3; i++) await producer.send(`job ${i}`); // waits in the queue until a consumer takes it
await queue.waitForMembers(CONSUMER, 1); // waits for the consumer to join; the event loop runs meanwhile
producer.close();
queue.close(); // ends the queue: the consumer gets FIPC_DISCONNECTED after the jobsimport { CONSUMER, joinQueue } from "fipc-js";
const consumer = await joinQueue("my_queue", CONSUMER, 5000);
for (;;) {
let job;
try {
job = await consumer.receive();
} catch (error) {
if (error.code === "FIPC_DISCONNECTED") break; // the queue ended
throw error;
}
console.log(job.toString()); // job 1, job 2, job 3
}
consumer.close();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 |
|---|---|---|---|
receive() or receiveInto() (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 |
receiveAcquire(), then receiveRelease() at once, then the work | at the release, before the work | as above | at most once: no duplicates |
receiveAcquire(), the work, then receiveRelease() | 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 the reply goes back to the client that sent it. A server waits for the requests of every client at once (one receive), and the servers of a pool share the load: each takes the next request of any client. Each client has a ring of its own, which holds its requests and the room for their replies, so a server never waits for a client. Requests and replies are of one piece, 1 to maxMessage bytes, the owner's choice (maxPiece). Here the server creates the service (createService(name, capacity, maxMessage, maxClients, maxServers) returns it), joins it as its server (joinService with SERVER: the owner's process admits every joiner on a thread of the library's, its own included, so one thread can own a service and serve) and answers three requests with their uppercase. The client joins (CLIENT) and calls ping 1 to ping 3: call sends a request and resolves to its reply. The service's close() ends it: clients' sends and servers' receives fail with FIPC_DISCONNECTED, and a client receives the replies that came before the end, then FIPC_DISCONNECTED; a client's or a server's close never ends the service. The garbage collector never closes a service: a service lives until close() or the end of its process (of its worker thread, for one a Worker created), and dropping the object doesn't end it.
import { SERVER, createService, joinService } from "fipc-js";
// 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 = createService("my_service", 4096, 256, 1, 1);
const server = await joinService("my_service", SERVER, 5000); // this process admits it, as any member
for (let i = 0; i < 3; i++) { // three requests, then the end
const request = await server.receive(); // the next request of any client
await server.send(request.toString().toUpperCase()); // the reply goes to its client, and never waits
}
server.close();
service.close(); // ends the service: the client still receives the replies sentimport { CLIENT, joinService } from "fipc-js";
const client = await joinService("my_service", CLIENT, 5000);
for (let i = 1; i <= 3; i++) {
// sends the request and resolves to its reply; FipcError FIPC_ABANDONED if the server ended before it replied
const reply = await client.call(`ping ${i}`);
console.log(reply.toString()); // PING 1, PING 2, PING 3
}
client.close();- A client's replies come in the order of its requests.
send(request)sends a request, and a receive (receive,receiveInto, or in placereceiveAcquireuntilreceiveRelease) resolves to the reply to its oldest request whose reply it hasn't received yet, whichever server replies first. A client may send several requests before it receives (as many as its ring has room for: each holds its length rounded up to 64 bytes, plusmaxMessagerounded up to 64, until its reply has been received), 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. client.call(request, timeout)never resolves to another request's reply. A call checks its request first:FIPC_INVALIDfor an empty one,FIPC_TOO_LARGEovermaxPiece, aTypeErrorfor another type, at once, even while a reply is owed. Then a call on a cancelled connection fails withFIPC_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 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; anFIPC_ABANDONEDin its place counts too), within its own timeout and before it sends. A call that had to drop an owed reply or wait for its turn first sends its request only if its timeout has time left: once a positive timeout has run out, it fails withFIPC_TIMEOUTwithout sending, so no server sees that request; withNO_WAITno step waits, and it sends if there is room. After a call that gave up, a plainreceivereturns that call's late reply (orFIPC_ABANDONED) first: replies come in request order. A call fails withFIPC_INVALIDwhile a request sent withsend(orsendCommit) 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 fails withFIPC_INVALIDtoo.callSynccalls on the calling thread.- Calls run one at a time, in the order they were made: a call made while another is pending waits for its turn, its timeout counting from when it was made: it fails with
FIPC_TIMEOUTif that runs out first (and withFIPC_CANCELLEDat acancelor aclose), and the calls behind it keep their order.Promise.allof several calls resolves to each one's own reply. Meanwhile the client's other sends and receives fail withERR_FIPC_BUSY, and so does a call while an async receive of yours is pending: either would race the call for its reply. - A server holds one request at a time.
receivetakes the next request of any client (each client's in the order it sent them), andsend(reply)replies to it and never waits; until then its receives fail withFIPC_INVALID, and a reply with no request held fails withFIPC_INVALID. A request received in place stays valid until a reply (sendorsendCommit) gives it up, which detaches its view, so the reply may be written from it; a reply refused with the request still held (empty, too large, or after the service's end) leaves the view until the close;receiveReleasedoes nothing. A reply while an async receive of the server's is pending fails withERR_FIPC_BUSY. A reply to a client that has left is dropped. FIPC_ABANDONED: a client's receive (or call) fails withFIPC_ABANDONEDwhen the server that took the request ended before it replied: it closed its connection, or its process (or worker thread) ended. The request counts as answered, and the next receive returns the next reply. An abandoned request may or may not have been carried out; the service never sends it again, and the client decides whether to.- The owner's service:
service.waitForMembers(role, atLeast, timeout)resolves to the number of members of the role (CLIENTorSERVER; left out, or 0, both) joined once there are at leastatLeast;service.cancel()makes its waits fail withFIPC_CANCELLED. Clients wait while no server is joined, and servers while no client is. - Not covered: a server that hangs while its process runs (there are no timeouts, by design: its client waits, within its own timeouts); the owner's process, with which the service ends.
let reply;
try {
reply = await client.call(request);
} catch (error) {
if (error.code !== "FIPC_ABANDONED") throw error; // "FIPC_DISCONNECTED": the service ended
reply = await client.call(request); // its server ended before it replied: send it again, if it may run twice
}Any language on the other end
one protocolThe server.mjs and client.mjs above work with a peer in any of the ten languages: every pair of languages is tested. A Node.js process can hand CPU-heavy work to a worker written in Rust, C++ or Python, or serve as the front end of a native engine, without a socket.
Lifetimes and worker threads
close, cancel, the garbage collectorconn.cancel() makes every call of the connection that waits, then or later, fail with FIPC_CANCELLED. conn.close() ends the connection: pending calls fail with FIPC_CANCELLED, every view is detached, and the peer gets FIPC_DISCONNECTED once it has received what this side sent. listener.close() stops listening, frees the name and leaves the connections it accepted open. Both work with using (Symbol.dispose), as a queue and a service do.
A listener or connection the program drops is closed when the garbage collector finalizes it; those still open when a worker thread or the process ends are closed then. A queue or a service is the exception: the collector never closes it, since its use lives in its members, so it lives until close() or that end. An idle connection doesn't keep the process alive; a pending call does, until it settles, as a socket would.
A listener, queue, service or connection belongs to the JavaScript thread that made it, the main thread or a Worker: each worker opens connections of its own. A client and its server may even run in one process, since await listener.accept() and await connectStream(name) each wait on a thread of their own. There is no heartbeat and no timeout: a paused or hung peer is not reported, a crashed or killed one as soon as its process has ended.
Node.js, Bun and Deno
Node-API 8 · x86-64-v3 · ARM64 Linux · Apple SiliconThe binding is one Node-API addon, the interface all three runtimes load, so one package serves them: Node.js 22 or newer, Bun, and Deno 2 (which needs --allow-ffi to load an addon, and --allow-read and --allow-env for the binding to find it). The package carries the addon and the native library for every platform in prebuilds/, picks the right one when it loads, and runs no install script. Types come with it, for import and for require.
The binding looks for its addon and the library in the folder FASTIPC_LIB_DIR names, then (in a checkout of the repository) its zig-out build, then the package's prebuilds/<platform>-<arch>/, and for the library last, the system's search. fipc.library and fipc.addon say which files it loaded; the library stays loaded until the process ends.
node client.mjs
bun client.mjs
deno run --allow-ffi --allow-read --allow-env client.mjsconst { createStream, connectStream } = require("fipc-js");Reference
index.d.ts| Call | What it does |
|---|---|
createStream(name, capacity) | Claims the name for a stream: its listener; FIPC_ADDR_IN_USE if another stream, broadcast, queue or service holds it. |
listener.accept(timeout) · acceptSync(timeout) | Sets the next client's connection up: a Connection. One client at a time: close it before the next accept. acceptSync(NO_WAIT) polls. |
listener.cancel() · close() · name · closed | Cancel waiting accepts · stop listening and free the name; accepted connections stay open. |
connectStream(name, timeout) · connectStreamSync | Connects to the server of the stream on the name, waiting for it to create the stream and accept. |
conn.send(data, timeout) · sendSync | Sends one message of any size: a string, a TypedArray, a DataView or an ArrayBuffer. |
conn.receive(timeout) · receiveSync | Receives one message of any size, as a new Buffer. |
conn.receiveInto(buffer, timeout) · receiveIntoSync | Receives into your buffer; its length. FIPC_TOO_LARGE (with error.length) if it doesn't fit. |
conn.sendMany(messages, timeout) · receiveMany(maxCount, timeout) · sendManySync · receiveManySync | Batches: send an array of messages, published together, and resolve to how many went (on a failure, the error's sent) · receive every message already there, up to maxCount, as an array of Buffers. Batch both ends. |
conn.sendAcquire(length, timeout) · sendAcquireSync · sendCommit(length) | Zero-copy send: a Uint8Array over the ring, then publish. |
conn.receiveAcquire(timeout) · receiveAcquireSync · receiveRelease() | Zero-copy receive: a Uint8Array over the message, then free its room. |
conn.maxPiece · ended · closed · cancel() · close() | The zero-copy limit · whether a call failed with FIPC_DISCONNECTED · whether it is closed · cancel waiting calls · end the connection. |
createBroadcast(name, capacity, maxSubscribers, maxStall) | Claims the name for a broadcast: the publisher, a connection whose sends go to every joined subscriber. Closing it ends the broadcast. |
joinBroadcast(name, role, timeout) · joinBroadcastSync | Joins the broadcast as SUBSCRIBER_LOSSY or SUBSCRIBER_GATED: a connection that receives copies. The publisher's process admits it by itself. |
publisher.waitForMembers(atLeast, timeout) · waitForMembersSync | The number of subscribers joined, once at least atLeast are (error.count at a timeout or a cancel). |
createQueue(name, capacity, maxProducers, maxConsumers) | Claims the name for a work queue: a Queue. It lives until close() or the end of its process; the garbage collector never closes it. |
joinQueue(name, role, timeout) · joinQueueSync | Joins the queue as a PRODUCER, which sends, or a CONSUMER, which receives each message no other consumer took, then FIPC_DISCONNECTED once the queue has ended. The owner's process admits it by itself. |
queue.waitForMembers(role, atLeast, timeout) · waitForMembersSync | The number of members of the role joined (undefined or 0: both roles), once at least atLeast are (error.count at a timeout, a cancel or the queue's end). |
queue.cancel() · close() · name · closed | Cancel waiting waitForMembers calls · end the queue: producers get FIPC_DISCONNECTED, consumers what is left, then FIPC_DISCONNECTED. |
createService(name, capacity, maxMessage, maxClients, maxServers) | Claims the name for a service: a Service. It lives until close() or the end of its process; the garbage collector never closes it. |
joinService(name, role, timeout) · joinServiceSync | Joins the service as a CLIENT, whose sends are requests and whose receives their replies, in order (or FIPC_ABANDONED), or a SERVER, which receives the next request of any client and replies with a send. The owner's process admits it by itself. |
client.call(request, timeout) · callSync | Sends a request and resolves to its reply; FIPC_ABANDONED if its server ended first. A call never resolves to another request's reply; calls run one at a time. |
service.waitForMembers(role, atLeast, timeout) · waitForMembersSync | The number of members of the role joined (undefined or 0: both roles), once at least atLeast are (error.count at a timeout, a cancel or the service's end). |
service.cancel() · close() · name · closed | Cancel waiting waitForMembers calls · end the service: clients' sends and servers' receives get FIPC_DISCONNECTED, clients the replies that came first, then FIPC_DISCONNECTED. |
NO_WAIT · FOREVER · SUBSCRIBER_LOSSY · SUBSCRIBER_GATED · PRODUCER · CONSUMER · CLIENT · SERVER | 0 · -1 · 1 · 2 · 3 · 4 · 5 · 6. |
FipcError · Result · resultStr(result) | The error (code, result, call, length, lost, count) · the results by name · the C name of one. |
library · addon · version | The paths of the library and the addon loaded · "1.0.0". |
Measured through this binding on Node.js 22, copied 16-byte messages run at 2.1 to 2.5 million per second between two processes through the async calls and about twice that through the Sync calls, and 64 KiB messages at 350,000 to 360,000 per second (details).