diff --git a/server/multiplayer/ServerMultiplayerRoomMixin.js b/server/multiplayer/ServerMultiplayerRoomMixin.js index e73596134..8b2fdb5c6 100644 --- a/server/multiplayer/ServerMultiplayerRoomMixin.js +++ b/server/multiplayer/ServerMultiplayerRoomMixin.js @@ -2,7 +2,7 @@ import ServerPlayer from './ServerPlayer.js'; import Votekick from './VoteKick.js'; import { HEADER, ENDC, OKCYAN, OKBLUE } from '../bcolors.js'; import isAppropriateString from '../moderation/is-appropriate-string.js'; -import { MODE_ENUM, QUESTION_TYPE_ENUM, TOSSUP_PROGRESS_ENUM } from '../../quizbowl/constants.js'; +import { BONUS_PROGRESS_ENUM, MODE_ENUM, QUESTION_TYPE_ENUM, TOSSUP_PROGRESS_ENUM } from '../../quizbowl/constants.js'; import insertTokensIntoHTML from '../../quizbowl/insert-tokens-into-html.js'; // import TossupRoom from '../../quizbowl/TossupRoom.js'; import RateLimit from '../RateLimit.js'; @@ -47,7 +47,7 @@ const ServerMultiplayerRoomMixin = (RoomClass) => class extends RoomClass { }; getSetList().then(setList => { this.packetList = setList; }); - setInterval(this.cleanupExpiredBansAndKicks.bind(this), 5 * 60 * 1000); // 5 minutes + this.cleanupInterval = setInterval(this.cleanupExpiredBansAndKicks.bind(this), 5 * 60 * 1000); // 5 minutes } async message ({ userId, username }, message) { @@ -254,6 +254,18 @@ const ServerMultiplayerRoomMixin = (RoomClass) => class extends RoomClass { } this.leave(userId); + + // Reclaim non-permanent rooms once they empty out, so the room object and + // its cleanup interval don't leak. Skip while a question is in progress; + // its timers still hold a reference and the room will be reclaimed the next + // time it empties between questions. + const isEmpty = Object.keys(this.sockets).length === 0; + const isIdle = this.tossupProgress === TOSSUP_PROGRESS_ENUM.NOT_STARTED && + this.bonusProgress === BONUS_PROGRESS_ENUM.NOT_STARTED; + if (isEmpty && isIdle && !this.isPermanent && !this.isVerified) { + clearInterval(this.cleanupInterval); + this.onEmpty?.(); + } } giveAnswerLiveUpdate ({ userId, username }, { givenAnswer }) { diff --git a/server/multiplayer/handle-wss-connection.js b/server/multiplayer/handle-wss-connection.js index 9d82b31d6..fb7e72db1 100644 --- a/server/multiplayer/handle-wss-connection.js +++ b/server/multiplayer/handle-wss-connection.js @@ -48,6 +48,8 @@ function createAndReturnRoom (roomName, userId, isPrivate = false, isControlled // A room cannot be both public and controlled room.settings.public = !isPrivate && !isControlled; room.settings.controlled = isControlled; + // Remove the room once it empties out so it doesn't leak (see closeConnection). + room.onEmpty = () => { delete tossupBonusRooms[roomName]; }; tossupBonusRooms[roomName] = room; }