Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Queue;
4
 
5
use Illuminate\Contracts\Queue\ClearableQueue;
6
use Illuminate\Contracts\Queue\Queue as QueueContract;
7
use Illuminate\Database\Connection;
8
use Illuminate\Queue\Jobs\DatabaseJob;
9
use Illuminate\Queue\Jobs\DatabaseJobRecord;
10
use Illuminate\Support\Carbon;
11
use Illuminate\Support\Str;
12
use PDO;
13
 
14
class DatabaseQueue extends Queue implements QueueContract, ClearableQueue
15
{
16
    /**
17
     * The database connection instance.
18
     *
19
     * @var \Illuminate\Database\Connection
20
     */
21
    protected $database;
22
 
23
    /**
24
     * The database table that holds the jobs.
25
     *
26
     * @var string
27
     */
28
    protected $table;
29
 
30
    /**
31
     * The name of the default queue.
32
     *
33
     * @var string
34
     */
35
    protected $default;
36
 
37
    /**
38
     * The expiration time of a job.
39
     *
40
     * @var int|null
41
     */
42
    protected $retryAfter = 60;
43
 
44
    /**
45
     * Create a new database queue instance.
46
     *
47
     * @param  \Illuminate\Database\Connection  $database
48
     * @param  string  $table
49
     * @param  string  $default
50
     * @param  int  $retryAfter
51
     * @param  bool  $dispatchAfterCommit
52
     * @return void
53
     */
54
    public function __construct(Connection $database,
55
                                $table,
56
                                $default = 'default',
57
                                $retryAfter = 60,
58
                                $dispatchAfterCommit = false)
59
    {
60
        $this->table = $table;
61
        $this->default = $default;
62
        $this->database = $database;
63
        $this->retryAfter = $retryAfter;
64
        $this->dispatchAfterCommit = $dispatchAfterCommit;
65
    }
66
 
67
    /**
68
     * Get the size of the queue.
69
     *
70
     * @param  string|null  $queue
71
     * @return int
72
     */
73
    public function size($queue = null)
74
    {
75
        return $this->database->table($this->table)
76
                    ->where('queue', $this->getQueue($queue))
77
                    ->count();
78
    }
79
 
80
    /**
81
     * Push a new job onto the queue.
82
     *
83
     * @param  string  $job
84
     * @param  mixed  $data
85
     * @param  string|null  $queue
86
     * @return mixed
87
     */
88
    public function push($job, $data = '', $queue = null)
89
    {
90
        return $this->enqueueUsing(
91
            $job,
92
            $this->createPayload($job, $this->getQueue($queue), $data),
93
            $queue,
94
            null,
95
            function ($payload, $queue) {
96
                return $this->pushToDatabase($queue, $payload);
97
            }
98
        );
99
    }
100
 
101
    /**
102
     * Push a raw payload onto the queue.
103
     *
104
     * @param  string  $payload
105
     * @param  string|null  $queue
106
     * @param  array  $options
107
     * @return mixed
108
     */
109
    public function pushRaw($payload, $queue = null, array $options = [])
110
    {
111
        return $this->pushToDatabase($queue, $payload);
112
    }
113
 
114
    /**
115
     * Push a new job onto the queue after (n) seconds.
116
     *
117
     * @param  \DateTimeInterface|\DateInterval|int  $delay
118
     * @param  string  $job
119
     * @param  mixed  $data
120
     * @param  string|null  $queue
121
     * @return void
122
     */
123
    public function later($delay, $job, $data = '', $queue = null)
124
    {
125
        return $this->enqueueUsing(
126
            $job,
127
            $this->createPayload($job, $this->getQueue($queue), $data),
128
            $queue,
129
            $delay,
130
            function ($payload, $queue, $delay) {
131
                return $this->pushToDatabase($queue, $payload, $delay);
132
            }
133
        );
134
    }
135
 
136
    /**
137
     * Push an array of jobs onto the queue.
138
     *
139
     * @param  array  $jobs
140
     * @param  mixed  $data
141
     * @param  string|null  $queue
142
     * @return mixed
143
     */
144
    public function bulk($jobs, $data = '', $queue = null)
145
    {
146
        $queue = $this->getQueue($queue);
147
 
148
        $now = $this->availableAt();
149
 
150
        return $this->database->table($this->table)->insert(collect((array) $jobs)->map(
151
            function ($job) use ($queue, $data, $now) {
152
                return $this->buildDatabaseRecord(
153
                    $queue,
154
                    $this->createPayload($job, $this->getQueue($queue), $data),
155
                    isset($job->delay) ? $this->availableAt($job->delay) : $now,
156
                );
157
            }
158
        )->all());
159
    }
160
 
161
    /**
162
     * Release a reserved job back onto the queue after (n) seconds.
163
     *
164
     * @param  string  $queue
165
     * @param  \Illuminate\Queue\Jobs\DatabaseJobRecord  $job
166
     * @param  int  $delay
167
     * @return mixed
168
     */
169
    public function release($queue, $job, $delay)
170
    {
171
        return $this->pushToDatabase($queue, $job->payload, $delay, $job->attempts);
172
    }
173
 
174
    /**
175
     * Push a raw payload to the database with a given delay of (n) seconds.
176
     *
177
     * @param  string|null  $queue
178
     * @param  string  $payload
179
     * @param  \DateTimeInterface|\DateInterval|int  $delay
180
     * @param  int  $attempts
181
     * @return mixed
182
     */
183
    protected function pushToDatabase($queue, $payload, $delay = 0, $attempts = 0)
184
    {
185
        return $this->database->table($this->table)->insertGetId($this->buildDatabaseRecord(
186
            $this->getQueue($queue), $payload, $this->availableAt($delay), $attempts
187
        ));
188
    }
189
 
190
    /**
191
     * Create an array to insert for the given job.
192
     *
193
     * @param  string|null  $queue
194
     * @param  string  $payload
195
     * @param  int  $availableAt
196
     * @param  int  $attempts
197
     * @return array
198
     */
199
    protected function buildDatabaseRecord($queue, $payload, $availableAt, $attempts = 0)
200
    {
201
        return [
202
            'queue' => $queue,
203
            'attempts' => $attempts,
204
            'reserved_at' => null,
205
            'available_at' => $availableAt,
206
            'created_at' => $this->currentTime(),
207
            'payload' => $payload,
208
        ];
209
    }
210
 
211
    /**
212
     * Pop the next job off of the queue.
213
     *
214
     * @param  string|null  $queue
215
     * @return \Illuminate\Contracts\Queue\Job|null
216
     *
217
     * @throws \Throwable
218
     */
219
    public function pop($queue = null)
220
    {
221
        $queue = $this->getQueue($queue);
222
 
223
        return $this->database->transaction(function () use ($queue) {
224
            if ($job = $this->getNextAvailableJob($queue)) {
225
                return $this->marshalJob($queue, $job);
226
            }
227
        });
228
    }
229
 
230
    /**
231
     * Get the next available job for the queue.
232
     *
233
     * @param  string|null  $queue
234
     * @return \Illuminate\Queue\Jobs\DatabaseJobRecord|null
235
     */
236
    protected function getNextAvailableJob($queue)
237
    {
238
        $job = $this->database->table($this->table)
239
                    ->lock($this->getLockForPopping())
240
                    ->where('queue', $this->getQueue($queue))
241
                    ->where(function ($query) {
242
                        $this->isAvailable($query);
243
                        $this->isReservedButExpired($query);
244
                    })
245
                    ->orderBy('id', 'asc')
246
                    ->first();
247
 
248
        return $job ? new DatabaseJobRecord((object) $job) : null;
249
    }
250
 
251
    /**
252
     * Get the lock required for popping the next job.
253
     *
254
     * @return string|bool
255
     */
256
    protected function getLockForPopping()
257
    {
258
        $databaseEngine = $this->database->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME);
259
        $databaseVersion = $this->database->getConfig('version') ?? $this->database->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
260
 
261
        if (Str::of($databaseVersion)->contains('MariaDB')) {
262
            $databaseEngine = 'mariadb';
263
            $databaseVersion = Str::before(Str::after($databaseVersion, '5.5.5-'), '-');
264
        } elseif (Str::of($databaseVersion)->contains(['vitess', 'PlanetScale'])) {
265
            $databaseEngine = 'vitess';
266
            $databaseVersion = Str::before($databaseVersion, '-');
267
        }
268
 
269
        if (($databaseEngine === 'mysql' && version_compare($databaseVersion, '8.0.1', '>=')) ||
270
            ($databaseEngine === 'mariadb' && version_compare($databaseVersion, '10.6.0', '>=')) ||
271
            ($databaseEngine === 'pgsql' && version_compare($databaseVersion, '9.5', '>='))) {
272
            return 'FOR UPDATE SKIP LOCKED';
273
        }
274
 
275
        if ($databaseEngine === 'sqlsrv') {
276
            return 'with(rowlock,updlock,readpast)';
277
        }
278
 
279
        return true;
280
    }
