Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Prisma transaction timeout during map submission #1039

Merged
merged 1 commit into from
Sep 27, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 94 additions & 84 deletions apps/backend/src/app/modules/maps/maps.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,51 +546,57 @@ export class MapsService {
const bspHash = FileStoreService.getHashForBuffer(bspFile.buffer);

let map: Awaited<ReturnType<typeof this.createMapDbEntry>>;
await this.db.$transaction(async (tx) => {
map = await this.createMapDbEntry(tx, dto, userID, bspHash, hasVmf);

await tx.leaderboard.createMany({
data: LeaderboardHandler.getMaximalLeaderboards(
dto.suggestions.map(({ gamemode, trackType, trackNum }) => ({
gamemode,
trackType,
trackNum
})),
dto.zones
).map((obj) => ({
mapID: map.id,
...obj,
style: 0, // When we add styles support getMaximalLeaderboards should generate all variations of this
type: LeaderboardType.IN_SUBMISSION
}))
});

const version = map.currentVersion;

const tasks: Promise<unknown>[] = [
(async () => {
const zippedVmf = hasVmf
? await this.zipVmfFiles(dto.name, 1, vmfFiles)
: undefined;
const tasks: Promise<any>[] = [
this.db.$transaction(async (tx) => {
map = await this.createMapDbEntry(tx, dto, userID, bspHash, hasVmf);

await tx.leaderboard.createMany({
data: LeaderboardHandler.getMaximalLeaderboards(
dto.suggestions.map(({ gamemode, trackType, trackNum }) => ({
gamemode,
trackType,
trackNum
})),
dto.zones
).map((obj) => ({
mapID: map.id,
...obj,
style: 0, // When we add styles support getMaximalLeaderboards should generate all variations of this
type: LeaderboardType.IN_SUBMISSION
}))
});

return this.uploadMapVersionFiles(version.id, bspFile, zippedVmf);
})(),
// We need the generated map ID from the above query for S3, but do NOT
// want to block our transaction on S3 operations - so push to promise
// array then await everything below.
tasks.push(
(async () => {
const zippedVmf = hasVmf
? await this.zipVmfFiles(dto.name, 1, vmfFiles)
: undefined;

return this.uploadMapVersionFiles(
map.currentVersion.id,
bspFile,
zippedVmf
);
})()
);

this.createMapUploadedActivities(tx, map.id, map.credits)
];
await this.createMapUploadedActivities(tx, map.id, map.credits);

if (dto.wantsPrivateTesting && dto.testInvites?.length > 0) {
tasks.push(
this.mapTestInviteService.createOrUpdatePrivateTestingInvites(
if (dto.wantsPrivateTesting && dto.testInvites?.length > 0) {
await this.mapTestInviteService.createOrUpdatePrivateTestingInvites(
tx,
map.id,
dto.testInvites
)
);
}
);
}
})
];

await Promise.all(tasks);
});
await Promise.all(tasks);

return DtoFactory(MapDto, map);
}
Expand Down Expand Up @@ -659,61 +665,65 @@ export class MapsService {
const oldVersion = map.currentVersion;
const newVersionNum = oldVersion.versionNum + 1;

await this.db.$transaction(async (tx) => {
const newVersion = await tx.mapVersion.create({
data: {
versionNum: newVersionNum,
submitter: { connect: { id: userID } },
hasVmf,
zones: zones as unknown as JsonValue, // TODO: #855
bspHash,
changelog: dto.changelog,
mmap: { connect: { id: mapID } }
}
});
const tasks: Promise<any>[] = [
this.db.$transaction(async (tx) => {
const newVersion = await tx.mapVersion.create({
data: {
versionNum: newVersionNum,
submitter: { connect: { id: userID } },
hasVmf,
zones: zones as unknown as JsonValue, // TODO: #855
bspHash,
changelog: dto.changelog,
mmap: { connect: { id: mapID } }
}
});

await this.generateSubmissionLeaderboards(
tx,
mapID,
map.submission.suggestions as unknown as MapSubmissionSuggestion[], // TODO: #855
zones
);
await this.generateSubmissionLeaderboards(
tx,
mapID,
map.submission.suggestions as unknown as MapSubmissionSuggestion[], // TODO: #855
zones
);

if (dto.resetLeaderboards === true) {
// If it's been approved before, deleting runs is a majorly destructive
// action that we probably don't want to allow the submitter to do.
// If the submitter is fixing the maps in a significant enough way to
// still require a leaderboard reset, they should just get an admin to
// do it.
if (
(map.submission.dates as unknown as MapSubmissionDate[]).some(
(date) => date.status === MapStatus.APPROVED
)
) {
throw new ForbiddenException(
'Cannot reset leaderboards on a previously approved map.' +
' Talk about it with an admin!'
);
}
if (dto.resetLeaderboards === true) {
// If it's been approved before, deleting runs is a majorly destructive
// action that we probably don't want to allow the submitter to do.
// If the submitter is fixing the maps in a significant enough way to
// still require a leaderboard reset, they should just get an admin to
// do it.
if (
(map.submission.dates as unknown as MapSubmissionDate[]).some(
(date) => date.status === MapStatus.APPROVED
)
) {
throw new ForbiddenException(
'Cannot reset leaderboards on a previously approved map.' +
' Talk about it with an admin!'
);
}

await tx.leaderboardRun.deleteMany({ where: { mapID: map.id } });
}
await tx.leaderboardRun.deleteMany({ where: { mapID: map.id } });
}

await parallel(
async () => {
const zippedVmf = hasVmf
? await this.zipVmfFiles(map.name, newVersionNum, vmfFiles)
: undefined;
tasks.push(
(async () => {
const zippedVmf = hasVmf
? await this.zipVmfFiles(map.name, newVersionNum, vmfFiles)
: undefined;

await this.uploadMapVersionFiles(newVersion.id, bspFile, zippedVmf);
},
await this.uploadMapVersionFiles(newVersion.id, bspFile, zippedVmf);
})()
);

tx.mMap.update({
await tx.mMap.update({
where: { id: mapID },
data: { currentVersion: { connect: { id: newVersion.id } } }
})
);
});
});
})
];

await Promise.all(tasks);

if (
map.status === MapStatus.PUBLIC_TESTING ||
Expand Down