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
+ {
24
+ $ this ->releaseJobsThatHaveBeenReservedTooLong ($ queue );
25
+ }
26
+
27
+ if ($ job = $ this ->getNextAvailableJobAndReserve ($ queue ))
28
+ {
29
+ return new DatabaseJob (
30
+ $ this ->container , $ this , $ job , $ queue
31
+ );
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Get the next available job for the queue and mark it as reserved.
37
+ *
38
+ * When using multiple daemon queue listeners to process jobs there
39
+ * is a possibility that multiple processes can end up reading the
40
+ * same record before one has flagged it as reserved.
41
+ *
42
+ * This race condition can result in random jobs being run more then
43
+ * once. To solve this we use findOneAndUpdate to lock the next jobs
44
+ * record while flagging it as reserved at the same time.
45
+ *
46
+ * @param string|null $queue
10
47
*
11
- * @param string|null $queue
12
48
* @return \StdClass|null
13
49
*/
14
- protected function getNextAvailableJob ($ queue )
50
+ protected function getNextAvailableJobAndReserve ($ queue )
15
51
{
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 ();
52
+ $ job = DB ::getCollection ($ this ->table )->findOneAndUpdate (
53
+ [
54
+ 'queue ' => $ this ->getQueue ($ queue ),
55
+ 'reserved ' => 0 ,
56
+ 'available_at ' => ['$lte ' => $ this ->getTime ()],
23
57
24
- if ($ job ) {
25
- $ job = (object ) $ job ;
58
+ ],
59
+ [
60
+ '$set ' => [
61
+ 'reserved ' => 1 ,
62
+ 'reserved_at ' => $ this ->getTime (),
63
+ ],
64
+ ],
65
+ [
66
+ 'returnNewDocument ' => true ,
67
+ 'sort ' => ['available_at ' => 1 ],
68
+ ]
69
+ );
70
+
71
+ if ($ job )
72
+ {
26
73
$ job ->id = $ job ->_id ;
27
74
}
28
75
29
- return $ job ?: null ;
76
+ return $ job ;
30
77
}
31
78
32
79
/**
@@ -40,16 +87,16 @@ protected function releaseJobsThatHaveBeenReservedTooLong($queue)
40
87
$ expired = Carbon::now ()->subSeconds ($ this ->expire )->getTimestamp ();
41
88
42
89
$ reserved = $ this ->database ->collection ($ this ->table )
43
- ->where ('queue ' , $ this ->getQueue ($ queue ))
44
- ->where ('reserved ' , 1 )
45
- ->where ('reserved_at ' , '<= ' , $ expired )->get ();
90
+ ->where ('queue ' , $ this ->getQueue ($ queue ))
91
+ ->where ('reserved ' , 1 )
92
+ ->where ('reserved_at ' , '<= ' , $ expired )->get ();
46
93
47
94
foreach ($ reserved as $ job ) {
48
95
$ attempts = $ job ['attempts ' ] + 1 ;
49
96
$ this ->releaseJob ($ job ['_id ' ], $ attempts );
50
97
}
51
98
}
52
-
99
+
53
100
/**
54
101
* Release the given job ID from reservation.
55
102
*
0 commit comments