From 8ca692bfb0b34a375195ff8177ff59761a715210 Mon Sep 17 00:00:00 2001 From: izzy Date: Fri, 19 Dec 2025 13:52:57 +0000 Subject: [PATCH] fix(maintenance): prevent CLI hanging on occassion fix(maintenance): always ack messages fix(maintenance): ensure Redis is connected first --- .../maintenance-websocket.repository.ts | 5 ++- server/src/services/cli.service.ts | 4 +- server/src/services/maintenance.service.ts | 3 +- server/src/utils/maintenance.ts | 45 +++++-------------- 4 files changed, 18 insertions(+), 39 deletions(-) diff --git a/server/src/maintenance/maintenance-websocket.repository.ts b/server/src/maintenance/maintenance-websocket.repository.ts index 5d8368cf69..cc0e592533 100644 --- a/server/src/maintenance/maintenance-websocket.repository.ts +++ b/server/src/maintenance/maintenance-websocket.repository.ts @@ -37,7 +37,10 @@ export class MaintenanceWebsocketRepository implements OnGatewayConnection, OnGa afterInit(websocketServer: Server) { this.logger.log('Initialized websocket server'); - websocketServer.on('AppRestart', () => this.appRepository.exitApp()); + websocketServer.on('AppRestart', (_, ack) => { + ack('ok'); + this.appRepository.exitApp(); + }); } clientBroadcast(event: T, ...data: ClientEventMap[T]) { diff --git a/server/src/services/cli.service.ts b/server/src/services/cli.service.ts index 3d248edc7a..7f6cb68d87 100644 --- a/server/src/services/cli.service.ts +++ b/server/src/services/cli.service.ts @@ -56,7 +56,7 @@ export class CliService extends BaseService { const state = { isMaintenanceMode: false as const }; await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, state); - sendOneShotAppRestart(state); + await sendOneShotAppRestart(state); return { alreadyDisabled: false, @@ -89,7 +89,7 @@ export class CliService extends BaseService { secret, }); - sendOneShotAppRestart({ + await sendOneShotAppRestart({ isMaintenanceMode: true, }); diff --git a/server/src/services/maintenance.service.ts b/server/src/services/maintenance.service.ts index e6808300bc..a672d5f1fd 100644 --- a/server/src/services/maintenance.service.ts +++ b/server/src/services/maintenance.service.ts @@ -31,7 +31,8 @@ export class MaintenanceService extends BaseService { } @OnEvent({ name: 'AppRestart', server: true }) - onRestart(): void { + onRestart(_: undefined, ack: (ok: 'ok') => void): void { + ack('ok'); this.appRepository.exitApp(); } diff --git a/server/src/utils/maintenance.ts b/server/src/utils/maintenance.ts index 22de2e4083..f050585ffa 100644 --- a/server/src/utils/maintenance.ts +++ b/server/src/utils/maintenance.ts @@ -7,47 +7,22 @@ import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto'; import { ConfigRepository } from 'src/repositories/config.repository'; import { AppRestartEvent } from 'src/repositories/event.repository'; -export function sendOneShotAppRestart(state: AppRestartEvent): void { +export async function sendOneShotAppRestart(state: AppRestartEvent): Promise { const server = new SocketIO(); const { redis } = new ConfigRepository().getEnv(); - const pubClient = new Redis(redis); + const pubClient = new Redis({ ...redis, lazyConnect: true }); const subClient = pubClient.duplicate(); + + await Promise.all([pubClient.connect(), subClient.connect()]); + server.adapter(createAdapter(pubClient, subClient)); - /** - * Keep trying until we manage to stop Immich - * - * Sometimes there appear to be communication - * issues between to the other servers. - * - * This issue only occurs with this method. - */ - async function tryTerminate() { - while (true) { - try { - const responses = await server.serverSideEmitWithAck('AppRestart', state); - if (responses.length > 0) { - return; - } - } catch (error) { - console.error(error); - console.error('Encountered an error while telling Immich to stop.'); - } - - console.info( - "\nIt doesn't appear that Immich stopped, trying again in a moment.\nIf Immich is already not running, you can ignore this error.", - ); - - await new Promise((r) => setTimeout(r, 1e3)); - } - } - // => corresponds to notification.service.ts#onAppRestart - server.emit('AppRestartV1', state, () => { - void tryTerminate().finally(() => { - pubClient.disconnect(); - subClient.disconnect(); - }); + server.emit('AppRestartV1', state, async () => { + await server.serverSideEmitWithAck('AppRestart', state); + + pubClient.disconnect(); + subClient.disconnect(); }); }