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 *
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' ;
3134import {
3235 existsSync ,
3336 mkdtempSync ,
@@ -40,6 +43,7 @@ import {registerHooks} from 'node:module';
4043import { networkInterfaces , tmpdir } from 'node:os' ;
4144import { join } from 'node:path' ;
4245import { fileURLToPath , pathToFileURL } from 'node:url' ;
46+ import { promisify } from 'node:util' ;
4347import { createJwkPair } from '../../../packages/zero-cache/src/auth/jwt.ts' ;
4448
4549const env = process . env ;
@@ -191,34 +195,66 @@ const {fastify} = (await import('../../zbugs/api/index.ts')) as {
191195} ;
192196await 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- } ) ;
222258process . on ( 'SIGINT' , ( ) => stop ( 0 ) ) ;
223259process . on ( 'SIGTERM' , ( ) => stop ( 0 ) ) ;
224260
@@ -249,6 +285,7 @@ const token = execFileSync(
249285 } ,
250286) . trim ( ) ;
251287process . stdout . write ( `token ${ token } \n` ) ;
288+ ready = true ;
252289process . stdout . write (
253290 `ready ws://${ localHost } :${ port } http://${ localHost } :${ apiPort } \n` ,
254291) ;
0 commit comments