| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Replace bare `client = null` with dropClient(): stop the clightning-client
library's internal reconnect loop (clear reconnectTimeout, neuter reconnect,
destroy the socket, drop listeners). A dead client was otherwise orphaned and
spun forever retrying `connect ENOENT`, spamming logs and leaking a socket+timer
on every cl restart. Guard the error handler on `c === client` so a stale
client's late error can't drop a newer, healthy one.
Add lnListen.onDrop so the invoice listener recycles immediately when its
long-poll socket dies (bump epoch, re-arm) instead of waiting up to 5 minutes for
the stall watchdog. Stop counting LightningUnavailableError toward the listener's
process.exit escalation, so a cl restart no longer makes the app self-exit — it
retries until cl returns.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
On 2026-06-04 cl restarted (autoheal, after its 15s healthcheck timed out),
and the app's waitanyinvoice long-poll hung on the now-dead listen socket.
listenerActive stayed true forever, so ensureListenerAlive never restarted
it, and incoming lightning payments stopped crediting for ~2h until a manual
app restart. The per-call RPC timeout in ln.ts can't catch this: waitanyinvoice
is intentionally in NO_TIMEOUT (it's meant to block).
Fix: ensureListenerAlive (runs every 120s) now also detects a stalled listener
— active but blocked in waitanyinvoice for >5min. It probes cl via the main
ln socket (getinfo); only if cl is actually alive does it treat the listen
socket as a zombie, call lnListen.reset() (new: drops the proxy's socket so the
next call reconnects), and re-arm. A listenerEpoch guard makes the wedged
invocation bow out when its waitanyinvoice finally rejects, so we never run two
listeners concurrently and never double-credit. Verified with a state-machine
test covering normal receive, quiet-but-healthy (left alone), stall+cl-alive
(recycled), stall+cl-down (left alone), and the no-double-listener race.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A unix-domain RPC socket has no TCP keepalive, so if cl goes
unresponsive while the socket stays open (zombie/half-open), an awaited
call hangs forever — the "stuck spinner past /pay/bob, no error"
symptom. Nothing threw, so the existing reconnect-on-error path never
fired and only an app restart recovered it.
Wrap short-lived calls in a 30s Promise.race timeout (matching
health.ts). A timeout rejects with ETIMEDOUT, now part of isSocketDied,
so the client is dropped and the next call reconnects — no process
restart. Long-polls (waitanyinvoice et al), in-flight payments
(pay/xpay/sendpay/keysend), and bolt12 round-trips (fetchinvoice/
sendinvoice/offer) are exempt: timing those out client-side would risk
leaked debits or break intentional blocking.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|