Skip to content

Add opt-in client/server mode to the rdx executable#869

Open
paracycle wants to merge 1 commit into
uk_add_cypher_query_enginefrom
uk-add-client-server-for-cli
Open

Add opt-in client/server mode to the rdx executable#869
paracycle wants to merge 1 commit into
uk_add_cypher_query_enginefrom
uk-add-client-server-for-cli

Conversation

@paracycle

@paracycle paracycle commented Jun 18, 2026

Copy link
Copy Markdown
Member

What

Adds an opt-in resident, per-workspace server to the rdx executable. The server keeps an indexed + resolved Rubydex::Graph in memory so repeated rdx query invocations skip the index/resolve cost. Without --server (or on platforms lacking fork/UNIX sockets) rdx query runs inline exactly as before.

Stacked on top of #868. Review/merge that first.

This is the first step of a larger effort to turn rdx into a client/server tool (similar in spirit to the RuboCop and Spring servers), so that the expensive indexing + resolution work is paid once and reused across commands. It is intentionally scoped to the foundations: the runtime/IPC plumbing and a working server for the read-only query path. It's pure Ruby on top of the existing gem API — no Rust/C changes.

CLI surface

  • rdx query <CYPHER> [--server] [--format ...] — opt into the resident server for a query (otherwise runs inline).
  • rdx server <start|stop|restart|status> [--no-detach] — manage the resident server for the current workspace.
  • Env: DISABLE_RDX_SERVER (force inline) and RDX_SERVER_DIR (override the runtime directory).

How

  • lib/rubydex/cli.rb — extracts all command/option parsing and subcommand dispatch into a Rubydex::CLI class, keeping exe/rdx a thin shim. Heavyweight requires (rubydex, rubydex/server) stay deferred into the handlers so paths that don't need the native extension stay cheap to start.
  • lib/rubydex/server/cache.rb — per-workspace runtime dir keyed by an app-id hashed from the workspace path, Ruby/rubydex version and a native-extension fingerprint (a gem upgrade forces a fresh server). Manages pid/token/socket/version files with 0700/0600 perms.
  • lib/rubydex/server/request.rb — length-prefixed JSON framing + an IO.select handshake read timeout.
  • lib/rubydex/server/core.rb — UNIX socket accept loop with a version-line gate, constant-time token auth, mutex-guarded dispatch (query/status/stop) and per-request mtime-manifest staleness (reindex changed / delete_document removed / resolve).
  • lib/rubydex/server/client.rb — start-if-absent (fork + Process.daemon, flock single-start, spawn-and-poll readiness), version handshake, cold/warm recovery, graceful stop with SIGTERM fallback.
  • lib/rubydex/server/commands.rb — implements the rdx server start|stop|restart|status actions.

The socket is bound/connected via a relative name from its directory so long runtime paths stay under the sockaddr_un ~104-byte limit.

What's coming next

This PR deliberately keeps the scope small. Follow-up PRs will build on this foundation:

  • Warm rdx console sessions served from the resident graph. An IRB session needs the client's real terminal, so this requires forking a per-session worker, passing the client's STDIN/STDOUT/STDERR file descriptors to it, and forwarding signals — landing a REPL that starts instantly against the already-built graph.
  • Always-fresh graph via a background file watcher. Instead of the per-request staleness check this PR uses, a watcher thread will apply incremental updates as files change, plus idle-timeout self-shutdown.

Testing

  • Unit tests for Cache (app-id stability, perms, version compare, stale cleanup) and the request framing.
  • Integration tests that spawn a real server subprocess: warm query, staleness on add/delete, inline-equivalence, start/status/stop, restart. POSIX-only assertions skip on Windows (where server mode is unsupported).
  • bundle exec rake ruby_test — all Ruby tests pass; RuboCop clean.

@paracycle paracycle requested a review from a team as a code owner June 18, 2026 23:20
@paracycle paracycle force-pushed the uk-add-client-server-for-cli branch 2 times, most recently from 6967765 to 3bf6c65 Compare June 18, 2026 23:37
@paracycle paracycle force-pushed the uk_add_cypher_query_engine branch 2 times, most recently from 49ed15d to 13f37ec Compare June 19, 2026 00:22
@paracycle paracycle force-pushed the uk-add-client-server-for-cli branch from 3bf6c65 to 59e9e4b Compare June 19, 2026 00:27
Introduce a resident, per-workspace server that keeps an indexed +
resolved Rubydex::Graph in memory so repeated `--query` invocations skip
the index/resolve cost. Server mode is opt-in via `--server`; without it
(or on platforms lacking fork/UNIX sockets) `rdx` runs inline as before.

- lib/rubydex/server/cache.rb: per-workspace runtime dir keyed by an
  app-id hashed from the workspace, Ruby/rubydex version and a native-ext
  fingerprint; pid/token/socket/version files with restrictive perms.
- lib/rubydex/server/request.rb: length-prefixed JSON framing plus an
  IO.select handshake read timeout.
- lib/rubydex/server/core.rb: UNIX socket accept loop with a version-line
  gate, constant-time token auth, mutex-guarded dispatch and per-request
  mtime-manifest staleness (reindex changed / delete removed / resolve).
- lib/rubydex/server/client.rb: start-if-absent (fork + Process.daemon,
  flock single-start, spawn-and-poll readiness), version handshake,
  cold/warm recovery, graceful stop with SIGTERM fallback.
- lib/rubydex/server/commands.rb: --start/stop/restart/server-status.
- exe/rdx: new --[no-]server, --start/stop/restart-server,
  --server-status and --no-detach flags; queries route to the server when
  opted in, everything else stays inline.

Binds/connects the socket via a relative name from its directory so long
runtime paths stay under the sockaddr_un limit. Adds unit tests for the
cache and framing plus integration tests that spawn a real server.
@paracycle paracycle force-pushed the uk-add-client-server-for-cli branch from 59e9e4b to 7a54996 Compare June 19, 2026 00:30
@paracycle paracycle force-pushed the uk_add_cypher_query_engine branch 2 times, most recently from 2e6a202 to bc2a231 Compare June 23, 2026 21:16
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