Skip to content

feat(ipc): .ipc.on.* connection hooks#216

Merged
singaraiona merged 2 commits into
masterfrom
feat/ipc-on-hooks
May 27, 2026
Merged

feat(ipc): .ipc.on.* connection hooks#216
singaraiona merged 2 commits into
masterfrom
feat/ipc-on-hooks

Conversation

@singaraiona
Copy link
Copy Markdown
Collaborator

@singaraiona singaraiona commented May 27, 2026

Replaces v1's two-letter port hooks with five descriptive hooks under .ipc.on.* plus a read-only .ipc.handle:

Name Signature Fires
.ipc.on.open (fn [h] ...) Inbound connection fully handshaked (and authed if -u/-U), before first header read.
.ipc.on.close (fn [h] ...) Inbound connection about to close, before fd/state teardown. Pairs 1-to-1 with on.open.
.ipc.on.sync (fn [m] ...) Sync request; return value becomes the response.
.ipc.on.async (fn [m] ...) Async message; return ignored, errors logged.
.ipc.on.auth (fn [u p] ...) After secret-compare passes in -u/-U auth. Truthy = accept, falsy = reject. Can only narrow.
.ipc.handle (.ipc.handle) Current handle inside any hook, -1 outside.

Install

Plain set / :, through a narrow allow-list carved out of ray_sym_is_reserved:

(set .ipc.on.open  (fn [h] (println "+ " h)))
(set .ipc.on.sync  (fn [m] (eval (parse m))))
(set .ipc.on.auth  (fn [u p] (!= u "ban")))

Only these five sym IDs are user-settable. Everything else under .ipc.* (open, close, send, handle) and every other dotted system namespace stays unsettable. Clearing a hook is (set .ipc.on.X 0) — anything that isn't a callable lambda is treated as "no hook installed".

Wiring

src/core/ipc.c covers both server paths:

  • Poll: ipc_read_handshake (no-auth ready), ipc_read_creds (post-auth ready + on.auth narrow), ipc_on_close, ipc_read_payload (sync/async dispatch + handle scope).
  • Legacy: conn_on_handshake, conn_on_creds, conn_close, conn_on_payload.

Lifecycle on.close fires only for connections that completed handshake, keeping the on.open / on.close pair balanced for the user. Hooks inherit the connection's restricted-mode flag — a .ipc.on.sync installed on a -U server cannot escalate.

Sym-cache lifetime

Hook sym IDs live in one place (g_ipc_hook_syms[] in env.c, accessed via ray_sym_ipc_hook(idx)) so a runtime destroy/recreate cycle (which tears the sym table down too) invalidates the cache exactly once. The IPC layer doesn't carry its own duplicate cache.

Deliberately not pre-interned in ray_env_init — single-char operator names need the low sym-ID slots so the resolve builtin's "slen<2 / operator-char" guard rejects them on small-int columns. Stealing IDs 1-5 for long dotted names silently promoted [1 2 3] columns to SYM (caught by print_resolve.rfl).

Per-hook error handling

Hook Lambda error
on.open / on.close Logged to stderr, swallowed.
on.sync Serialised and shipped as the response (same as raw eval error).
on.async Logged and dropped — no wire response on async anyway.
on.auth Treated as reject; same 0x01 byte the wrong-password path uses.

Tests

  • ipc/hooks_lifecycle — end-to-end open → sync → close round-trip on the legacy path; reads per-hook counters and .ipc.handle back through the global env.
  • ipc/hooks_auth_narrow — poll-path -u server with an .ipc.on.auth that rejects one username; proves the hook can selectively narrow rather than blanket-reject.
  • rfl/system/reserved_namespace — positive set/let cases for the five hook names + negatives ensuring the rest of .ipc.* and the new .ipc.handle stay reserved.

make test: 2847 of 2850 passed, 3 skipped, 0 failed (3 skipped pre-existed).

Docs

New "Connection Hooks" section in website/docs/ipc.html plus rows in website/docs/reference.html.

🤖 Generated with Claude Code

singaraiona and others added 2 commits May 27, 2026 17:50
Replaces v1's two-letter port hooks with five descriptive hooks under
.ipc.on.* plus a read-only .ipc.handle:

  .ipc.on.open  {[h] ...}       inbound conn fully handshaked
  .ipc.on.close {[h] ...}       inbound conn about to close
  .ipc.on.sync  {[m] ...}       sync request; return = response
  .ipc.on.async {[m] ...}       async message; return ignored
  .ipc.on.auth  {[u; p] ...}    narrows -u/-U auth (truthy = accept)
  (.ipc.handle)                 current handle inside any hook, -1 else

Hooks install via plain set / : through a narrow allow-list carved out
of ray_sym_is_reserved — only these five sym ids are user-settable;
everything else under .ipc.* (open/close/send/handle) stays unsettable,
as do all other dotted system namespaces.

Wiring lives in src/core/ipc.c covering both server paths:
  - Poll: ipc_read_handshake (no-auth ready), ipc_read_creds
    (post-auth ready + on.auth narrow), ipc_on_close,
    ipc_read_payload (sync/async dispatch + handle scope).
  - Legacy: conn_on_handshake, conn_on_creds, conn_close,
    conn_on_payload, with matching semantics.

Lifecycle on.close fires only for connections that completed handshake,
keeping the on.open / on.close pair balanced for the user.  Sync hook
errors flow back to the client as the response; async hook errors are
logged and dropped (no wire response on async); on.auth errors are
treated as reject; lifecycle hook errors are logged and swallowed.

Hook lookup goes through env.c's central sym cache (ray_sym_ipc_hook
getter) so a runtime destroy/recreate cycle invalidates the IDs in one
place and the IPC layer doesn't carry its own duplicate cache.

Tests: ipc/hooks_lifecycle drives an end-to-end open → sync → close
round-trip and reads back per-hook counters and .ipc.handle through the
global env.  ipc/hooks_auth_narrow exercises a hook that selectively
rejects one username on top of -u password auth.  rfl/system/
reserved_namespace gets positive cases for set/let on the five hook
names and negative cases ensuring the rest of .ipc.* and the new
.ipc.handle stay reserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New 'Connection Hooks' section in ipc.html with the hook table,
install/clear semantics, the carve-out note, and per-hook error
behaviour.  Five new rows in reference.html covering .ipc.handle
and the five hook slots.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@singaraiona singaraiona merged commit e81f035 into master May 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant