You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Signal bridge spawns a fresh signal-cli JVM every few seconds, forever: the receive loop runs receive --timeout 2 --max-messages 100 per poll (internal/signallive/client.go), and every send/reaction/metadata refresh is its own invocation too.
Up to ~5s message latency — inbound Signal messages wait for the next poll.
Head-of-line blocking — commandMu serializes every signal-cli call behind the in-flight poll (client.go), so sends/reactions/downloads regularly wait multi-second for an idle receive to come back.
Polls that overrun their context deadline get killed, wasting the whole cycle.
Fix: one long-lived signal-cli jsonRpc process
signal-cli's stdio JSON-RPC mode keeps a single JVM alive: envelopes stream in as receive notifications, and sends become RPC requests (send, sendReaction, sendTyping, sendSyncRequest, listContacts, listGroups, …).
Design notes from the investigation:
Envelope compatibility: jsonRpc notifications wrap the same envelope JSON that receive --output json emits per line ({"jsonrpc":"2.0","method":"receive","params":{"envelope":…,"account":…}}). handleReceiveOutput/processReceiveLine can be reused by unwrapping params — the WAL, recovery queue, and quarantine logic all stay.
Sends: replace per-call runSignalCLI invocations with request/response correlation over stdin/stdout (id → pending future, timeout per request). commandMu disappears entirely.
Pairing stays as-is:link runs before an account exists, so it remains a separate short-lived process.
Fallback: keep the polling path for signal-cli builds without jsonRpc (probe --version or first-spawn failure), selected automatically — not a user flag.
Soak plan: run against a live paired account for a day before release; watch the receive WAL for gaps and compare message counts with the phone.
Both #27's temp-dir confinement and this change were verified against signal-cli 0.14.1 (Homebrew, gradle launcher honoring SIGNAL_CLI_OPTS).
Problem
The Signal bridge spawns a fresh
signal-cliJVM every few seconds, forever: the receive loop runsreceive --timeout 2 --max-messages 100per poll (internal/signallive/client.go), and every send/reaction/metadata refresh is its own invocation too.Costs, measured during the #27 investigation:
javaprocess perpetually respawning in Activity Monitor) plus ~21MB of native-lib extraction per spawn (now confined and cleaned per Fix libsignal temp dir leak from signal-cli subprocess churn #28/Fix races: relocate signal-cli tmp root, join bridge goroutines on Close #30, but still written to disk every poll).commandMuserializes every signal-cli call behind the in-flight poll (client.go), so sends/reactions/downloads regularly wait multi-second for an idle receive to come back.Fix: one long-lived
signal-cli jsonRpcprocesssignal-cli's stdio JSON-RPC mode keeps a single JVM alive: envelopes stream in as
receivenotifications, and sends become RPC requests (send,sendReaction,sendTyping,sendSyncRequest,listContacts,listGroups, …).Design notes from the investigation:
receive --output jsonemits per line ({"jsonrpc":"2.0","method":"receive","params":{"envelope":…,"account":…}}).handleReceiveOutput/processReceiveLinecan be reused by unwrappingparams— the WAL, recovery queue, and quarantine logic all stay.goTracked/Close-join plumbing from Fix races: relocate signal-cli tmp root, join bridge goroutines on Close #30 already fits a long-lived child.runSignalCLIinvocations with request/response correlation over stdin/stdout (id → pending future, timeout per request).commandMudisappears entirely.linkruns before an account exists, so it remains a separate short-lived process.--versionor first-spawn failure), selected automatically — not a user flag.Both #27's temp-dir confinement and this change were verified against signal-cli 0.14.1 (Homebrew, gradle launcher honoring
SIGNAL_CLI_OPTS).