32
32
use Doctrine \DBAL \ArrayParameterType ;
33
33
use Doctrine \DBAL \Exception ;
34
34
use Doctrine \DBAL \Exception as DBALException ;
35
+ use Doctrine \DBAL \TransactionIsolationLevel ;
35
36
use Doctrine \ORM \AbstractQuery ;
36
37
use Doctrine \ORM \EntityManager ;
37
38
use Doctrine \ORM \EntityManagerInterface ;
@@ -348,9 +349,10 @@ public function updateJudgingAction(
348
349
349
350
$ rowsAffected = $ this ->em ->createQueryBuilder ()
350
351
->update (Submission::class, 's ' )
351
- ->set ('entry_point ' , $ newEntryPoint )
352
- ->where ('submitid = :id ' )
353
- ->andWhere ('entry_point IS NULL ' )
352
+ ->set ('s.entry_point ' , ':entrypoint ' )
353
+ ->where ('s.submitid = :id ' )
354
+ ->andWhere ('s.entry_point IS NULL ' )
355
+ ->setParameter ('entrypoint ' , $ newEntryPoint )
354
356
->setParameter ('id ' , $ submission ->getSubmitid ())
355
357
->getQuery ()
356
358
->execute ();
@@ -1387,6 +1389,28 @@ public function checkVersions(Request $request, string $judgetaskid): array
1387
1389
if ($ request ->request ->has ('runner ' )) {
1388
1390
$ reportedVersions ['runner ' ] = base64_decode ($ request ->request ->get ('runner ' ));
1389
1391
}
1392
+
1393
+ // We want the version update to be both lock-free manner while keeping one latest version exposed.
1394
+ // To ensure this is the case a READ_UNCOMMITTED transaction is used. This is not ideal, but does keep both
1395
+ // constraints. The updating threads might see multiple versions, but only the newest one is exposed.
1396
+ //
1397
+ // Keep in mind that there might be multiple concurrent updates while reading the explanation.
1398
+ // - First insert a new version assuming it will be the latest version.
1399
+ // - Retrieve all 'new versions', not newest 'new versions' stop processing.
1400
+ // - The newest 'new version' assumes it is the latest version and updates all "older" 'new versions' to
1401
+ // not be the latest.
1402
+ // Age is determined by `judgetaskid`. The greatest `judgetaskid` is the newest. It might be possible that yet
1403
+ // another thread is inserting versions. Though unfortunate there is no way to prevent this.
1404
+ // Runs in a READ_UNCOMMITTED transaction to make sure concurrent threads see, and can update,
1405
+ // required values.
1406
+ $ this ->em ->wrapInTransaction (function ($ em ) {
1407
+ $ isoLevel = $ this ->em ->getConnection ()->getTransactionIsolation ();
1408
+ $ em ->getConnection ()->setTransactionIsolation (TransactionIsolationLevel::READ_UNCOMMITTED );
1409
+
1410
+ // Restore the isolation level.
1411
+ $ em ->getConnection ()->setTransactionIsolation ($ isoLevel );
1412
+ });
1413
+
1390
1414
$ this ->em ->wrapInTransaction (function () use (
1391
1415
$ judgehost ,
1392
1416
$ reportedVersions ,
@@ -1396,10 +1420,12 @@ public function checkVersions(Request $request, string $judgetaskid): array
1396
1420
$ activeVersion = $ this ->em ->getRepository (Version::class)
1397
1421
->findOneBy (['language ' => $ language , 'judgehost ' => $ judgehost , 'active ' => true ]);
1398
1422
1399
- $ isNewVersion = false ;
1400
- if (!$ activeVersion ) {
1401
- $ isNewVersion = true ;
1402
- } else {
1423
+ $ isNewVersion = !$ activeVersion ||
1424
+ $ activeVersion ->getCompilerVersion () !== @$ reportedVersions ['compiler ' ] ||
1425
+ $ activeVersion ->getRunnerVersion () !== @$ reportedVersions ['runner ' ];
1426
+
1427
+ $ isNewVersion = !$ activeVersion ;
1428
+ if (!$ isNewVersion ) {
1403
1429
$ reportedCompilerVersion = $ reportedVersions ['compiler ' ] ?? null ;
1404
1430
if ($ activeVersion ->getCompilerVersion () !== $ reportedCompilerVersion ) {
1405
1431
$ isNewVersion = true ;
0 commit comments