From c20e9d2ef0e3d555d478d1c1080e369762308fd3 Mon Sep 17 00:00:00 2001 From: Kevin Cox Date: Sat, 23 Aug 2025 16:59:30 -0400 Subject: [PATCH] Precise user configuration reloading. Previously when any user config file was changed all users would be reloaded. This could be very expensive on installations with thousands of users. Since this triggered when any file was changed it would trigger when any user connected which can be quite frequent. As a side-effect this removes the debouncing. This means that user changes take effect instantly rather than after 1s. Since there is no longer a 1s delay it is extra important that files are written safely. To this end the `thelounge add ` command was updated to write the user file atomically. (The update path already did this.) --- server/clientManager.ts | 51 +++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/server/clientManager.ts b/server/clientManager.ts index a0f1aabf..bbab4f49 100644 --- a/server/clientManager.ts +++ b/server/clientManager.ts @@ -86,39 +86,28 @@ class ClientManager { } autoloadUsers() { - fs.watch( - Config.getUsersPath(), - _.debounce( - () => { - const loaded = this.clients.map((c) => c.name); - const updatedUsers = this.getUsers(); + fs.watch(Config.getUsersPath(), (_eventType, file) => { + if (!file.endsWith(".json")) { + return; + } - if (updatedUsers.length === 0) { - log.info( - `There are currently no users. Create one with ${colors.bold( - "thelounge add " - )}.` - ); - } + const name = file.slice(0, -5); - // Reload all users. Existing users will only have their passwords reloaded. - updatedUsers.forEach((name) => this.loadUser(name)); + const userPath = Config.getUserConfigPath(name); - // Existing users removed since last time users were loaded - _.difference(loaded, updatedUsers).forEach((name) => { - const client = _.find(this.clients, {name}); + if (fs.existsSync(userPath)) { + this.loadUser(name); + return; + } - if (client) { - client.quit(true); - this.clients = _.without(this.clients, client); - log.info(`User ${colors.bold(name)} disconnected and removed.`); - } - }); - }, - 1000, - {maxWait: 10000} - ) - ); + const client = _.find(this.clients, {name}); + + if (client) { + client.quit(true); + this.clients = _.without(this.clients, client); + log.info(`User ${colors.bold(name)} disconnected and removed.`); + } + }); } loadUser(name: string) { @@ -180,9 +169,11 @@ class ClientManager { }; try { - fs.writeFileSync(userPath, JSON.stringify(user, null, "\t"), { + const tmpPath = userPath + ".tmp"; + fs.writeFileSync(tmpPath, JSON.stringify(user, null, "\t"), { mode: 0o600, }); + fs.renameSync(tmpPath, userPath); } catch (e: any) { log.error(`Failed to create user ${colors.green(name)} (${e})`); throw e;