| Platform | Build | Tests |
|---|---|---|
| Linux | ||
| macOS | ||
| Windows | ||
| FreeBSD | ||
Sintra is a C++17 library for type-safe interprocess communication on a single host. It lets independent processes exchange typed messages, broadcast events, and invoke RPC-style calls with a compile-time-checked API, avoiding string-based protocols and external brokers. It also provides coordination primitives like named barriers to structure multi-process workflows.
- Type-safe APIs across processes - interfaces are expressed as C++ types, so mismatched payloads are detected at compile time instead of surfacing as runtime protocol errors.
- Signal bus and RPC in one package - publish/subscribe message dispatch and synchronous remote procedure calls share the same primitives, allowing programs to mix patterns as the architecture requires.
- Header-only distribution - integrate the library by adding the headers to a project; no separate build step or binaries are necessary.
- Cross-platform design - shared-memory transport on Linux, macOS, Windows, and FreeBSD.
- Opt-in crash recovery - mark critical workers with
sintra::enable_recovery()so the coordinator automatically respawns them after an unexpected exit.
Typical use cases include plugin hosts coordinating work with out-of-process plugins, GUI front-ends that need to communicate with background services, and distributed test harnesses that must keep multiple workers in sync while exchanging strongly typed data.
Sintra targets x86/x64 and ARM/AArch64 CPUs. Other architectures are not supported; builds emit a warning and use a no-op spin pause (performance not guaranteed).
// Sender process: announce a shared struct Ping to everyone listening.
sintra::world() << Ping{};
// Receiver process: register a slot so cross-process Pings show up locally.
sintra::activate_slot([](const Ping&) {
sintra::console() << "Received Ping from another process" << '\n';
});struct Remotely_accessible: sintra::Derived_transceiver<Remotely_accessible>
{
std::string append(const std::string& s, int v) {
return std::to_string(v) + ": " + s;
}
SINTRA_RPC(append); // generates Remotely_accessible::rpc_append(...)
};// Remote exceptions thrown inside append() propagate back across the process boundary.
try {
sintra::console() << Remotely_accessible::rpc_append("instance", "Hi", 43) << '\n';
}
catch (const std::exception& e) {
sintra::console() << "Remote RPC failed in callee: " << e.what() << '\n';
}auto crash_monitor = sintra::activate_slot(
[](const sintra::Managed_process::terminated_abnormally& crash) {
sintra::console()
<< "Process "
<< sintra::process_of(crash.sender_instance_id)
<< " crashed with status " << crash.status << '\n';
},
sintra::Typed_instance_id<sintra::Managed_process>(sintra::any_remote));Sintra uses dedicated reader threads to process incoming messages from shared memory rings. When a message arrives:
- A reader thread pulls the message from the ring buffer.
- The reader thread invokes the matching slot or RPC handler asynchronously.
- Handlers (and their post-handler continuations) execute on the reader thread, not the thread that published the message or called the barrier.
Concurrency reminder: Slot handlers that touch shared state must still synchronize with other threads in the process (via mutexes, atomics, etc.). The barriers described below coordinate when handlers run; they do not eliminate the need for thread-safe data structures.
sintra::barrier() coordinates progress across processes and comes in three flavors that trade off strength for cost. The template defaults to delivery_fence_t, so a plain barrier("name") is already stronger than a bare rendezvous. Choose the lightest-weight barrier whose guarantees match the code's requirements:
- Rendezvous barriers (
barrier<sintra::rendezvous_t>(name)) simply ensure that every participant has reached the synchronization point. Messages published before the barrier might still be in flight or waiting to be handled, so use this mode when only aligned phase progression is needed-for example, coordinating the simultaneous start of a workload whose logic does not depend on the effects of earlier messages. - Delivery-fence barriers (
barrier(name)orbarrier<sintra::delivery_fence_t>(name)) guarantee that all pre-barrier messages have been pulled off the shared-memory rings by each process’s reader thread and are queued locally for handling, though the handlers may still be running. Reach for the default delivery fence when the next step requires the complete set of incoming work to be staged, such as inspecting an inbox before taking action. - Processing-fence barriers (
barrier<sintra::processing_fence_t>(name)) wait until every handler (and any continuations) for messages published before the barrier has finished executing. Choose this mode when subsequent logic must observe the completed side effects-for instance, reading shared state that earlier handlers updated or applying a configuration change only after all peers processed preparatory updates.
Delivery fences cost the same as rendezvous plus a short wait for readers to catch up. Processing fences add a single control message per process and an extra rendezvous to allow deterministic observation of handler side effects.
// Wait until everyone reaches the same point and any prior messages are queued locally.
sintra::barrier("phase-1"); // delivery fence
// Later: ensure the side effects from earlier messages are visible before reading shared data.
sintra::barrier<sintra::processing_fence_t>("apply-updates");Processing fences are safe to call from any thread, including handlers themselves: reader threads continue draining queued work and post-handlers while the fence waits, so invoking a fence from within a handler keeps the system making progress. When coordination between threads inside the same process is also required, combine Sintra barriers with standard threading primitives.
- Add the
include/directory to the project's include path. - Ensure that a C++17 compliant compiler is used (GCC, Clang, or MSVC are supported).
- Explore the
example/directory to see how to set up signal buses, channels, and remote call endpoints.
Because everything ships as headers, Sintra works well in monorepos or projects that prefer vendoring dependencies as git submodules or fetching them during configuration.
- macOS - Sintra always uses
os_sync_wait_on_addressfor its interprocess semaphore implementation. The build fails if<os/os_sync_wait_on_address.h>or<os/clock.h>is missing, so ensure the runner has macOS 15.0 or newer with the Command Line Tools for Xcode 15 (or newer) installed (the full Xcode IDE is not required). No legacy semaphore fallback is provided or supported.
The library includes a comprehensive test suite covering publish/subscribe, RPC,
barriers, and crash recovery. Tests are controlled by tests/active_tests.txt.
cmake -B build -DSINTRA_BUILD_TESTS=ON
cmake --build build
cd tests && python3 run_tests.py --build-dir ../build --config ReleaseSee TESTING.md for detailed documentation.
CI runs on Linux, macOS, Windows (GitHub Actions), and FreeBSD (Cirrus CI).
The source code is licensed under the Simplified BSD License.