Skip to content

Commit 11e2c75

Browse files
authored
Merge pull request #400 from topcoder-platform/PM-4299-1
Fix for scoring race condition seen in MM
2 parents b6692e8 + 4abf69d commit 11e2c75

2 files changed

Lines changed: 73 additions & 36 deletions

File tree

src/api/scoring-result/scoring-result.service.spec.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,7 @@ describe('ScoringResultService', () => {
10941094
);
10951095
});
10961096

1097-
it('does not persist system summation when completing the review fails', async () => {
1097+
it('persists system summation before completing the review', async () => {
10981098
const { service, httpService, m2mService, prisma } = createService();
10991099
const systemPayload: ScoringResultCallbackPayload = {
11001100
...basePayload,
@@ -1156,12 +1156,26 @@ describe('ScoringResultService', () => {
11561156
},
11571157
}),
11581158
);
1159-
expect(findExistingReviewSummationsSpy).not.toHaveBeenCalled();
1160-
expect(createReviewSummationSpy).not.toHaveBeenCalled();
1159+
expect(findExistingReviewSummationsSpy).toHaveBeenCalledWith(
1160+
'm2m-token',
1161+
systemPayload.submissionId,
1162+
'system',
1163+
);
1164+
expect(createReviewSummationSpy).toHaveBeenCalledWith(
1165+
'm2m-token',
1166+
expect.objectContaining({
1167+
aggregateScore: 100,
1168+
isFinal: true,
1169+
submissionId: systemPayload.submissionId,
1170+
}),
1171+
);
11611172
expect(updateReviewSummationSpy).not.toHaveBeenCalled();
1173+
expect(createReviewSummationSpy.mock.invocationCallOrder[0]).toBeLessThan(
1174+
httpService.patch.mock.invocationCallOrder[0],
1175+
);
11621176
});
11631177

1164-
it('does not persist currentReview summation when completing the review fails', async () => {
1178+
it('persists currentReview summation before completing the review', async () => {
11651179
const { service, httpService, m2mService, prisma } = createService();
11661180
const systemPayload: ScoringResultCallbackPayload = {
11671181
...basePayload,
@@ -1220,10 +1234,21 @@ describe('ScoringResultService', () => {
12201234
},
12211235
}),
12221236
);
1223-
expect(upsertFromLegacyReviewPayloadSpy).not.toHaveBeenCalled();
1237+
expect(upsertFromLegacyReviewPayloadSpy).toHaveBeenCalledWith(
1238+
'm2m-token',
1239+
expect.objectContaining({
1240+
fallbackScore: 100,
1241+
fallbackSubmissionId: systemPayload.submissionId,
1242+
legacyReview: systemPayload.currentReview,
1243+
testPhase: 'system',
1244+
}),
1245+
);
1246+
expect(
1247+
upsertFromLegacyReviewPayloadSpy.mock.invocationCallOrder[0],
1248+
).toBeLessThan(httpService.patch.mock.invocationCallOrder[0]);
12241249
});
12251250

1226-
it('does not persist relative summations when completing the review fails', async () => {
1251+
it('persists relative summations before completing the review', async () => {
12271252
const { service, httpService, m2mService, prisma } = createService();
12281253
const systemPayload: ScoringResultCallbackPayload = {
12291254
...basePayload,
@@ -1300,7 +1325,19 @@ describe('ScoringResultService', () => {
13001325
},
13011326
}),
13021327
);
1303-
expect(upsertReviewSummationSpy).not.toHaveBeenCalled();
1328+
expect(upsertReviewSummationSpy).toHaveBeenCalledWith(
1329+
'm2m-token',
1330+
'system',
1331+
expect.objectContaining({
1332+
aggregateScore: 100,
1333+
isFinal: true,
1334+
submissionId: systemPayload.submissionId,
1335+
}),
1336+
undefined,
1337+
);
1338+
expect(upsertReviewSummationSpy.mock.invocationCallOrder[0]).toBeLessThan(
1339+
httpService.patch.mock.invocationCallOrder[0],
1340+
);
13041341
});
13051342

13061343
it('returns a bad request error when review-api rejects a nonexistent submissionId', async () => {

src/api/scoring-result/scoring-result.service.ts

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ export class ScoringResultService {
216216

217217
/**
218218
* Processes one scorer callback payload after verifying the challenge config exists,
219-
* then completes any SYSTEM review before final summation writes.
219+
* then writes terminal summations before completing any SYSTEM review.
220220
*/
221221
async processScoringResult(
222222
payload: ScoringResultCallbackPayload,
@@ -279,18 +279,6 @@ export class ScoringResultService {
279279
payload.score,
280280
);
281281

282-
await this.completeSystemReviewIfNeeded(
283-
token,
284-
payload.reviewId,
285-
currentReviewScore,
286-
normalizedPhase,
287-
{
288-
challengeId: payload.challengeId,
289-
scorecardId: fallbackScorecardId,
290-
submissionId: payload.submissionId,
291-
},
292-
);
293-
294282
await this.upsertFromLegacyReviewPayload(token, {
295283
legacyReview: payload.currentReview,
296284
fallbackSubmissionId: payload.submissionId,
@@ -311,6 +299,18 @@ export class ScoringResultService {
311299
});
312300
}
313301

302+
await this.completeSystemReviewIfNeeded(
303+
token,
304+
payload.reviewId,
305+
currentReviewScore,
306+
normalizedPhase,
307+
{
308+
challengeId: payload.challengeId,
309+
scorecardId: fallbackScorecardId,
310+
submissionId: payload.submissionId,
311+
},
312+
);
313+
314314
await this.notifyScoringCompletionEmailIfReady(
315315
token,
316316
payload,
@@ -328,6 +328,7 @@ export class ScoringResultService {
328328
testPhase: normalizedPhase,
329329
});
330330

331+
await this.upsertReviewSummation(token, normalizedPhase, reviewPayload);
331332
await this.completeSystemReviewIfNeeded(
332333
token,
333334
payload.reviewId,
@@ -339,7 +340,6 @@ export class ScoringResultService {
339340
submissionId: payload.submissionId,
340341
},
341342
);
342-
await this.upsertReviewSummation(token, normalizedPhase, reviewPayload);
343343
await this.notifyScoringCompletionEmailIfReady(
344344
token,
345345
payload,
@@ -452,6 +452,7 @@ export class ScoringResultService {
452452
testPhase: normalizedPhase,
453453
});
454454

455+
await this.upsertReviewSummation(token, normalizedPhase, reviewPayload);
455456
await this.completeSystemReviewIfNeeded(
456457
token,
457458
input.reviewId,
@@ -463,7 +464,6 @@ export class ScoringResultService {
463464
submissionId: input.submissionId,
464465
},
465466
);
466-
await this.upsertReviewSummation(token, normalizedPhase, reviewPayload);
467467
}
468468

469469
/**
@@ -755,7 +755,7 @@ export class ScoringResultService {
755755
/**
756756
* Recomputes relative scores for the latest submission from each member while
757757
* holding a challenge/phase advisory lock for the read-compute-write cycle.
758-
* Completes the current SYSTEM review before writing the recomputed summations.
758+
* Writes recomputed summations before completing the current SYSTEM review.
759759
* Returns undefined when relative scoring cannot be applied and the caller
760760
* should fall back to direct review upserts.
761761
*/
@@ -894,18 +894,6 @@ export class ScoringResultService {
894894
return undefined;
895895
}
896896

897-
await this.completeSystemReviewIfNeeded(
898-
token,
899-
payload.reviewId,
900-
currentReviewPayload.payload.aggregateScore,
901-
testPhase,
902-
{
903-
challengeId: payload.challengeId,
904-
scorecardId: fallbackScorecardId,
905-
submissionId: payload.submissionId,
906-
},
907-
);
908-
909897
for (let index = 0; index < relativeReviewPayloads.length; index += 1) {
910898
const reviewPayload = relativeReviewPayloads[index];
911899
const reviewId = this.asString(reviewPayload.reviewObject.id);
@@ -930,6 +918,18 @@ export class ScoringResultService {
930918
);
931919
}
932920

921+
await this.completeSystemReviewIfNeeded(
922+
token,
923+
payload.reviewId,
924+
currentReviewPayload.payload.aggregateScore,
925+
testPhase,
926+
{
927+
challengeId: payload.challengeId,
928+
scorecardId: fallbackScorecardId,
929+
submissionId: payload.submissionId,
930+
},
931+
);
932+
933933
return currentReviewPayload?.payload.aggregateScore;
934934
}
935935

@@ -2801,7 +2801,7 @@ export class ScoringResultService {
28012801
}
28022802

28032803
/**
2804-
* Completes the originating review record before final SYSTEM summations are persisted.
2804+
* Completes the originating review record after final SYSTEM summations are persisted.
28052805
*/
28062806
private async completeSystemReviewIfNeeded(
28072807
token: string,

0 commit comments

Comments
 (0)