From 894944680f6e4852a56a6e36833896235310ad85 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 24 Nov 2021 13:46:23 +0000 Subject: [PATCH] band-aid over partial-import failures in ImportAllIterableData. --- .../score-importing/score-import-main.ts | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/server/src/lib/score-import/framework/score-importing/score-import-main.ts b/server/src/lib/score-import/framework/score-importing/score-import-main.ts index 572b0a52f..50513dc90 100644 --- a/server/src/lib/score-import/framework/score-importing/score-import-main.ts +++ b/server/src/lib/score-import/framework/score-importing/score-import-main.ts @@ -102,16 +102,46 @@ export default async function ScoreImportMain( // --- 2. Importing --- // ImportAllIterableData iterates over the iterable, applying the converter function to each bit of data. const importTimeStart = process.hrtime.bigint(); - const importInfo = await ImportAllIterableData( - user.id, - importType, - iterable, - ConverterFunction, - context, - game, - logger, - job - ); + + let importInfo; + const startOfImportingScores = Date.now(); + + // Score imports are not transaction based. As such, if they fail midway through, corrupt state + // may occur. This sucks, but we can band-aid over some of the worse parts. Here, if our score + // import fails, we **know** that we might've inserted scores but not properly handled their + // state. If so, we should attempt to revert any of those scores. + // + // There are still places where Tachi can fail on importing scores and result in corrupt state, + // but this band-aid fix handles all known cases of a partial-import failure. + try { + importInfo = await ImportAllIterableData( + user.id, + importType, + iterable, + ConverterFunction, + context, + game, + logger, + job + ); + } catch (err) { + logger.error( + `An error was thrown from ImportAllIterableData, which has resulted in a potential partial-score-import. Undoing scores inserted from this import.`, + { err } + ); + + // Remove all scores from the database for this user which were imported after our timer started. + const r = await db.scores.remove({ + userID: user.id, + timeAdded: { $gte: startOfImportingScores }, + }); + + logger.error( + `Removed ${r.deletedCount} scores from the database to undo partial-import.` + ); + + throw err; + } const importTime = GetMillisecondsSince(importTimeStart); const importTimeRel = importTime / importInfo.length;