fix(queue): run service and database start commands on the deployment queue

On cloud, StartService and DeployServiceApplication dispatched their
CoolifyTask on the high queue, so service starts competed with all other
high jobs. remote_process() and CoolifyTask now accept a queue (default
high); start paths pass deployment_queue(). The queued RestartDatabase
action also runs on deployment_queue() instead of the default queue.
This commit is contained in:
Andras Bacsai
2026-10-03 20:50:49 +02:00
parent c0d81d4c9c
commit f1f7272494
9 changed files with 53 additions and 4 deletions
+6
View File
@@ -15,11 +15,17 @@ use App\Models\StandaloneSqlite;
use App\Support\DatabaseOperationReservation;
use App\Support\ResourceStartActivity;
use Lorisleiva\Actions\Concerns\AsAction;
use Lorisleiva\Actions\Decorators\JobDecorator;
class RestartDatabase
{
use AsAction;
public function configureJob(JobDecorator $job): void
{
$job->onQueue(deployment_queue());
}
/**
* @param string|null $reservation The token from StartDatabase::reserveOperation(). The request
* that queued this action holds the reservation; this action
@@ -64,6 +64,6 @@ class DeployServiceApplication
$commands->push("docker network connect --alias {$networkAlias} {$network} {$containerName} >/dev/null 2>&1 || true");
}
return remote_process($commands->toArray(), $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return remote_process($commands->toArray(), $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged', queue: deployment_queue());
}
}
+1 -1
View File
@@ -64,7 +64,7 @@ class StartService
}
$commands = array_merge($commands, $this->logDrainNetworkConnectCommands($service));
return remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged', queue: deployment_queue());
}
/**
+3 -1
View File
@@ -38,6 +38,7 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
* Create a new job instance.
*
* @param int|null $timeout Job timeout in seconds; null keeps the default above.
* @param string $queue Queue to run on; start actions pass deployment_queue().
*/
public function __construct(
public Activity $activity,
@@ -45,12 +46,13 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
public $call_event_on_finish,
public $call_event_data,
?int $timeout = null,
string $queue = 'high',
) {
if ($timeout !== null) {
$this->timeout = $timeout;
}
$this->onQueue('high');
$this->onQueue($queue);
}
/**
+1 -1
View File
@@ -14,6 +14,6 @@ trait ExecutesDatabaseStartCommands
return app(DatabaseStartCommandExecutor::class)->execute($commands, $database, $activity);
}
return remote_process($commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
return remote_process($commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged', queue: deployment_queue());
}
}
+2
View File
@@ -29,6 +29,7 @@ function remote_process(
$callEventData = null,
array $properties = [],
?int $timeout = null,
string $queue = 'high',
): Activity {
$type = $type ?? ActivityTypes::INLINE->value;
$command = $command instanceof Collection ? $command->toArray() : $command;
@@ -74,6 +75,7 @@ function remote_process(
call_event_on_finish: $callEventOnFinish,
call_event_data: $callEventData,
timeout: $timeout,
queue: $queue,
));
$activity->refresh();
@@ -4,6 +4,7 @@ use App\Actions\Service\DeployServiceApplication;
use App\Actions\Service\StartService;
use App\Actions\Shared\EnsureContentFilesOnServer;
use App\Jobs\ApplicationDeploymentJob;
use App\Jobs\CoolifyTask;
use App\Jobs\ServerStorageSaveJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
@@ -579,3 +580,22 @@ test('the server file sync still marks a storage without content as a directory'
expect($volume->fresh()->is_directory)->toBeTruthy();
});
test('starting a service runs its commands on the deployment queue', function (bool $selfHosted, string $queue) {
config(['constants.coolify.self_hosted' => $selfHosted]);
$service = Service::factory()->create([
'environment_id' => $this->environment->id,
'server_id' => $this->server->id,
'destination_id' => $this->destination->id,
'destination_type' => $this->destination->getMorphClass(),
'docker_compose_raw' => "services:\n app:\n image: nginx:alpine\n",
]);
fakeContentFilesServer([]);
StartService::run($service);
Bus::assertDispatched(CoolifyTask::class, fn (CoolifyTask $job) => $job->queue === $queue);
})->with([
'cloud' => [false, 'deployments'],
'self-hosted' => [true, 'high'],
]);
@@ -85,3 +85,19 @@ it('removes the command when the task fails permanently', function () {
expect($stored->getExtraProperty('command'))->toBeNull()
->and($stored->getExtraProperty('status'))->toBe(ProcessStatus::ERROR->value);
});
it('queues a remote process on the high queue by default', function () {
Queue::fake();
remote_process(['echo ok'], $this->server);
Queue::assertPushedOn('high', CoolifyTask::class);
});
it('queues a remote process on the requested queue', function () {
Queue::fake();
remote_process(['echo ok'], $this->server, queue: 'deployments');
Queue::assertPushedOn('deployments', CoolifyTask::class);
});
+3
View File
@@ -1,5 +1,6 @@
<?php
use App\Actions\Database\RestartDatabase;
use App\Actions\Database\StartDatabase;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Service\StartService;
@@ -42,6 +43,7 @@ describe('start action job routing', function () {
expect($actionClass::makeJob()->queue)->toBe('deployments');
})->with([
StartDatabase::class,
RestartDatabase::class,
StartDatabaseProxy::class,
StartService::class,
]);
@@ -52,6 +54,7 @@ describe('start action job routing', function () {
expect($actionClass::makeJob()->queue)->toBe('high');
})->with([
StartDatabase::class,
RestartDatabase::class,
StartDatabaseProxy::class,
StartService::class,
]);