22
33use Carbon \Carbon ;
44use Illuminate \Queue \DatabaseQueue ;
5+ use Illuminate \Queue \Jobs \DatabaseJob ;
6+ use MongoDB \Operation \FindOneAndUpdate ;
7+ use DB ;
58
69class MongoQueue extends DatabaseQueue
710{
811 /**
9- * Get the next available job for the queue.
12+ * Pop the next job off of the queue.
13+ *
14+ * @param string $queue
15+ *
16+ * @return \Illuminate\Contracts\Queue\Job|null
17+ */
18+ public function pop ($ queue = null )
19+ {
20+ $ queue = $ this ->getQueue ($ queue );
21+
22+ if (!is_null ($ this ->expire )) {
23+ $ this ->releaseJobsThatHaveBeenReservedTooLong ($ queue );
24+ }
25+
26+ if ($ job = $ this ->getNextAvailableJobAndReserve ($ queue )) {
27+ return new DatabaseJob (
28+ $ this ->container , $ this , $ job , $ queue
29+ );
30+ }
31+ }
32+
33+ /**
34+ * Get the next available job for the queue and mark it as reserved.
35+ *
36+ * When using multiple daemon queue listeners to process jobs there
37+ * is a possibility that multiple processes can end up reading the
38+ * same record before one has flagged it as reserved.
39+ *
40+ * This race condition can result in random jobs being run more then
41+ * once. To solve this we use findOneAndUpdate to lock the next jobs
42+ * record while flagging it as reserved at the same time.
43+ *
44+ * @param string|null $queue
1045 *
11- * @param string|null $queue
1246 * @return \StdClass|null
1347 */
14- protected function getNextAvailableJob ($ queue )
48+ protected function getNextAvailableJobAndReserve ($ queue )
1549 {
16- $ job = $ this ->database ->table ($ this ->table )
17- ->lockForUpdate ()
18- ->where ('queue ' , $ this ->getQueue ($ queue ))
19- ->where ('reserved ' , 0 )
20- ->where ('available_at ' , '<= ' , $ this ->getTime ())
21- ->orderBy ('id ' , 'asc ' )
22- ->first ();
50+ $ job = DB ::getCollection ($ this ->table )->findOneAndUpdate (
51+ [
52+ 'queue ' => $ this ->getQueue ($ queue ),
53+ 'reserved ' => 0 ,
54+ 'available_at ' => ['$lte ' => $ this ->getTime ()],
55+
56+ ],
57+ [
58+ '$set ' => [
59+ 'reserved ' => 1 ,
60+ 'reserved_at ' => $ this ->getTime (),
61+ ],
62+ ],
63+ [
64+ 'returnDocument ' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER ,
65+ 'sort ' => ['available_at ' => 1 ],
66+ ]
67+ );
2368
2469 if ($ job ) {
25- $ job = (object ) $ job ;
2670 $ job ->id = $ job ->_id ;
2771 }
2872
29- return $ job ?: null ;
73+ return $ job ;
3074 }
3175
3276 /**
@@ -40,16 +84,16 @@ protected function releaseJobsThatHaveBeenReservedTooLong($queue)
4084 $ expired = Carbon::now ()->subSeconds ($ this ->expire )->getTimestamp ();
4185
4286 $ reserved = $ this ->database ->collection ($ this ->table )
43- ->where ('queue ' , $ this ->getQueue ($ queue ))
44- ->where ('reserved ' , 1 )
45- ->where ('reserved_at ' , '<= ' , $ expired )->get ();
87+ ->where ('queue ' , $ this ->getQueue ($ queue ))
88+ ->where ('reserved ' , 1 )
89+ ->where ('reserved_at ' , '<= ' , $ expired )->get ();
4690
4791 foreach ($ reserved as $ job ) {
4892 $ attempts = $ job ['attempts ' ] + 1 ;
4993 $ this ->releaseJob ($ job ['_id ' ], $ attempts );
5094 }
5195 }
52-
96+
5397 /**
5498 * Release the given job ID from reservation.
5599 *
@@ -66,19 +110,6 @@ protected function releaseJob($id, $attempts)
66110 ]);
67111 }
68112
69- /**
70- * Mark the given job ID as reserved.
71- *
72- * @param string $id
73- * @return void
74- */
75- protected function markJobAsReserved ($ id )
76- {
77- $ this ->database ->collection ($ this ->table )->where ('_id ' , $ id )->update ([
78- 'reserved ' => 1 , 'reserved_at ' => $ this ->getTime (),
79- ]);
80- }
81-
82113 /**
83114 * Delete a reserved job from the queue.
84115 *
0 commit comments