Search guides and the APICtrl K
  1. API reference
  2. System, devices and network

ipc.h

Processes talking to each other.

#include <sonore/ipc.h>
On this page

Design notes

NamedPipe                      a local, two-way, connection-oriented
                               channel with a name
InterprocessConnection         whole messages over a socket or a pipe,
                               delivered to callbacks on its own thread
InterprocessConnectionServer   accepts socket connections into them
ChildProcessCoordinator /      a worker process of the program's choosing,
ChildProcessWorker             launched and connected, pinged, and noticed
                               when it dies or hangs
NetworkServiceDiscovery        "who on this network offers service X":
                               UDP broadcast advertiser and listener

Why each is built the way it is

NamedPipe on Windows is a named pipe (\\.\pipe\name), overlapped so every read and write can be given a timeout and cancelled, and created with PIPE_REJECT_REMOTE_CLIENTS: a plugin's pipe is for this machine. On macOS and Linux it is a Unix-domain socket in the user's own runtime directory, owner-only -- NOT the pair of FIFOs the reference uses. A FIFO cannot say whether the process at the other end has died (its reader sees end-of-file both before the writer arrives and after it is gone, so the reference opens it for writing too, and then never sees an end at all), and a write to a FIFO whose reader has gone raises SIGPIPE, whose default is to end the process -- the host. A Unix socket is two-way, reports the peer's death as an end of stream at once, and is written with MSG_NOSIGNAL/SO_NOSIGPIPE.

InterprocessConnection frames each message as the reference does: a 32-bit magic number and a 32-bit length, little-endian, then the bytes -- so a process built with the reference that uses the same magic over a SOCKET can talk to one built with this. A frame with the wrong magic, or longer than the connection's limit (64 MB unless set), ends the connection: a length field from the wire is never trusted with an allocation.

ChildProcessCoordinator/Worker: the worker is launched (ChildProcess, process.h) with "<uniqueID>:<pipeName>" among its arguments, connects to the pipe the coordinator created, and both sides ping every quarter of the timeout. A worker that crashes is noticed at once (the pipe ends); one that hangs is noticed when it has been silent for the timeout. Each message carries a type byte in front of the caller's bytes, so pings can never be confused with a message that happens to look like one.

NetworkServiceDiscovery sends a small text datagram (not the reference's XML: there is no XML parser here to read it with, and a datagram from the network is untrusted input -- the parser is fuzzed). Advertisements expire when they stop arriving.

Threads

Every callback here -- connectionMade, messageReceived, connectionLost, the coordinator's and worker's handlers, a service list's change callback -- runs on the object's OWN thread, never a UI thread and never the audio thread: hand what arrives over (a UiEventQueue, a lock-free FIFO). A subclass must call disconnect() / stop() in its own destructor, before its members go, because the thread may be inside one of its overrides. Nothing here is for the audio thread.

Types 6

A two-way channel between two processes on this machine, found by name.

One side creates it (createNewPipe) and waits for the other to open it (openExisting). The creator serves one client at a time: when that client goes, its next read or write waits for another.

read() waits until the buffer is full or the timeout passes, as the reference's does; readSome() returns as soon as anything arrives, which is what a reader of framed messages wants. Both return -1 when the other side has gone (or on an error) with nothing read.

One thread may read while another writes. close() from any thread makes a blocked read or write return.

Whole messages between two processes, over a TCP socket or a NamedPipe.

Subclass it and implement the three callbacks; they run on the connection's own thread (see the header), which starts when the connection does. connectionMade() comes first -- for a created pipe, when a client arrives -- then messageReceived() per message, then connectionLost() once, whether the other side went or disconnect() was called. Call disconnect() in the subclass's destructor.

sendMessage may be called from any thread, including from inside messageReceived, and so may disconnect(). connectToSocket, connectToPipe and createPipe may not: from inside a callback they return false, and a client that reconnects when the link drops does it from a thread of its own.

Listens on a TCP port and hands each connection that arrives to a new InterprocessConnection from createConnectionObject(). The server does not own those connections: the subclass keeps them (and deletes them). Call stop() in the subclass's destructor.

Runs a worker process and talks to it.

struct Scanner : ChildProcessCoordinator {
  void handleMessageFromWorker(const std::vector<uint8_t>& m) override {...}
  void handleConnectionLost() override { ...it crashed or hung... }
  ~Scanner() override { killWorkerProcess(); }
};
scanner.launchWorkerProcess(currentModulePath() + "-scanner", "sonore-scan");

The worker is any executable that calls ChildProcessWorker::initialiseFromCommandLine with the same unique id -- often the same program, started in a "worker" mode by that argument. handleConnectionLost is for the UNEXPECTED: the worker exiting, crashing, or going silent past the timeout. killWorkerProcess does not call it.

The worker half: in the worker process's main(),

MyWorker worker;
if (worker.initialiseFromCommandLine(commandLine, "sonore-scan")) {
  ...run until handleConnectionLost says the coordinator has gone...
}

handleConnectionLost is called when the coordinator dies, hangs, or calls killWorkerProcess -- the worker should then exit.

Finding a service on the local network without knowing its address: an Advertiser broadcasts, an AvailableServiceList listens.

↑↓ to move↵ to openEvery public name in the headers, and every guide