2
2
3
3
use Carbon \Carbon ;
4
4
use Illuminate \Queue \DatabaseQueue ;
5
+ use Illuminate \Queue \Jobs \DatabaseJob ;
6
+ use MongoDB \Operation \FindOneAndUpdate ;
7
+ use DB ;
5
8
6
9
class MongoQueue extends DatabaseQueue
7
10
{
8
11
/**
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
10
45
*
11
- * @param string|null $queue
12
46
* @return \StdClass|null
13
47
*/
14
- protected function getNextAvailableJob ($ queue )
48
+ protected function getNextAvailableJobAndReserve ($ queue )
15
49
{
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
+ );
23
68
24
69
if ($ job ) {
25
- $ job = (object ) $ job ;
26
70
$ job ->id = $ job ->_id ;
27
71
}
28
72
29
- return $ job ?: null ;
73
+ return $ job ;
30
74
}
31
75
32
76
/**
@@ -40,16 +84,16 @@ protected function releaseJobsThatHaveBeenReservedTooLong($queue)
40
84
$ expired = Carbon::now ()->subSeconds ($ this ->expire )->getTimestamp ();
41
85
42
86
$ 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 ();
46
90
47
91
foreach ($ reserved as $ job ) {
48
92
$ attempts = $ job ['attempts ' ] + 1 ;
49
93
$ this ->releaseJob ($ job ['_id ' ], $ attempts );
50
94
}
51
95
}
52
-
96
+
53
97
/**
54
98
* Release the given job ID from reservation.
55
99
*
@@ -66,19 +110,6 @@ protected function releaseJob($id, $attempts)
66
110
]);
67
111
}
68
112
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
-
82
113
/**
83
114
* Delete a reserved job from the queue.
84
115
*
0 commit comments