Skip to content

Commit cfd8dd3

Browse files
committed
Handling possible race condition in exam creation by retry mechanism.
1 parent 82c9b2b commit cfd8dd3

1 file changed

Lines changed: 37 additions & 11 deletions

File tree

app/model/repository/GroupExams.php

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Model\Entity\Group;
66
use App\Model\Entity\GroupExam;
77
use Doctrine\ORM\EntityManagerInterface;
8+
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
89
use DateTime;
910
use Exception;
1011

@@ -38,6 +39,36 @@ public function findPendingForGroup(Group $group): ?GroupExam
3839
return $exam ? reset($exam) : null;
3940
}
4041

42+
/**
43+
* Internal helper that tries to find the exam or create it if not present.
44+
* It returns null if creation failed due to race condition (expects retry by caller).
45+
* @param Group $group
46+
* @param DateTime $begin
47+
* @param DateTime $end
48+
* @param bool $strict
49+
* @return GroupExam|null
50+
*/
51+
private function tryFindOrCreate(Group $group, DateTime $begin, DateTime $end, bool $strict): ?GroupExam
52+
{
53+
$exam = $this->findBy(["group" => $group, "begin" => $begin]);
54+
if (count($exam) > 1) {
55+
throw new Exception("Data corruption, there is more than one group exam starting at the same time.");
56+
}
57+
58+
if (!$exam) {
59+
$exam = new GroupExam($group, $begin, $end, $strict);
60+
try {
61+
$this->persist($exam);
62+
} catch (UniqueConstraintViolationException) {
63+
return null;
64+
}
65+
} else {
66+
$exam = reset($exam);
67+
}
68+
69+
return $exam;
70+
}
71+
4172
/**
4273
* Fetch group exam entity by group-begin index. If not present, new entity is created.
4374
* @param Group $group
@@ -56,18 +87,13 @@ public function findOrCreate(
5687
$end = $end ?? $group->getExamEnd();
5788
$strict = $strict === null ? $group->isExamLockStrict() : $strict;
5889

59-
$exam = $this->findBy(["group" => $group, "begin" => $begin]);
60-
if (count($exam) > 1) {
61-
throw new Exception("Data corruption, there is more than one group exam starting at the same time.");
62-
}
63-
64-
if (!$exam) {
65-
$exam = new GroupExam($group, $begin, $end, $strict);
66-
$this->persist($exam);
67-
} else {
68-
$exam = reset($exam);
90+
for ($retries = 0; $retries < 3; $retries++) {
91+
$exam = $this->tryFindOrCreate($group, $begin, $end, $strict);
92+
if ($exam !== null) {
93+
return $exam;
94+
}
6995
}
7096

71-
return $exam;
97+
throw new Exception("Failed to find or create group exam after multiple attempts.");
7298
}
7399
}

0 commit comments

Comments
 (0)