Skip to content

Commit 9236983

Browse files
tmmampagent
andauthored
fix(zbugs-swift): restart zero-cache in a kept backend when it exits (#120)
zero-cache exits with code 14 when its replication stream times out and it can't reach Postgres, as when the Mac sleeps with Postgres in Docker, and leaves restarting it to a supervisor. The backend stopped with it, so a long-running dev backend died whenever the Mac slept. With ZBUGS_KEEP=1 the backend now restarts zero-cache once Postgres answers again. Without it, as in CI, a zero-cache exit still stops the backend, so a crash still fails the run. Amp-Thread-ID: https://ampcode.com/threads/T-c513a037-a257-45e0-b9a9-15c15edfe859 Co-authored-by: Amp <amp@ampcode.com>
1 parent deb8449 commit 9236983

2 files changed

Lines changed: 65 additions & 26 deletions

File tree

‎apps/zbugs-swift/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ ZBUGS_KEEP=1 ZBUGS_DIR=~/.zbugs-swift ZBUGS_PG=... \
3939
```
4040

4141
It seeds the database only when it doesn't exist; drop it, or run once
42-
without `ZBUGS_KEEP`, to start over.
42+
without `ZBUGS_KEEP`, to start over. A kept backend also restarts zero-cache
43+
when zero-cache exits, once Postgres answers again: zero-cache exits when it
44+
loses Postgres, as when the Mac sleeps with Postgres in Docker.
4345

4446
Then generate the Xcode project and run the `ZBugsApp` scheme on a Mac or an
4547
iOS simulator:

‎apps/zbugs-swift/server/server.ts‎

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
* seed data, zbugs' API server (apps/zbugs/api), and zero-cache, all from
55
* this repository's sources. With `ZBUGS_KEEP=1` it keeps the database, the
66
* replica, and its signing keys from the last run instead, so an app keeps
7-
* its local data and its token across restarts.
7+
* its local data and its token across restarts, and it outlives zero-cache:
8+
* when zero-cache exits, as when Postgres is unreachable after this machine
9+
* sleeps, it restarts zero-cache once Postgres answers again.
810
*
911
* node apps/zbugs-swift/server/server.ts
1012
*
@@ -25,9 +27,10 @@
2527
* ZBUGS_API_PORT API server port (default 5173)
2628
* ZBUGS_DIR working directory for the replica (default a temp dir)
2729
* ZBUGS_KEEP 1 to keep the last run's database, and the replica and
28-
* keys in ZBUGS_DIR, which it needs
30+
* keys in ZBUGS_DIR, which it needs, and to restart
31+
* zero-cache when it exits
2932
*/
30-
import {execFileSync, spawn} from 'node:child_process';
33+
import {execFile, execFileSync, spawn} from 'node:child_process';
3134
import {
3235
existsSync,
3336
mkdtempSync,
@@ -40,6 +43,7 @@ import {registerHooks} from 'node:module';
4043
import {networkInterfaces, tmpdir} from 'node:os';
4144
import {join} from 'node:path';
4245
import {fileURLToPath, pathToFileURL} from 'node:url';
46+
import {promisify} from 'node:util';
4347
import {createJwkPair} from '../../../packages/zero-cache/src/auth/jwt.ts';
4448

4549
const env = process.env;
@@ -191,34 +195,66 @@ const {fastify} = (await import('../../zbugs/api/index.ts')) as {
191195
};
192196
await fastify.listen({port: apiPort, host});
193197

194-
const zeroCache = spawn(
195-
process.execPath,
196-
[here('../../../packages/zero-cache/src/server/runner/main.ts')],
197-
{
198-
stdio: ['ignore', 'inherit', 'inherit'],
199-
env: {
200-
...env,
201-
['NODE_ENV']: 'development',
202-
['ZERO_UPSTREAM_DB']: upstreamURL,
203-
['ZERO_REPLICA_FILE']: join(workDir, 'replica.db'),
204-
['ZERO_PORT']: String(port),
205-
['ZERO_QUERY_URL']: apiURLs('query'),
206-
['ZERO_MUTATE_URL']: apiURLs('mutate'),
207-
['ZERO_NUM_SYNC_WORKERS']: '1',
208-
['ZERO_LOG_LEVEL']: env['ZERO_LOG_LEVEL'] ?? 'info',
198+
function startZeroCache() {
199+
const child = spawn(
200+
process.execPath,
201+
[here('../../../packages/zero-cache/src/server/runner/main.ts')],
202+
{
203+
stdio: ['ignore', 'inherit', 'inherit'],
204+
env: {
205+
...env,
206+
['NODE_ENV']: 'development',
207+
['ZERO_UPSTREAM_DB']: upstreamURL,
208+
['ZERO_REPLICA_FILE']: join(workDir, 'replica.db'),
209+
['ZERO_PORT']: String(port),
210+
['ZERO_QUERY_URL']: apiURLs('query'),
211+
['ZERO_MUTATE_URL']: apiURLs('mutate'),
212+
['ZERO_NUM_SYNC_WORKERS']: '1',
213+
['ZERO_LOG_LEVEL']: env['ZERO_LOG_LEVEL'] ?? 'info',
214+
},
209215
},
210-
},
211-
);
216+
);
217+
child.on('exit', code => void restartZeroCache(code));
218+
return child;
219+
}
220+
221+
/**
222+
* zero-cache exits when it loses Postgres, as when this machine sleeps and
223+
* Docker's VM wakes after it, and leaves restarting it to a supervisor. A
224+
* kept backend is long-lived, so it restarts zero-cache once Postgres
225+
* answers again; otherwise, as in the tests, it stops.
226+
*/
227+
async function restartZeroCache(code: number | null) {
228+
process.stderr.write(`zero-cache exited with ${code}\n`);
229+
if (!keep || !ready) {
230+
stop(1);
231+
}
232+
do {
233+
await new Promise(resolve => setTimeout(resolve, 5000));
234+
} while (!(await postgresAnswers()));
235+
process.stderr.write('restarting zero-cache\n');
236+
zeroCache = startZeroCache();
237+
}
238+
239+
async function postgresAnswers() {
240+
try {
241+
await promisify(execFile)('psql', [upstreamURL, '-tAc', 'SELECT 1'], {
242+
env: {...env, ['PGCONNECT_TIMEOUT']: '5'},
243+
});
244+
return true;
245+
} catch {
246+
return false;
247+
}
248+
}
212249

213-
function stop(code: number) {
250+
let ready = false;
251+
let zeroCache = startZeroCache();
252+
253+
function stop(code: number): never {
214254
zeroCache.kill('SIGTERM');
215255
void fastify.close();
216256
process.exit(code);
217257
}
218-
zeroCache.on('exit', code => {
219-
process.stderr.write(`zero-cache exited with ${code}\n`);
220-
stop(1);
221-
});
222258
process.on('SIGINT', () => stop(0));
223259
process.on('SIGTERM', () => stop(0));
224260

@@ -249,6 +285,7 @@ const token = execFileSync(
249285
},
250286
).trim();
251287
process.stdout.write(`token ${token}\n`);
288+
ready = true;
252289
process.stdout.write(
253290
`ready ws://${localHost}:${port} http://${localHost}:${apiPort}\n`,
254291
);

0 commit comments

Comments
 (0)