Skip to content

Commit 4b224ae

Browse files
authored
Merge branch '4.x-dev' into 4.x-dev
2 parents 829f720 + 3ffdee8 commit 4b224ae

7 files changed

+38
-64
lines changed

src/CloudTasksJob.php

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,24 +69,9 @@ public function getTaskName(): string
6969

7070
public function delete(): void
7171
{
72-
// Laravel automatically calls delete() after a job is processed successfully. However, this is
73-
// not what we want to happen in Cloud Tasks because Cloud Tasks will also delete the task upon
74-
// a 200 OK status, which means a task is deleted twice, possibly resulting in errors. So if the
75-
// task was processed successfully (no errors or failures) then we will not delete the task
76-
// manually and will let Cloud Tasks do it.
77-
$successful =
78-
// If the task has failed, we should be able to delete it permanently
79-
$this->hasFailed() === false
80-
// If the task has errored, it should be released, which in process deletes the errored task
81-
&& $this->hasError() === false;
82-
83-
if ($successful) {
84-
return;
85-
}
86-
87-
parent::delete();
88-
89-
$this->driver->delete($this);
72+
// Laravel automatically calls delete() after a job is processed successfully.
73+
// However, this is not what we want to happen in Cloud Tasks because Cloud Tasks
74+
// will also delete the task upon a 200 OK status, which means a task is deleted twice.
9075
}
9176

9277
public function hasError(): bool

src/CloudTasksQueue.php

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
use Google\Protobuf\Timestamp;
1717
use Illuminate\Contracts\Queue\Queue as QueueContract;
1818
use Illuminate\Queue\Queue as LaravelQueue;
19+
use Illuminate\Support\Str;
1920
use Stackkit\LaravelGoogleCloudTasksQueue\Events\TaskCreated;
2021

2122
use function Safe\json_decode;
2223
use function Safe\json_encode;
23-
use function Safe\preg_replace;
2424

2525
class CloudTasksQueue extends LaravelQueue implements QueueContract
2626
{
@@ -136,7 +136,7 @@ protected function pushToCloudTasks($queue, $payload, $delay, mixed $job)
136136

137137
$payload = (array) json_decode($payload, true);
138138

139-
$task = tap(new Task())->setName($this->taskName($queue, $payload));
139+
$task = tap(new Task())->setName($this->taskName($queue, $payload['displayName']));
140140

141141
$payload = $this->enrichPayloadWithInternalData(
142142
payload: $payload,
@@ -167,29 +167,19 @@ protected function pushToCloudTasks($queue, $payload, $delay, mixed $job)
167167
return $payload['uuid'];
168168
}
169169

170-
private function taskName(string $queueName, array $payload): string
170+
private function taskName(string $queueName, string $displayName): string
171171
{
172-
$displayName = $this->sanitizeTaskName($payload['displayName']);
173-
174172
return CloudTasksClient::taskName(
175173
$this->config['project'],
176174
$this->config['location'],
177175
$queueName,
178-
$displayName.'-'.bin2hex(random_bytes(8)),
176+
str($displayName)
177+
->afterLast('\\')
178+
->prepend((string) Str::ulid(), '-')
179+
->toString(),
179180
);
180181
}
181182

182-
private function sanitizeTaskName(string $taskName): string
183-
{
184-
// Remove all characters that are not -, letters, numbers, or whitespace
185-
$sanitizedName = preg_replace('![^-\pL\pN\s]+!u', '-', $taskName);
186-
187-
// Replace all separator characters and whitespace by a -
188-
$sanitizedName = preg_replace('![-\s]+!u', '-', $sanitizedName);
189-
190-
return trim($sanitizedName, '-');
191-
}
192-
193183
private function enrichPayloadWithInternalData(
194184
array $payload,
195185
string $queueName,
@@ -256,13 +246,11 @@ public function delete(CloudTasksJob $job): void
256246

257247
public function release(CloudTasksJob $job, int $delay = 0): void
258248
{
259-
$job->delete();
260-
261-
$payload = $job->getRawBody();
262-
263-
$options = ['delay' => $delay, 'job' => $job];
264-
265-
$this->pushRaw($payload, $job->getQueue(), $options);
249+
$this->pushRaw(
250+
payload: $job->getRawBody(),
251+
queue: $job->getQueue(),
252+
options: ['delay' => $delay, 'job' => $job],
253+
);
266254
}
267255

268256
/** @param string|object $job */

src/IncomingTask.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public static function fromJson(string $payload): self
2626
}
2727
}
2828

29-
public function isEmpty(): bool
29+
public function isInvalid(): bool
3030
{
3131
return $this->task === [];
3232
}

src/TaskHandler.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function handle(?string $task = null): void
2121
{
2222
$task = IncomingTask::fromJson($task ?: request()->getContent());
2323

24-
if ($task->isEmpty()) {
24+
if ($task->isInvalid()) {
2525
abort(422, 'Invalid task payload');
2626
}
2727

@@ -33,7 +33,12 @@ public function handle(?string $task = null): void
3333

3434
$this->config = is_array($config) ? $config : [];
3535

36-
$this->run($task);
36+
// We want to catch any errors so we have more fine-grained control over
37+
// how tasks are retried. Cloud Tasks will retry the task if a 5xx status
38+
// is returned. Because we manually manage retries by releaseing jobs,
39+
// we never want to return a 5xx status as that will result in duplicate
40+
// job attempts.
41+
rescue(fn () => $this->run($task), report: false);
3742
}
3843

3944
private function run(IncomingTask $task): void

tests/QueueTest.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Illuminate\Support\Facades\DB;
1515
use Illuminate\Support\Facades\Event;
1616
use Illuminate\Support\Facades\Queue;
17+
use Illuminate\Support\Str;
1718
use Override;
1819
use PHPUnit\Framework\Attributes\Test;
1920
use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksApi;
@@ -466,20 +467,18 @@ public function test_ignoring_jobs_with_deleted_models()
466467
}
467468

