55use App \Model \Entity \Group ;
66use App \Model \Entity \GroupExam ;
77use Doctrine \ORM \EntityManagerInterface ;
8+ use Doctrine \DBAL \Exception \UniqueConstraintViolationException ;
89use DateTime ;
910use Exception ;
1011
@@ -38,6 +39,44 @@ 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+ try {
60+ $ this ->em ->getConnection ()->executeQuery (
61+ "INSERT INTO group_exams (group_id, begin, end, lock_strict) VALUES (:gid, :begin, :end, :strict) " ,
62+ [
63+ 'gid ' => $ group ->getId (),
64+ 'begin ' => $ begin ->format ('Y-m-d H:i:s ' ),
65+ 'end ' => $ end ->format ('Y-m-d H:i:s ' ),
66+ 'strict ' => $ strict ? 1 : 0
67+ ]
68+ );
69+ } catch (UniqueConstraintViolationException ) {
70+ // race condition, another transaction created the entity meanwhile
71+ }
72+ return null ; // signal caller to retry
73+ } else {
74+ $ exam = reset ($ exam );
75+ }
76+
77+ return $ exam ;
78+ }
79+
4180 /**
4281 * Fetch group exam entity by group-begin index. If not present, new entity is created.
4382 * @param Group $group
@@ -56,18 +95,13 @@ public function findOrCreate(
5695 $ end = $ end ?? $ group ->getExamEnd ();
5796 $ strict = $ strict === null ? $ group ->isExamLockStrict () : $ strict ;
5897
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. " );
98+ for ($ retries = 0 ; $ retries < 3 ; $ retries ++) {
99+ $ exam = $ this ->tryFindOrCreate ($ group , $ begin , $ end , $ strict );
100+ if ($ exam !== null ) {
101+ return $ exam ;
102+ }
62103 }
63104
64- if (!$ exam ) {
65- $ exam = new GroupExam ($ group , $ begin , $ end , $ strict );
66- $ this ->persist ($ exam );
67- } else {
68- $ exam = reset ($ exam );
69- }
70-
71- return $ exam ;
105+ throw new Exception ("Failed to find or create group exam after multiple attempts. " );
72106 }
73107}
0 commit comments