From fff8e056c31861fa5985859ab068a6ac9f1b52ab Mon Sep 17 00:00:00 2001 From: Brian Le Date: Sun, 14 Apr 2024 00:45:26 +0000 Subject: [PATCH] Fix race condition for double startRound() during correct guess (#2080) * Fix race condition for double startRound() during correct guess * Add prevalidation delay for test runner stages --- src/structures/game_session.ts | 24 ++++++++++++------- src/test/end-to-end-tests/test-runner-bot.ts | 4 ++++ .../test_suites/gameplay_test.ts | 1 + .../test_suites/test_suite.ts | 1 + 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/structures/game_session.ts b/src/structures/game_session.ts index 0d10f0e37..41a861fd2 100644 --- a/src/structures/game_session.ts +++ b/src/structures/game_session.ts @@ -243,11 +243,20 @@ export default class GameSession extends Session { * Ends an active GameRound * @param isError - Whether the round ended due to an error * @param messageContext - An object containing relevant parts of Eris.Message + * @param gameRound - The round to end */ async endRound( isError: boolean, messageContext: MessageContext, + gameRound?: GameRound, ): Promise { + // if round ending due to correct song guess, ensure that we are operating on the provided + // game round to ensure we don't end the same round twice (since this.round is modified) + let round = gameRound ?? this.round; + if (!round) { + round = this.round; + } + // wait and accept multiguess results await delay( this.multiguessDelayIsActive(this.guildPreference) @@ -255,10 +264,8 @@ export default class GameSession extends Session { : 0, ); - const round = this.round; - // ensure that only one invocation can proceed - if (round === null || round.finished) { + if (!round || round.finished) { return; } @@ -394,6 +401,8 @@ export default class GameSession extends Session { } else if (this.gameType === GameType.SUDDEN_DEATH && !isCorrectGuess) { await this.endSession("Sudden death game ended", false); } + + await this.startRound(messageContext); } /** @@ -565,18 +574,15 @@ export default class GameSession extends Session { return; } - // mark round as complete, so no more guesses can go through - await this.endRound(false, messageContext); + this.stopGuessTimeout(); this.correctGuesses++; + // mark round as complete, so no more guesses can go through + await this.endRound(false, messageContext, round); // update game session's lastActive await this.lastActiveNow(); - this.stopGuessTimeout(); - await this.incrementGuildSongGuessCount(); - - await this.startRound(messageContext); } else if (this.isMultipleChoiceMode() || this.isHiddenMode()) { // If hidden or multiple choice, everyone guessed and no one was right if ( diff --git a/src/test/end-to-end-tests/test-runner-bot.ts b/src/test/end-to-end-tests/test-runner-bot.ts index 1dad7d195..59bf20176 100644 --- a/src/test/end-to-end-tests/test-runner-bot.ts +++ b/src/test/end-to-end-tests/test-runner-bot.ts @@ -330,6 +330,10 @@ async function evaluateStage(messageResponse?: { log(`STAGE ${CURRENT_STAGE.stage} | Validating stage`); const stageOutputValidator = testStage.responseValidator; + if (testStage.prevalidationDelay) { + await delay(testStage.prevalidationDelay); + } + if ( stageOutputValidator( messageResponse?.title!, diff --git a/src/test/end-to-end-tests/test_suites/gameplay_test.ts b/src/test/end-to-end-tests/test_suites/gameplay_test.ts index 74788837f..30efcacae 100644 --- a/src/test/end-to-end-tests/test_suites/gameplay_test.ts +++ b/src/test/end-to-end-tests/test_suites/gameplay_test.ts @@ -216,6 +216,7 @@ const PLAY_TEST_SUITE: TestSuite = { console.log(`voiceMembers: ${voiceMembers.join(", ")}`); return !voiceMembers.includes(process.env.BOT_CLIENT_ID!); }, + prevalidationDelay: 3000, expectedResponseType: KmqResponseType.NONE, }, ], diff --git a/src/test/end-to-end-tests/test_suites/test_suite.ts b/src/test/end-to-end-tests/test_suites/test_suite.ts index 66276c711..f3bbca855 100644 --- a/src/test/end-to-end-tests/test_suites/test_suite.ts +++ b/src/test/end-to-end-tests/test_suites/test_suite.ts @@ -12,6 +12,7 @@ export default interface TestSuite { client?: Eris.Client, ) => boolean; expectedResponseType: KmqResponseType; + prevalidationDelay?: number; preCommandDelay?: number; }[]; cascadingFailures: boolean;