281
 
282
    /**
283
     * Modify the query to check for available jobs.
284
     *
285
     * @param  \Illuminate\Database\Query\Builder  $query
286
     * @return void
287
     */
288
    protected function isAvailable($query)
289
    {
290
        $query->where(function ($query) {
291
            $query->whereNull('reserved_at')
292
                  ->where('available_at', '<=', $this->currentTime());
293
        });
294
    }
295
 
296
    /**
297
     * Modify the query to check for jobs that are reserved but have expired.
298
     *
299
     * @param  \Illuminate\Database\Query\Builder  $query
300
     * @return void
301
     */
302
    protected function isReservedButExpired($query)
303
    {
304
        $expiration = Carbon::now()->subSeconds($this->retryAfter)->getTimestamp();
305
 
306
        $query->orWhere(function ($query) use ($expiration) {
307
            $query->where('reserved_at', '<=', $expiration);
308
        });
309
    }
310
 
311
    /**
312
     * Marshal the reserved job into a DatabaseJob instance.
313
     *
314
     * @param  string  $queue
315
     * @param  \Illuminate\Queue\Jobs\DatabaseJobRecord  $job
316
     * @return \Illuminate\Queue\Jobs\DatabaseJob
317
     */
318
    protected function marshalJob($queue, $job)
