@@ -35,7 +35,7 @@ const TaskScheduler = require("./classes/task-scheduler-singleton");
3535// const tasks = require("./tasks/tasks");
3636
3737// websocket
38- const { setupWebSocketServer } = require ( "./ws" ) ;
38+ const { setupWebSocketServer, shutdownWebSocketServer } = require ( "./ws" ) ;
3939const writeEnvVariables = require ( "./classes/env" ) ;
4040
4141process . env . POSTGRES_USER = process . env . POSTGRES_USER ?? "postgres" ;
@@ -231,6 +231,7 @@ async function authenticate(req, res, next) {
231231}
232232
233233// start server
234+ let server ;
234235try {
235236 createdb . createDatabase ( ) . then ( ( result ) => {
236237 if ( result ) {
@@ -240,7 +241,7 @@ try {
240241 }
241242
242243 db . migrate . latest ( ) . then ( ( ) => {
243- const server = http . createServer ( app ) ;
244+ server = http . createServer ( app ) ;
244245
245246 setupWebSocketServer ( server , BASE_NAME ) ;
246247 server . listen ( PORT , LISTEN_IP , async ( ) => {
@@ -255,3 +256,89 @@ try {
255256} catch ( error ) {
256257 console . log ( "[JELLYSTAT] An error has occured on startup: " + error ) ;
257258}
259+
260+ function runStep ( name , fn ) {
261+ return ( async ( ) => {
262+ console . log ( `[JELLYSTAT] Beginnging shutdown step ${ name } ` ) ;
263+ try {
264+ await fn ( ) ;
265+ console . log ( `[JELLYSTAT] Shutdown step ${ name } complete.` ) ;
266+ } catch ( err ) {
267+ console . error ( `[JELLYSTAT] Shutdown step ${ name } failed.` , err ) ;
268+ throw err ;
269+ }
270+ } ) ( ) ;
271+ }
272+
273+ function shutdownKnex ( ) {
274+ if ( db && typeof db . destroy === "function" ) {
275+ console . info ( "[JELLYSTAT] Destroying knex connection." ) ;
276+ return db . destroy ( ) ;
277+ }
278+
279+ return Promise . resolve ( ) ;
280+ }
281+
282+ function shutdownHttpServer ( ) {
283+ return new Promise ( ( resolve , reject ) => {
284+ // Stop new connections
285+ server . close ( ( err ) => {
286+ if ( err ) return reject ( err ) ;
287+ resolve ( ) ;
288+ } ) ;
289+
290+ if ( typeof server . closeIdleConnections === "function" ) {
291+ console . info ( `[JELLYSTAT] Closing idle HTTP connections` ) ;
292+ server . closeIdleConnections ( ) ;
293+ }
294+ } ) ;
295+ }
296+
297+ function withTimeout ( promise , ms , label = "shutdown" ) {
298+ let timeoutId ;
299+
300+ const timeoutPromise = new Promise ( ( _ , reject ) => {
301+ timeoutId = setTimeout ( ( ) => {
302+ reject ( new Error ( `[JELLYSTAT] ${ label } timed out after ${ ms } ms` ) ) ;
303+ } , ms ) ;
304+ } ) ;
305+
306+ return Promise . race ( [ promise , timeoutPromise ] ) . finally ( ( ) => {
307+ clearTimeout ( timeoutId ) ;
308+ } ) ;
309+ }
310+
311+ // shutdown server
312+ let shuttingDown = false ;
313+ async function shutdown ( signal ) {
314+ if ( shuttingDown ) return ;
315+ shuttingDown = true ;
316+
317+ console . log ( `[JELLYSTAT] Recieved ${ signal } , beginning shutdown process` ) ;
318+
319+ const timeLimit = 9000 ;
320+
321+ const shutdownTasks = [
322+ runStep ( "httpServer" , async ( ) => shutdownHttpServer ( signal ) ) ,
323+ runStep ( "webSocketServer" , async ( ) => shutdownWebSocketServer ( signal ) ) ,
324+ runStep ( "database" , async ( ) => shutdownKnex ( ) ) ,
325+ ] ;
326+
327+ try {
328+ await withTimeout (
329+ Promise . allSettled ( shutdownTasks ) ,
330+ timeLimit ,
331+ "graceful shutdown"
332+ ) ;
333+
334+ console . log ( "[JELLYSTAT] Graceful shutdown complete" ) ;
335+ process . exit ( 0 ) ;
336+ } catch ( err ) {
337+ console . error ( `[JELLYSTAT] Error occurred while attempting to gracefully shut down.` , err ) ;
338+ process . exit ( 1 ) ;
339+ }
340+ }
341+
342+ // register signal handlers
343+ process . on ( "SIGTERM" , shutdown ) ;
344+ process . on ( "SIGINT" , shutdown ) ;
0 commit comments