digidem / digidem/comapeo-core-react-native

chore: recover from backend restarts via backend-owned project lifecycle (tracking)

Open
#247 2 comments 0 reactions 2 assignees View on GitHub

@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 (createServer accepts 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_LEFT semantics, and the notifyTransportReset/resubscribe recovery helpers (supersedes digidem/comapeo-ipc#87/digidem/comapeo-ipc#89).
  • #226 (this repo) — Kotlin socket reconnect with backoff and epoch-scoped teardown, transportStateChange into JS, the two-phase recovery wiring, subscribeToBackendRestart, and a boot nonce in the control channel's ready frame 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_LEFT error 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.