From d417ea8a4bd895b9a9c8fc56d0069a870b26e150 Mon Sep 17 00:00:00 2001 From: zkldi Date: Wed, 16 Jun 2021 13:25:52 +0100 Subject: [PATCH] Create Import Locks --- server/pnpm-lock.yaml | 6 ++-- server/src/external/mongo/db.ts | 2 ++ .../framework/import-locks/lock.ts | 32 +++++++++++++++++++ .../framework/score-import-main.ts | 19 +++++++++++ .../src/test-utils/mock-db/import-locks.json | 1 + 5 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 server/src/lib/score-import/framework/import-locks/lock.ts create mode 100644 server/src/test-utils/mock-db/import-locks.json diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 225a3b5d5..ad75a17b2 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -80,7 +80,7 @@ dependencies: rate-limit-redis: 2.1.0 redis: 3.1.2 rimraf: 3.0.2 - tachi-common: github.com/zkldi/tachi-common/137fb72499a2ee6a98791ffea3961d0c69bf24b7 + tachi-common: github.com/zkldi/tachi-common/fff6217ee6c483f773fed97848802a774e8c5b70 typescript: 4.3.2 winston: 3.3.3 @@ -4445,8 +4445,8 @@ packages: engines: {node: '>=6'} dev: true - github.com/zkldi/tachi-common/137fb72499a2ee6a98791ffea3961d0c69bf24b7: - resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/137fb72499a2ee6a98791ffea3961d0c69bf24b7} + github.com/zkldi/tachi-common/fff6217ee6c483f773fed97848802a774e8c5b70: + resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/fff6217ee6c483f773fed97848802a774e8c5b70} name: kamaitachi-common version: 0.1.0 dependencies: diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 8abc32358..35139c420 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -24,6 +24,7 @@ import { PBScoreDocument, UserMilestoneDocument, BMSCourseDocument, + ImportLockDocument, } from "tachi-common"; import monk from "monk"; import { MONGO_BASE_URL } from "../../lib/setup/config"; @@ -132,6 +133,7 @@ const db = { "bms-course-lookup": monkDB.get("bms-course-lookup"), "api-tokens": monkDB.get("api-tokens"), "orphan-scores": monkDB.get("orphan-scores"), + "import-locks": monkDB.get("import-locks"), }; export default db; diff --git a/server/src/lib/score-import/framework/import-locks/lock.ts b/server/src/lib/score-import/framework/import-locks/lock.ts new file mode 100644 index 000000000..06e53a065 --- /dev/null +++ b/server/src/lib/score-import/framework/import-locks/lock.ts @@ -0,0 +1,32 @@ +import db from "../../../../external/mongo/db"; +import { integer } from "tachi-common"; + +/** + * Gets a users "import lock" if one exists. If one does not exist, it is set. + * @param userID - The user this import lock is for. + * @returns If no lock exists for this user (and one was created), undefined is returned + * If a lock exists for the user, the lock is returned. + */ +export function GetOrSetUserLock(userID: integer) { + return db["import-locks"].findOneAndUpdate( + { + userID, + }, + { + userID, + }, + { + upsert: true, + returnDocument: "before", + } + ); +} + +/** + * Removes a users import lock. + */ +export function RemoveUserLock(userID: integer) { + return db["import-locks"].remove({ + userID, + }); +} diff --git a/server/src/lib/score-import/framework/score-import-main.ts b/server/src/lib/score-import/framework/score-import-main.ts index 9281e6dd1..378c1df9e 100644 --- a/server/src/lib/score-import/framework/score-import-main.ts +++ b/server/src/lib/score-import/framework/score-import-main.ts @@ -22,6 +22,8 @@ import { ConverterFunction, ImportInputParser } from "../import-types/common/typ import { ScorePlaytypeMap } from "./common/types"; import { Converters } from "../import-types/converters"; import { ClassHandler } from "./user-game-stats/types"; +import { GetOrSetUserLock, RemoveUserLock } from "./import-locks/lock"; +import ScoreImportFatalError from "./score-importing/score-import-error"; /** * Performs a Score Import. @@ -33,6 +35,21 @@ export default async function ScoreImportMain( InputParser: ImportInputParser, providedImportObjects?: { logger: KtLogger; importID: string } ) { + const lock = await GetOrSetUserLock(user.id); + + if (lock) { + // @danger + // Throwing away an import if the user already has one outgoing is *bad*, as in the case + // of degraded performance we might just start throwing scores away. This is obviously + // not great, but any other solution involves making a queue, which can't be done because + // InputParser is a very dynamic function that cannot be stored in redis or something. + // + // Under normal circumstances, there is no scenario where a user would have two ongoing + // imports at the same time - even if they were using single-score imports on a 5 second + // chart, as each score import takes only around ~10-15miliseconds. + throw new ScoreImportFatalError(409, "This user already has an ongoing import."); + } + const timeStarted = Date.now(); let importID; let logger; @@ -202,6 +219,8 @@ export default async function ScoreImportMain( }, }); + await RemoveUserLock(user.id); + return ImportDocument; } diff --git a/server/src/test-utils/mock-db/import-locks.json b/server/src/test-utils/mock-db/import-locks.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/server/src/test-utils/mock-db/import-locks.json @@ -0,0 +1 @@ +[] \ No newline at end of file