468469
#[Test]
469-
public function it_adds_a_task_name_based_on_the_display_name()
470+
public function it_adds_a_pre_defined_task_name()
470471
{
471472
// Arrange
472473
CloudTasksApi::fake();
474+
Str::createUlidsUsingSequence(['01HSR4V9QE2F4T0K8RBAYQ88KE']);
473475

474476
// Act
475477
$this->dispatch((new SimpleJob()));
476478

477479
// Assert
478480
CloudTasksApi::assertTaskCreated(function (Task $task): bool {
479-
return str_starts_with(
480-
$task->getName(),
481-
'projects/my-test-project/locations/europe-west6/queues/barbequeue/tasks/Tests-Support-SimpleJob'
482-
);
481+
return $task->getName() === 'projects/my-test-project/locations/europe-west6/queues/barbequeue/tasks/01HSR4V9QE2F4T0K8RBAYQ88KE-SimpleJob';
483482
});
484483
}
485484

tests/Support/FailingJob.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class FailingJob extends BaseJob
1212

1313
public function handle()
1414
{
15+
event(new JobOutput('FailingJob:running'));
1516
throw new Error('simulating a failing job');
1617
}
1718

tests/TaskHandlerTest.php

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,33 +78,31 @@ public function after_max_attempts_it_will_log_to_failed_table()
7878
}
7979

8080
#[Test]
81-
public function after_max_attempts_it_will_delete_the_task()
81+
public function after_max_attempts_it_will_no_longer_execute_the_task()
8282
{
8383
// Arrange
84+
Event::fake([JobOutput::class]);
8485
$job = $this->dispatch(new FailingJob());
8586

8687
// Act & Assert
8788
$releasedJob = $job->runAndGetReleasedJob();
88-
CloudTasksApi::assertDeletedTaskCount(1);
89-
CloudTasksApi::assertTaskDeleted($job->task->getName());
89+
Event::assertDispatched(JobOutput::class, 1);
9090
$this->assertDatabaseCount('failed_jobs', 0);
9191

9292
$releasedJob = $releasedJob->runAndGetReleasedJob();
93-
CloudTasksApi::assertDeletedTaskCount(2);
94-
CloudTasksApi::assertTaskDeleted($job->task->getName());
93+
Event::assertDispatched(JobOutput::class, 2);
9594
$this->assertDatabaseCount('failed_jobs', 0);
9695

9796
$releasedJob->run();
98-
CloudTasksApi::assertDeletedTaskCount(3);
99-
CloudTasksApi::assertTaskDeleted($job->task->getName());
97+
Event::assertDispatched(JobOutput::class, 4);
10098
$this->assertDatabaseCount('failed_jobs', 1);
10199
}
102100

103101
#[Test]
104102
#[TestWith([['now' => '2020-01-01 00:00:00', 'try_at' => '2020-01-01 00:00:00', 'should_fail' => false]])]
105103
#[TestWith([['now' => '2020-01-01 00:00:00', 'try_at' => '2020-01-01 00:04:59', 'should_fail' => false]])]
106104
#[TestWith([['now' => '2020-01-01 00:00:00', 'try_at' => '2020-01-01 00:05:00', 'should_fail' => true]])]
107-
public function after_max_retry_until_it_will_log_to_failed_table_and_delete_the_task(array $args)
105+
public function after_max_retry_until_it_will_log_to_failed_table(array $args)
108106
{
109107
// Arrange
110108
$this->travelTo($args['now']);
@@ -115,8 +113,6 @@ public function after_max_retry_until_it_will_log_to_failed_table_and_delete_the
115113
$releasedJob = $job->runAndGetReleasedJob();
116114

117115
// Assert
118-
CloudTasksApi::assertDeletedTaskCount(1);
119-
CloudTasksApi::assertTaskDeleted($job->task->getName());
120116
$this->assertDatabaseCount('failed_jobs', 0);
121117

122118
// Act
@@ -130,6 +126,9 @@ public function after_max_retry_until_it_will_log_to_failed_table_and_delete_the
130126
#[Test]
131127
public function test_unlimited_max_attempts()
132128
{
129+
// Assert
130+
Event::fake(JobOutput::class);
131+
133132
// Act
134133
$job = $this->dispatch(new FailingJobWithUnlimitedTries());
135134

@@ -138,8 +137,7 @@ public function test_unlimited_max_attempts()
138137
$job = $job->runAndGetReleasedJob();
139138
}
140139

141-
// -1 because the last job is not run.
142-
CloudTasksApi::assertDeletedTaskCount(51);
140+
Event::assertDispatched(JobOutput::class, 51);
143141
}
144142

145143
#[Test]
@@ -204,9 +202,7 @@ public function failing_jobs_are_released()
204202

205203
$job->run();
206204

207-
CloudTasksApi::assertDeletedTaskCount(1);
208205
CloudTasksApi::assertCreatedTaskCount(2);
209-
CloudTasksApi::assertTaskDeleted($job->task->getName());
210206
Event::assertDispatched(JobReleasedAfterException::class, function ($event) {
211207
return $event->job->attempts() === 1;
212208
});

0 commit comments

Comments
 (0)