New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[8.x] Job Batching #32830
Merged
Merged
[8.x] Job Batching #32830
Changes from all commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
625719a
initial wip
taylorotwell 19833c4
wip
taylorotwell 8a9bcb1
wip
taylorotwell df1bedc
wip
taylorotwell 0f73e71
wip
taylorotwell b16b06e
wip
taylorotwell 9f425e4
wip
taylorotwell 55bf88e
wip
taylorotwell f1b0b44
wip
taylorotwell 5b1a494
wip
taylorotwell 5b77a0f
wip
taylorotwell 93e72b9
wip
taylorotwell 26d440f
wip
taylorotwell b233839
wip
taylorotwell 6f1f12a
wip
taylorotwell cee1dd3
wip
taylorotwell 061e05b
wip
taylorotwell 9843020
wip
taylorotwell 59fe606
wip
taylorotwell 36d3899
wip
taylorotwell 3a20748
wip
taylorotwell 50accae
wip
taylorotwell 9232f46
wip
taylorotwell b1d7f4f
wip
taylorotwell 3fa1936
wip
taylorotwell bca0748
wip
taylorotwell e64abe1
wip
taylorotwell 2eca840
wip
taylorotwell 1462403
Apply fixes from StyleCI (#32820)
taylorotwell d2df4a9
wip
taylorotwell 36a640c
wip
taylorotwell a02b203
Merge branch 'batches' of github.com:laravel/framework into batches
taylorotwell fd3384c
Apply fixes from StyleCI (#32829)
taylorotwell 7aedc56
add batches table command
themsaid b803b86
Apply fixes from StyleCI (#32831)
themsaid 1ec1280
fix doc block
taylorotwell 77ea7e4
fix doc block
taylorotwell f3fc80d
wip
taylorotwell 99018fc
wip
taylorotwell efe73f1
opis/closure minimum version
themsaid a3e7478
Merge branch 'batches' of https://github.com/laravel/framework into b…
themsaid 959b369
wip
themsaid fb29922
wip
taylorotwell c336dc2
formatting
taylorotwell f26557e
formatting
taylorotwell File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,380 @@ | ||
<?php | ||
|
||
namespace Illuminate\Bus; | ||
|
||
use Carbon\CarbonImmutable; | ||
use Illuminate\Collections\Arr; | ||
use Illuminate\Collections\Collection; | ||
use Illuminate\Contracts\Queue\Factory as QueueFactory; | ||
use JsonSerializable; | ||
|
||
class Batch implements JsonSerializable | ||
{ | ||
/** | ||
* The queue factory implementation. | ||
* | ||
* @var \Illuminate\Contracts\Queue\Factory | ||
*/ | ||
protected $queue; | ||
|
||
/** | ||
* The repository implementation. | ||
* | ||
* @var \Illuminate\Bus\BatchRepository | ||
*/ | ||
protected $repository; | ||
|
||
/** | ||
* The batch ID. | ||
* | ||
* @var string | ||
*/ | ||
public $id; | ||
|
||
/** | ||
* The total number of jobs that belong to the batch. | ||
* | ||
* @var int | ||
*/ | ||
public $totalJobs; | ||
|
||
/** | ||
* The total number of jobs that are still pending. | ||
* | ||
* @var int | ||
*/ | ||
public $pendingJobs; | ||
|
||
/** | ||
* The total number of jobs that have failed. | ||
* | ||
* @var int | ||
*/ | ||
public $failedJobs; | ||
|
||
/** | ||
* The IDs of the jobs that have failed. | ||
* | ||
* @var array | ||
*/ | ||
public $failedJobIds; | ||
|
||
/** | ||
* The batch options. | ||
* | ||
* @var array | ||
*/ | ||
public $options; | ||
|
||
/** | ||
* The date indicating when the batch was created. | ||
* | ||
* @var \Illuminate\Support\CarbonImmutable | ||
*/ | ||
public $createdAt; | ||
|
||
/** | ||
* The date indicating when the batch was cancelled. | ||
* | ||
* @var \Illuminate\Support\CarbonImmutable|null | ||
*/ | ||
public $cancelledAt; | ||
|
||
/** | ||
* The date indicating when the batch was finished. | ||
* | ||
* @var \Illuminate\Support\CarbonImmutable|null | ||
*/ | ||
public $finishedAt; | ||
|
||
/** | ||
* Create a new batch instance. | ||
* | ||
* @param \Illuminate\Contracts\Queue\Factory $queue | ||
* @param \Illuminate\Bus\BatchRepository $repository | ||
* @param string $id | ||
* @param int $totalJobs | ||
* @param int $pendingJobs | ||
* @param int $failedJobs | ||
* @param array $failedJobIds | ||
* @param array $options | ||
* @param \Illuminate\Support\CarbonImmutable $createdAt | ||
* @param \Illuminate\Support\CarbonImmutable|null $cancelledAt | ||
* @param \Illuminate\Support\CarbonImmutable|null $finishedAt | ||
* @return void | ||
*/ | ||
public function __construct(QueueFactory $queue, | ||
BatchRepository $repository, | ||
string $id, | ||
int $totalJobs, | ||
int $pendingJobs, | ||
int $failedJobs, | ||
array $failedJobIds, | ||
array $options, | ||
CarbonImmutable $createdAt, | ||
?CarbonImmutable $cancelledAt = null, | ||
?CarbonImmutable $finishedAt = null) | ||
{ | ||
$this->queue = $queue; | ||
$this->repository = $repository; | ||
$this->id = $id; | ||
$this->totalJobs = $totalJobs; | ||
$this->pendingJobs = $pendingJobs; | ||
$this->failedJobs = $failedJobs; | ||
$this->failedJobIds = $failedJobIds; | ||
$this->options = $options; | ||
$this->createdAt = $createdAt; | ||
$this->cancelledAt = $cancelledAt; | ||
$this->finishedAt = $finishedAt; | ||
} | ||
|
||
/** | ||
* Get a fresh instance of the batch represented by this ID. | ||
* | ||
* @return self | ||
*/ | ||
public function fresh() | ||
{ | ||
return $this->repository->find($this->id); | ||
} | ||
|
||
/** | ||
* Add additional jobs to the batch. | ||
* | ||
* @param \Illuminate\Collections\Collection|array $jobs | ||
* @return self | ||
*/ | ||
public function add($jobs) | ||
{ | ||
$jobs = Collection::wrap($jobs); | ||
|
||
$jobs->each->withBatchId($this->id); | ||
|
||
$this->repository->transaction(function () use ($jobs) { | ||
$this->repository->incrementTotalJobs($this->id, count($jobs)); | ||
|
||
$this->queue->connection($this->options['connection'] ?? null)->bulk( | ||
$jobs->all(), | ||
$data = '', | ||
$this->options['queue'] ?? null | ||
); | ||
}); | ||
|
||
return $this->fresh(); | ||
} | ||
|
||
/** | ||
* Get the total number of jobs that have been processed by the batch thus far. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is the return type? Should be; |
||
*/ | ||
public function processedJobs() | ||
{ | ||
return $this->totalJobs - $this->pendingJobs; | ||
} | ||
|
||
/** | ||
* Get the percentage of jobs that have been processed. | ||
* | ||
* @return float | ||
*/ | ||
public function progress() | ||
{ | ||
return $this->totalJobs > 0 ? round($this->processedJobs() / $this->totalJobs, 2) : 0; | ||
} | ||
|
||
/** | ||
* Record that a job within the batch finished successfully, executing any callbacks if necessary. | ||
* | ||
* @param string $jobId | ||
* @return void | ||
*/ | ||
public function recordSuccessfulJob(string $jobId) | ||
{ | ||
$counts = $this->decrementPendingJobs($jobId); | ||
|
||
if ($counts->pendingJobs === 0) { | ||
$this->repository->markAsFinished($this->id); | ||
} | ||
|
||
if ($counts->pendingJobs === 0 && $this->hasSuccessCallbacks()) { | ||
$batch = $this->fresh(); | ||
|
||
collect($this->options['success'])->each->__invoke($batch); | ||
} | ||
|
||
if ($counts->allJobsHaveRanExactlyOnce() && $this->hasThenCallbacks()) { | ||
$batch = $this->fresh(); | ||
|
||
collect($this->options['then'])->each->__invoke($batch); | ||
} | ||
} | ||
|
||
/** | ||
* Decrement the pending jobs for the batch. | ||
* | ||
* @param string $jobId | ||
* @return int | ||
*/ | ||
public function decrementPendingJobs(string $jobId) | ||
{ | ||
return $this->repository->decrementPendingJobs($this->id, $jobId); | ||
} | ||
|
||
/** | ||
* Determine if the batch has finished executing. | ||
* | ||
* @return bool | ||
*/ | ||
public function finished() | ||
{ | ||
return ! is_null($this->finishedAt); | ||
} | ||
|
||
/** | ||
* Determine if the batch has "success" callbacks. | ||
* | ||
* @return bool | ||
*/ | ||
public function hasSuccessCallbacks() | ||
{ | ||
return isset($this->options['success']) && ! empty($this->options['success']); | ||
} | ||
|
||
/** | ||
* Determine if the batch allows jobs to fail without cancelling the batch. | ||
* | ||
* @return bool | ||
*/ | ||
public function allowsFailures() | ||
{ | ||
return Arr::get($this->options, 'allowFailures', false) === true; | ||
} | ||
|
||
/** | ||
* Determine if the batch has job failures. | ||
* | ||
* @return bool | ||
*/ | ||
public function hasFailures() | ||
{ | ||
return $this->failedJobs > 0; | ||
} | ||
|
||
/** | ||
* Record that a job within the batch failed to finish successfully, executing any callbacks if necessary. | ||
* | ||
* @param string $jobId | ||
* @param \Throwable $e | ||
* @return void | ||
*/ | ||
public function recordFailedJob(string $jobId, $e) | ||
{ | ||
$counts = $this->incrementFailedJobs($jobId); | ||
|
||
if ($counts->failedJobs === 1 && ! $this->allowsFailures()) { | ||
$this->cancel(); | ||
} | ||
|
||
if ($counts->failedJobs === 1 && $this->hasCatchCallbacks()) { | ||
$batch = $this->fresh(); | ||
|
||
collect($this->options['catch'])->each->__invoke($batch, $e); | ||
} | ||
|
||
if ($counts->allJobsHaveRanExactlyOnce() && $this->hasThenCallbacks()) { | ||
$batch = $this->fresh(); | ||
|
||
collect($this->options['then'])->each->__invoke($batch, $e); | ||
} | ||
} | ||
|
||
/** | ||
* Increment the failed jobs for the batch. | ||
* | ||
* @param string $jobId | ||
* @return int | ||
*/ | ||
public function incrementFailedJobs(string $jobId) | ||
{ | ||
return $this->repository->incrementFailedJobs($this->id, $jobId); | ||
} | ||
|
||
/** | ||
* Determine if the batch has "catch" callbacks. | ||
* | ||
* @return bool | ||
*/ | ||
public function hasCatchCallbacks() | ||
{ | ||
return isset($this->options['catch']) && ! empty($this->options['catch']); | ||
} | ||
|
||
/** | ||
* Determine if the batch has "then" callbacks. | ||
* | ||
* @return bool | ||
*/ | ||
public function hasThenCallbacks() | ||
{ | ||
return isset($this->options['then']) && ! empty($this->options['then']); | ||
} | ||
|
||
/** | ||
* Cancel the batch. | ||
* | ||
* @return void | ||
*/ | ||
public function cancel() | ||
{ | ||
$this->repository->cancel($this->id); | ||
} | ||
|
||
/** | ||
* Determine if the batch has been cancelled. | ||
* | ||
* @return bool | ||
*/ | ||
public function canceled() | ||
{ | ||
return $this->cancelled(); | ||
} | ||
|
||
/** | ||
* Determine if the batch has been cancelled. | ||
* | ||
* @return bool | ||
*/ | ||
public function cancelled() | ||
{ | ||
return ! is_null($this->cancelledAt); | ||
} | ||
|
||
/** | ||
* Delete the batch from storage. | ||
* | ||
* @return void | ||
*/ | ||
public function delete() | ||
{ | ||
$this->repository->delete($this->id); | ||
} | ||
|
||
/** | ||
* Get the JSON serializable representation of the object. | ||
* | ||
* @return array | ||
*/ | ||
public function jsonSerialize() | ||
{ | ||
return [ | ||
'id' => $this->id, | ||
'totalJobs' => $this->totalJobs, | ||
'pendingJobs' => $this->pendingJobs, | ||
'processedJobs' => $this->processedJobs(), | ||
'progress' => $this->progress(), | ||
'failedJobs' => $this->failedJobs, | ||
'createdAt' => $this->createdAt, | ||
'cancelledAt' => $this->cancelledAt, | ||
'finishedAt' => $this->finishedAt, | ||
]; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually laravel does not use the questionmark and implements nullable types with the default value being null. Not both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is legacy PHP behavior which IMO shouldn't be used anymore. The new database factory code (which was also written by Taylor) also uses the question mark.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://wiki.php.net/rfc/typed_properties_v2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many people still use the style without the question mark. Including me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(and the rest of the laravel/framework source code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If laravel wants to change its code style to use the question mark, StyleCI can enforce this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can also enforce non-usage if we want to enable that rule on the laravel preset.