Ivan FB db46bb9aa2
feat(examples): in-library chronos CBOR server + cross-platform IPC CI
Adds a standalone IPC example: the library serving itself over a CBOR socket.
examples/timer/ipc_chronos/serve.nim compiles into libmy_timer only under
-d:ffiIpcServe (every other build untouched) and runs a chronos socket server
that, per request, decodes CBOR at the socket edge and calls the library's own
async procs directly — native, in-process, zero serialization between the
socket and the logic, no FFI boundary, no callback bridge. Exposed as
my_timer_serve(address).

CBOR (not the native struct ABI) is correct at the wire here: a relay's data is
serialized regardless, so native would only relocate the decode and add
marshalling for no gain — native locally, CBOR for IPC.

serve_host.nim starts it; client.nim is a lib-free chronos client. Both use
chronos sockets, so the example builds and runs on Linux, macOS and Windows
over TCP (unix sockets are a POSIX bonus).

CI: tests/e2e/ipc/run_roundtrip.nim builds the dylib + host + client, spawns
the server and round-trips over loopback TCP asserting the replies; wired as
`nimble test_ipc` and a 3-OS CI matrix (ubuntu/macos/windows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 08:42:56 +02:00
..

IPC example — the library serves itself over CBOR (chronos)

When a caller lives in the same process as the library it uses the native ABI (zero serialization). When it lives in a different process — possibly on a different machine — there is no shared address space, so requests must be serialized. That is what CBOR is for: native locally, CBOR for IPC.

This example puts the socket server inside the library. With -d:ffiIpcServe, libmy_timer gains serve.nim, which:

  • runs a chronos socket server, and
  • for each request, decodes CBOR at the socket edge and calls the library's own async procs directly — a plain in-process call, native, with no serialization between the socket layer and the logic, and no FFI boundary or callback bridge inside the server. The server is the library.
remote client ──CBOR over socket──▶ serve loop ──direct Nim call──▶ timer procs
              (inter-process)                   (in-process, zero-serialization)

It speaks CBOR (not the native struct ABI) at the wire because over a socket the data is serialized regardless — a native ABI would only move the decode and add marshalling for no gain. CBOR-on-the-wire / direct-call-in-process is the right shape for a relay.

Files

File Role
serve.nim Compiled into libmy_timer under -d:ffiIpcServe; the chronos server + my_timer_serve(address).
serve_host.nim Tiny host that links the library and starts the server.
client.nim Lib-free chronos client (builds CBOR requests, reads replies).

The wire framing (network byte order, so endianness never matters):

request:  [u32 method_len][method][u32 payload_len][cbor payload]
response: [i32 ret       ][u32 resp_len][cbor response]

Run

It builds and runs on Linux, macOS and Windows (TCP is the portable transport; unix:<path> also works on POSIX).

# one-command, asserted round-trip over loopback TCP (this is what CI runs):
nimble test_ipc

Or by hand, from the repo root:

ext=$(case "$(uname -s)" in Darwin) echo dylib;; *) echo so;; esac)
nim c --app:lib --noMain --nimMainPrefix:libmy_timer -d:ffiIpcServe \
  -o:examples/timer/ipc_chronos/libmy_timer.$ext examples/timer/timer.nim
nim c --passL:-Lexamples/timer/ipc_chronos --passL:-lmy_timer \
  --passL:-Wl,-rpath,"$PWD/examples/timer/ipc_chronos" \
  -o:examples/timer/ipc_chronos/serve_host examples/timer/ipc_chronos/serve_host.nim
nim c -o:examples/timer/ipc_chronos/client examples/timer/ipc_chronos/client.nim

examples/timer/ipc_chronos/serve_host tcp:127.0.0.1:9099 &
examples/timer/ipc_chronos/client     tcp:127.0.0.1:9099

Expected client output:

[client] version    = nim-timer v0.1.0
[client] echo.echoed= hello over the wire
[client] echo.timer = ipc-server     # the server's own context state round-tripped

Notes

  • The serve loop fires the library's events (e.g. echoonEchoFired); it installs an empty event registry on its thread so dispatch finds zero listeners. Delivering events to remote clients is separate, future work.
  • A remote client needs only a CBOR codec, not the compiled library — it can be written in any language. client.nim is the Nim reference.