Skip to content

Commit 8d6e8b2

Browse files
committed
Add initial draft of job releasing
1 parent d54ba2e commit 8d6e8b2

File tree

7 files changed

+80
-17
lines changed

7 files changed

+80
-17
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## 3.?.0 - ????-??-??
8+
9+
**Added**
10+
11+
- Jobs can now be released back onto the queue.
12+
713
## 3.2.1 - 2022-09-02
814

915
**Fixed**

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,19 @@ Please check the table below on what the values mean and what their value should
7979
</details>
8080
<details>
8181
<summary>
82-
How it works
82+
How it works & Differences
8383
</summary>
8484
<br>
8585
Using Cloud Tasks as a Laravel queue driver is fundamentally different than other Laravel queue drivers, like Redis.
8686

8787
Typically a Laravel queue has a worker that listens to incoming jobs using the `queue:work` / `queue:listen` command.
8888
With Cloud Tasks, this is not the case. Instead, Cloud Tasks will schedule the job for you and make an HTTP request to your application with the job payload. There is no need to run a `queue:work/listen` command.
89+
90+
#### Good to know
91+
92+
- The "Min backoff" and "Max backoff" options in Cloud Tasks are ignored. This is intentional: Laravel has its own backoff feature (which is more powerful than what Cloud Tasks offers) and therefore I have chosen that over the Cloud Tasks one.
93+
- Similarly to the backoff feature, I have also chosen to let the package do job retries the 'Laravel way'. In Cloud Tasks, when a task throws an exception, Cloud Tasks will decide for itself when to retry the task (based on the backoff values). It will also manage its own state and knows how many times a task has been retried. This is different from Laravel. In typical Laravel queues, when a job throws an exception, the job is deleted and released back onto the queue. In order to support Laravel's backoff feature, this package must behave the same way about job retries.
94+
8995
</details>
9096
<details>
9197
<summary>Dashboard (beta)</summary>

src/CloudTasksApiFake.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,9 @@ public function assertTaskCreated(Closure $closure): void
9191

9292
Assert::assertTrue($count > 0, 'Task was not created.');
9393
}
94+
95+
public function assertCreatedTaskCount(int $count): void
96+
{
97+
Assert::assertCount($count, $this->createdTasks);
98+
}
9499
}

src/CloudTasksJob.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,11 @@ public function delete(): void
9595

9696
$this->cloudTasksQueue->delete($this);
9797
}
98+
99+
public function release($delay = 0)
100+
{
101+
parent::release();
102+
103+
$this->cloudTasksQueue->release($this, $delay);
104+
}
98105
}

src/CloudTasksQueue.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ function ($payload, $queue) {
9393
*/
9494
public function pushRaw($payload, $queue = null, array $options = [])
9595
{
96-
return $this->pushToCloudTasks($queue, $payload);
96+
$delay = ! empty($options['delay']) ? $options['delay'] : 0;
97+
98+
$this->pushToCloudTasks($queue, $payload, $delay);
9799
}
98100

99101
/**
@@ -217,6 +219,17 @@ public function delete(CloudTasksJob $job): void
217219
CloudTasksApi::deleteTask($taskName);
218220
}
219221

222+
public function release(CloudTasksJob $job, int $delay = 0): void
223+
{
224+
$job->delete();
225+
226+
$payload = $job->getRawBody();
227+
228+
$options = ['delay' => $delay];
229+
230+
$this->pushRaw($payload, $job->getQueue(), $options);
231+
}
232+
220233
private function createTask(): Task
221234
{
222235
return app(Task::class);

src/TaskHandler.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Bus\Queueable;
88
use Illuminate\Contracts\Encryption\Encrypter;
99
use Illuminate\Queue\Jobs\Job;
10+
use Illuminate\Queue\QueueManager;
1011
use Illuminate\Queue\WorkerOptions;
1112
use Illuminate\Support\Str;
1213
use Illuminate\Validation\ValidationException;
@@ -102,11 +103,14 @@ private function loadQueueConnectionConfiguration(array $task): void
102103
* @var stdClass $command
103104
*/
104105
$command = self::getCommandProperties($task['data']['command']);
105-
$connection = $command['connection'] ?? config('queue.default');
106-
$this->config = array_merge(
107-
(array) config("queue.connections.{$connection}"),
108-
['connection' => $connection]
109-
);
106+
$connection = $command->connection ?? config('queue.default');
107+
$baseConfig = config('queue.connections.' . $connection);
108+
$config = (new CloudTasksConnector())->connect($baseConfig)->config;
109+
110+
// The connection name from the config may not be the actual connection name
111+
$config['connection'] = $connection;
112+
113+
$this->config = $config;
110114
}
111115

