diff --git a/api/src/app.ts b/api/src/app.ts index 1ea1adad2b3..44846c9dea6 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -41,6 +41,7 @@ import { FCC_ENABLE_SWAGGER_UI, FCC_ENABLE_SHADOW_CAPTURE, FCC_ENABLE_SENTRY_ROUTES, + FREECODECAMP_NODE_ENV, GROWTHBOOK_FASTIFY_API_HOST, GROWTHBOOK_FASTIFY_CLIENT_KEY } from './utils/env.js'; @@ -86,7 +87,11 @@ export const buildOptions: FastifyHttpOptions< loggerInstance: getLogger(), genReqId: () => randomBytes(8).toString('hex'), // disabled so we can customise the request/response logging - disableRequestLogging: true + disableRequestLogging: true, + // destroy all connections on close to avoid EADDRINUSE + // on restart, in development. Leave default in production. + forceCloseConnections: + FREECODECAMP_NODE_ENV === 'production' ? ('idle' as const) : true }; /** diff --git a/api/src/server.ts b/api/src/server.ts index 1a5cdc024d1..4c948683f29 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -8,8 +8,17 @@ const start = async () => { const stop = async (signal: NodeJS.Signals) => { fastify.log.info(`Received ${signal}, shutting down.`); + + fastify.server.closeAllConnections(); + await new Promise(resolve => { + fastify.server.close(() => resolve()); + }); + + // Yield one tick so libuv can finalize uv_close() on the TCP handle + // before pino's autoEnd blocks the event loop via Atomics.wait(). + await new Promise(resolve => setImmediate(resolve)); + await fastify.close(); - fastify.log.info('Shutdown complete'); process.exit(0); }; @@ -17,9 +26,7 @@ const start = async () => { process.on('SIGTERM', signal => void stop(signal)); try { - const port = Number(PORT); - fastify.log.info(`Starting server on port ${port}`); - await fastify.listen({ port, host: HOST }); + await fastify.listen({ port: Number(PORT), host: HOST }); } catch (err) { fastify.log.error(err); process.exit(1);