| … | … | @@ -146,13 +146,22 @@ export class Client { |
| 146 | 146 | this._decoder.push(chunk) |
| 147 | 147 | } catch (err) { |
| 148 | 148 | this._failAll(err) |
| 149 | | this._sock.destroy() |
| 149 | this._down() |
| 150 | 150 | } |
| 151 | 151 | }) |
| 152 | | this._sock.on('close', () => { |
| 153 | | this._failAll(new Error('connection closed')) |
| 154 | | }) |
| 155 | | this._sock.on('error', () => { /* close handler runs */ }) |
| 152 | // EVENT-DRIVEN disconnect detection (fleet reconciliation contract). When the peer daemon |
| 153 | // restarts (the common case: an OTA/`pgh app restart` of hpp/pgv3/etc.), the cached client's |
| 154 | // socket dies. Previously 'close' only `_failAll`'d the pending map but NEVER set `_closed`, |
| 155 | // and there was no 'end' handler at all — so `client.closed` stayed FALSE and long-lived |
| 156 | // consumers (favela-web Writer, pgh sidecar lookups) kept handing back a DEAD client, whose |
| 157 | // calls hang or reject "connection closed" forever with nothing to trigger a reconnect. Mark |
| 158 | // the client CLOSED on close/end/error so `.closed` is truthy → consumers reconcile (reconnect |
| 159 | // a fresh Client) on their next call. 'end' is load-bearing under bare-net (allowHalfOpen: on |
| 160 | // peer-end it fires 'end' but not 'close'), so we destroy to force teardown — same posture as |
| 161 | // the server-side half-open fix (hyper-svc 0.1.3). |
| 162 | this._sock.on('close', () => this._down()) |
| 163 | this._sock.on('end', () => this._down()) |
| 164 | this._sock.on('error', () => this._down()) |
| 156 | 165 | } |
| 157 | 166 | } |
| 158 | 167 | |
| … | … | @@ -207,6 +216,15 @@ export class Client { |
| 207 | 216 | this._pending.clear() |
| 208 | 217 | } |
| 209 | 218 | |
| 219 | // The socket died (peer restarted / errored / half-open end). Mark the client CLOSED so any |
| 220 | // consumer's `.closed` check reconnects, fail every in-flight call, and force teardown. Idempotent. |
| 221 | _down () { |
| 222 | if (this._closed) return |
| 223 | this._closed = true |
| 224 | this._failAll(new Error('connection closed')) |
| 225 | if (this._sock) { try { this._sock.destroy() } catch {} } |
| 226 | } |
| 227 | |
| 210 | 228 | async _callHttp (method, params) { |
| 211 | 229 | const url = new URL(`${this._base}/rpc/${encodeURIComponent(method)}`) |
| 212 | 230 | return new Promise((resolve, reject) => { |