Skip to content
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 45 commits into from May 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
625719a
initial wip
taylorotwell May 13, 2020
19833c4
wip
taylorotwell May 13, 2020
8a9bcb1
wip
taylorotwell May 13, 2020
df1bedc
wip
taylorotwell May 13, 2020
0f73e71
wip
taylorotwell May 13, 2020
b16b06e
wip
taylorotwell May 13, 2020
9f425e4
wip
taylorotwell May 13, 2020
55bf88e
wip
taylorotwell May 14, 2020
f1b0b44
wip
taylorotwell May 14, 2020
5b1a494
wip
taylorotwell May 14, 2020
5b77a0f
wip
taylorotwell May 14, 2020
93e72b9
wip
taylorotwell May 14, 2020
26d440f
wip
taylorotwell May 14, 2020
b233839
wip
taylorotwell May 14, 2020
6f1f12a
wip
taylorotwell May 14, 2020
cee1dd3
wip
taylorotwell May 14, 2020
061e05b
wip
taylorotwell May 14, 2020
9843020
wip
taylorotwell May 14, 2020
59fe606
wip
taylorotwell May 14, 2020
36d3899
wip
taylorotwell May 15, 2020
3a20748
wip
taylorotwell May 15, 2020
50accae
wip
taylorotwell May 15, 2020
9232f46
wip
taylorotwell May 15, 2020
b1d7f4f
wip
taylorotwell May 15, 2020
3fa1936
wip
taylorotwell May 15, 2020
bca0748
wip
taylorotwell May 15, 2020
e64abe1
wip
taylorotwell May 15, 2020
2eca840
wip
taylorotwell May 15, 2020
1462403
Apply fixes from StyleCI (#32820)
taylorotwell May 15, 2020
d2df4a9
wip
taylorotwell May 15, 2020
36a640c
wip
taylorotwell May 15, 2020
a02b203
Merge branch 'batches' of github.com:laravel/framework into batches
taylorotwell May 15, 2020
fd3384c
Apply fixes from StyleCI (#32829)
taylorotwell May 15, 2020
7aedc56
add batches table command
themsaid May 15, 2020
b803b86
Apply fixes from StyleCI (#32831)
themsaid May 15, 2020
1ec1280
fix doc block
taylorotwell May 15, 2020
77ea7e4
fix doc block
taylorotwell May 15, 2020
f3fc80d
wip
taylorotwell May 15, 2020
99018fc
wip
taylorotwell May 15, 2020
efe73f1
opis/closure minimum version
themsaid May 15, 2020
a3e7478
Merge branch 'batches' of https://github.com/laravel/framework into b…
themsaid May 15, 2020
959b369
wip
themsaid May 15, 2020
fb29922
wip
taylorotwell May 15, 2020
c336dc2
formatting
taylorotwell May 15, 2020
f26557e
formatting
taylorotwell May 15, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -26,7 +26,7 @@
"league/flysystem": "^1.0.8",
"monolog/monolog": "^2.0",
"nesbot/carbon": "^2.17",
"opis/closure": "^3.1",
"opis/closure": "^3.5",
"psr/container": "^1.0",
"psr/simple-cache": "^1.0",
"ramsey/uuid": "^4.0",
Expand Down
380 changes: 380 additions & 0 deletions src/Illuminate/Bus/Batch.php
@@ -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,
Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Contributor

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

Typed properties cannot have a null default value, unless the type is explicitly nullable (?Type). This is in contrast to parameter types, where a null default value automatically implies a nullable type. We consider this to be a legacy behavior, which we do not wish to support for newly introduced syntax.

Copy link
Member

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.

Copy link
Member

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)

Copy link
Member

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...

Copy link
Member

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.

?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.
Copy link

@ghost ghost May 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the return type?

Should be;
@return int

*/
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,
];
}
}