Skip to content

#95 job deletion #96

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

Merged
merged 2 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
composer.lock
.idea/
.phpunit.result.cache
.phpunit.cache
2 changes: 1 addition & 1 deletion src/CloudTasksApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @method static Task createTask(string $queueName, Task $task)
* @method static void deleteTask(string $taskName)
* @method static Task getTask(string $taskName)
* @method static int|null getRetryUntilTimestamp(string $taskName)
* @method static int|null getRetryUntilTimestamp(Task $task)
*/
class CloudTasksApi extends Facade
{
Expand Down
4 changes: 1 addition & 3 deletions src/CloudTasksApiConcrete.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,8 @@ public function getTask(string $taskName): Task
return $this->client->getTask($taskName);
}

public function getRetryUntilTimestamp(string $taskName): ?int
public function getRetryUntilTimestamp(Task $task): ?int
{
$task = $this->getTask($taskName);

$attempt = $task->getFirstAttempt();

if (!$attempt instanceof Attempt) {
Expand Down
2 changes: 1 addition & 1 deletion src/CloudTasksApiContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public function getRetryConfig(string $queueName): RetryConfig;
public function createTask(string $queueName, Task $task): Task;
public function deleteTask(string $taskName): void;
public function getTask(string $taskName): Task;
public function getRetryUntilTimestamp(string $taskName): ?int;
public function getRetryUntilTimestamp(Task $task): ?int;
}
2 changes: 1 addition & 1 deletion src/CloudTasksApiFake.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function getTask(string $taskName): Task
}


public function getRetryUntilTimestamp(string $taskName): ?int
public function getRetryUntilTimestamp(Task $task): ?int
{
return null;
}
Expand Down
20 changes: 20 additions & 0 deletions src/CloudTasksJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,31 @@ public function timeoutAt(): ?int

public function delete(): void
{
// Laravel automatically calls delete() after a job is processed successfully. However, this is
// not what we want to happen in Cloud Tasks because Cloud Tasks will also delete the task upon
// a 200 OK status, which means a task is deleted twice, possibly resulting in errors. So if the
// task was processed successfully (no errors or failures) then we will not delete the task
// manually and will let Cloud Tasks do it.
$successful =
// If the task has failed, we should be able to delete it permanently
$this->hasFailed() === false
// If the task has errored, it should be released, which in process deletes the errored task
&& $this->hasError() === false;

if ($successful) {
return;
}

parent::delete();

$this->cloudTasksQueue->delete($this);
}

public function hasError(): bool
{
return data_get($this->job, 'internal.errored') === true;
}

public function release($delay = 0)
{
parent::release();
Expand Down
34 changes: 20 additions & 14 deletions src/TaskHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Stackkit\LaravelGoogleCloudTasksQueue;

use Google\ApiCore\ApiException;
use Google\Cloud\Tasks\V2\CloudTasksClient;
use Google\Cloud\Tasks\V2\RetryConfig;
use Illuminate\Bus\Queueable;
Expand Down Expand Up @@ -122,6 +123,24 @@ private function handleTask(array $task): void

$this->loadQueueRetryConfig($job);

$taskName = request()->header('X-Cloudtasks-Taskname');
$fullTaskName = $this->client->taskName(
$this->config['project'],
$this->config['location'],
$job->getQueue() ?: $this->config['queue'],
$taskName,
);

try {
$apiTask = CloudTasksApi::getTask($fullTaskName);
} catch (ApiException $e) {
if (in_array($e->getStatus(), ['NOT_FOUND', 'PRECONDITION_FAILED'])) {
abort(404);
}

throw $e;
}

// If the task has a [X-CloudTasks-TaskRetryCount] header higher than 0, then
// we know the job was created using an earlier version of the package. This
// job does not have the attempts tracked internally yet.
Expand All @@ -138,20 +157,7 @@ private function handleTask(array $task): void
// max retry duration has been set. If that duration
// has passed, it should stop trying altogether.
if ($job->attempts() > 0) {
$taskName = request()->header('X-Cloudtasks-Taskname');

if (!is_string($taskName)) {
throw new UnexpectedValueException('Expected task name to be a string.');
}

$fullTaskName = $this->client->taskName(
$this->config['project'],
$this->config['location'],
$job->getQueue() ?: $this->config['queue'],
$taskName,
);

$job->setRetryUntil(CloudTasksApi::getRetryUntilTimestamp($fullTaskName));
$job->setRetryUntil(CloudTasksApi::getRetryUntilTimestamp($apiTask));
}

$job->setAttempts($job->attempts() + 1);
Expand Down
2 changes: 1 addition & 1 deletion tests/CloudTasksApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public function test_get_retry_until_timestamp()
// The queue max retry duration is 5 seconds. The max retry until timestamp is calculated from the
// first attempt, so we expect it to be [timestamp first attempt] + 5 seconds.
$expected = $createdTask->getFirstAttempt()->getDispatchTime()->getSeconds() + 5;
$actual = CloudTasksApi::getRetryUntilTimestamp($createdTask->getName());
$actual = CloudTasksApi::getRetryUntilTimestamp($createdTask);
$this->assertSame($expected, $actual);
}
}
6 changes: 3 additions & 3 deletions tests/QueueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ public function jobs_can_be_released()

// Assert
Event::assertNotDispatched($this->getJobReleasedAfterExceptionEvent());
CloudTasksApi::assertDeletedTaskCount(1);
CloudTasksApi::assertDeletedTaskCount(0); // it returned 200 OK so we dont delete it, but Google does
$releasedJob = null;
Event::assertDispatched(JobReleased::class, function (JobReleased $event) use (&$releasedJob) {
$releasedJob = $event->job;
Expand All @@ -257,7 +257,7 @@ public function jobs_can_be_released()

$this->runFromPayload($releasedJob->getRawBody());

CloudTasksApi::assertDeletedTaskCount(2);
CloudTasksApi::assertDeletedTaskCount(0);
CloudTasksApi::assertTaskCreated(function (Task $task) {
$body = $task->getHttpRequest()->getBody();
$decoded = json_decode($body, true);
Expand Down Expand Up @@ -476,6 +476,6 @@ public function test_ignoring_jobs_with_deleted_models()

// Act
Log::assertLogged('UserJob:John');
CloudTasksApi::assertTaskDeleted($job->task->getName());
CloudTasksApi::assertTaskNotDeleted($job->task->getName());
}
}