319
    {
320
        $job = $this->markJobAsReserved($job);
321
 
322
        return new DatabaseJob(
323
            $this->container, $this, $job, $this->connectionName, $queue
324
        );
325
    }
326
 
327
    /**
328
     * Mark the given job ID as reserved.
329
     *
330
     * @param  \Illuminate\Queue\Jobs\DatabaseJobRecord  $job
331
     * @return \Illuminate\Queue\Jobs\DatabaseJobRecord
332
     */
333
    protected function markJobAsReserved($job)
334
    {
335
        $this->database->table($this->table)->where('id', $job->id)->update([
336
            'reserved_at' => $job->touch(),
337
            'attempts' => $job->increment(),
338
        ]);
339
 
340
        return $job;
341
    }
342
 
343
    /**
344
     * Delete a reserved job from the queue.
345
     *
346
     * @param  string  $queue
347
     * @param  string  $id
348
     * @return void
349
     *
350
     * @throws \Throwable
351
     */
352
    public function deleteReserved($queue, $id)
353
    {
354
        $this->database->transaction(function () use ($id) {
355
            if ($this->database->table($this->table)->lockForUpdate()->find($id)) {
356
                $this->database->table($this->table)->where('id', $id)->delete();
357
            }
358
        });
359
    }
360
 
361
    /**
362
     * Delete a reserved job from the reserved queue and release it.
363
     *
364
     * @param  string  $queue
365
     * @param  \Illuminate\Queue\Jobs\DatabaseJob  $job
366
     * @param  int  $delay
367
     * @return void
368
     */
369
    public function deleteAndRelease($queue, $job, $delay)
370
    {
371
        $this->database->transaction(function () use ($queue, $job, $delay) {
372
            if ($this->database->table($this->table)->lockForUpdate()->find($job->getJobId())) {
373
                $this->database->table($this->table)->where('id', $job->getJobId())->delete();
374
            }
375
 
376
            $this->release($queue, $job->getJobRecord(), $delay);
377
        });
378
    }
379
 
380
    /**
381
     * Delete all of the jobs from the queue.
382
     *
383
     * @param  string  $queue
384
     * @return int
385
     */
386
    public function clear($queue)
387
    {
388
        return $this->database->table($this->table)
389
                    ->where('queue', $this->getQueue($queue))
390
                    ->delete();
391
    }
392
 
393
    /**
394
     * Get the queue or return the default.
395
     *
396
     * @param  string|null  $queue
397
     * @return string
398
     */
399
    public function getQueue($queue)
400
    {
401
        return $queue ?: $this->default;
402
    }
403
 
404
    /**
405
     * Get the underlying database instance.
406
     *
407
     * @return \Illuminate\Database\Connection
408
     */
409
    public function getDatabase()
410
    {
411
        return $this->database;
412
    }
413
}