112116
private function setQueue(): void

tests/TaskHandlerTest.php

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -268,17 +268,17 @@ public function after_max_attempts_it_will_delete_the_task()
268268

269269
// Act & Assert
270270
$job->run();
271-
CloudTasksApi::assertDeletedTaskCount(0);
272-
CloudTasksApi::assertTaskNotDeleted($job->task->getName());
271+
CloudTasksApi::assertDeletedTaskCount(1);
272+
CloudTasksApi::assertTaskDeleted($job->task->getName());
273273
$this->assertDatabaseCount('failed_jobs', 0);
274274

275275
$job->run();
276-
CloudTasksApi::assertDeletedTaskCount(0);
277-
CloudTasksApi::assertTaskNotDeleted($job->task->getName());
276+
CloudTasksApi::assertDeletedTaskCount(2);
277+
CloudTasksApi::assertTaskDeleted($job->task->getName());
278278
$this->assertDatabaseCount('failed_jobs', 0);
279279

280280
$job->run();
281-
CloudTasksApi::assertDeletedTaskCount(1);
281+
CloudTasksApi::assertDeletedTaskCount(3);
282282
CloudTasksApi::assertTaskDeleted($job->task->getName());
283283
$this->assertDatabaseCount('failed_jobs', 1);
284284
}
@@ -300,16 +300,16 @@ public function after_max_retry_until_it_will_log_to_failed_table_and_delete_the
300300
$job->run();
301301

302302
// Assert
303-
CloudTasksApi::assertDeletedTaskCount(0);
304-
CloudTasksApi::assertTaskNotDeleted($job->task->getName());
303+
CloudTasksApi::assertDeletedTaskCount(1);
304+
CloudTasksApi::assertTaskDeleted($job->task->getName());
305305
$this->assertDatabaseCount('failed_jobs', 0);
306306

307307
// Act
308308
CloudTasksApi::partialMock()->shouldReceive('getRetryUntilTimestamp')->andReturn(1);
309309
$job->run();
310310

311311
// Assert
312-
CloudTasksApi::assertDeletedTaskCount(1);
312+
CloudTasksApi::assertDeletedTaskCount(2);
313313
CloudTasksApi::assertTaskDeleted($job->task->getName());
314314
$this->assertDatabaseCount('failed_jobs', 1);
315315
}
@@ -330,8 +330,8 @@ public function test_unlimited_max_attempts()
330330
$job = $this->dispatch(new FailingJob());
331331
foreach (range(1, 50) as $attempt) {
332332
$job->run();
333-
CloudTasksApi::assertDeletedTaskCount(0);
334-
CloudTasksApi::assertTaskNotDeleted($job->task->getName());
333+
CloudTasksApi::assertDeletedTaskCount($attempt);
334+
CloudTasksApi::assertTaskDeleted($job->task->getName());
335335
$this->assertDatabaseCount('failed_jobs', 0);
336336
}
337337
}
@@ -408,4 +408,26 @@ public function it_can_handle_encrypted_jobs()
408408

409409
Log::assertLogged('EncryptedJob:success');
410410
}
411+
412+
public function failing_jobs_are_released()
413+
{
414+
// Arrange
415+
OpenIdVerificator::fake();
416+
CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn(
417+
(new RetryConfig())->setMaxAttempts(3)
418+
);
419+
420+
// Act
421+
$job = $this->dispatch(new FailingJob());
422+
423+
CloudTasksApi::assertDeletedTaskCount(0);
424+
CloudTasksApi::assertCreatedTaskCount(1);
425+
CloudTasksApi::assertTaskNotDeleted($job->task->getName());
426+
427+
$job->run();
428+
429+
CloudTasksApi::assertDeletedTaskCount(1);
430+
CloudTasksApi::assertCreatedTaskCount(2);
431+
CloudTasksApi::assertTaskDeleted($job->task->getName());
432+
}
411433
}

0 commit comments

Comments
 (0)