digidem / digidem/comapeo-core-react-native
chore: recover from backend restarts via backend-owned project lifecycle (tracking)
@RangerMauve is already working on this.
Since Aug 31, 2026.
- Dominant language
- Kotlin
- Stars
- 1
- Forks
- 0
- Avg merge
- 8h 24m
- Merged PRs (30d)
- 9
Description
The problem
The :ComapeoCore foreground service runs in its own process, so Android can kill and restart it (low-memory killer, watchdog) while the app process lives on. On current released code that leaves the frontend permanently stuck: the data socket has no reconnect and no state signal into JS, in-flight RPC calls hang until a 30 s timeout, every event subscription goes permanently deaf (server-side rpc-reflector state is per-connection), and the control socket's drop latches the app into a fatal-error screen — which unmounts the very tree whose activity would have triggered a reconnect.
Underneath that sits a structural issue in @comapeo/ipc v9: project instance lifecycle is client-visible, via per-open instance ids minted from a per-connection counter. Five successive fix PRs (digidem/comapeo-ipc#47, digidem/comapeo-ipc#49, digidem/comapeo-ipc#61, digidem/comapeo-ipc#74, digidem/comapeo-ipc#83/digidem/comapeo-ipc#84) each patched a race the previous one introduced, and the design is unsound across a restart — a restarted (or merely reconnected) backend can remint the exact id a cached wrapper is bound to, so revalidation falsely passes. It also produced the leave/re-invite bugs (digidem/comapeo-mobile#2041, digidem/comapeo-mobile#2042).
The direction
Make the backend own instance lifecycle entirely. Per-project channels are keyed by the stable projectPublicId; client references are permanent; the backend closes and re-opens MapeoProject instances behind them invisibly (leave/re-join today, memory-pressure eviction later). With no client-visible instance identity, the whole class of stale-reference races disappears, and restart recovery reduces to two phases: reject pending calls at drop time, resubscribe once reconnected — no cache invalidation of references, no generation guards. The one client-visible lifecycle state is "you left this project", surfaced as a typed PROJECT_LEFT error.
The pieces
- digidem/rpc-reflector#55 — late-bound handlers: a server's handler becomes a replaceable plug-in behind a durable subscription registry (
createServeraccepts a factory;detachHandler/ensureHandler). This is the mechanism that lets instances swap behind a stable channel without the IPC layer inspecting wire frames. - digidem/comapeo-ipc#88 — v10: stable channels, permanent references, thin server host on late-bound handlers,
PROJECT_LEFTsemantics, and thenotifyTransportReset/resubscriberecovery helpers (supersedes digidem/comapeo-ipc#87/digidem/comapeo-ipc#89). - #226 (this repo) — Kotlin socket reconnect with backoff and epoch-scoped teardown,
transportStateChangeinto JS, the two-phase recovery wiring,subscribeToBackendRestart, and a boot nonce in the control channel'sreadyframe so a reconnect to a surviving backend is distinguished from a genuine restart (supersedes #225; carries its commits unchanged). - digidem/comapeo-core-react#202 — the hook layer: reset/refetch query caches on the restart signal, refresh sync stores (v10's permanent references removed the accidental recovery path), bounded query-only retry on the transport error code, survivable map-share streams.
- digidem/comapeo-core#1319 — core-level lifecycle hardening (tracking): the typed
PROJECT_LEFTerror that lets the IPC layer drop its interim leave logic, plus getProject races, close-event fixes, and related robustness issues (digidem/comapeo-core#1313–digidem/comapeo-core#1318).
Remaining work
comapeo-mobile must stop treating the transport drop as fatal (keep the tree mounted with a reconnecting affordance; wire subscribeToBackendRestart to the query reset and re-derive its module-scope captures). On-device e2e needs the published packages and COMAPEO_DEBUG_LIFECYCLE plumbed into the native start path. Out of scope for now, pending memory measurements and the hypercore 11 migration: boot-time re-opening of projects (including left projects whose leave record hasn't propagated) and the eviction policy.
Merge order is forced by dependencies: digidem/rpc-reflector#55 → release → digidem/comapeo-ipc#88 lockfile + merge → release → #226 and digidem/comapeo-core-react#202 lockfile bumps. Until the upstream releases, npm ci fails on the downstream branches by design.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.