mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
The scheduler ran one dispatcher for database backups, tasks, volume backups, and Docker cleanups after each other. A slow type, for example many tasks due at the same minute, made the whole run longer, and the shared overlap lock then made all types skip the next minute. Run `scheduled:dispatch --type=<type>` every minute for backups, tasks, volume-backups, and docker-cleanups. Each type runs in its own background process with its own scheduler and job overlap locks, and publishes and recovers only the deliveries of its own job type. Running the command without a type still processes all types.
111 lines
3.9 KiB
PHP
111 lines
3.9 KiB
PHP
<?php
|
|
|
|
use App\Models\InstanceSettings;
|
|
use Illuminate\Console\Scheduling\Schedule;
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
InstanceSettings::unguarded(fn () => InstanceSettings::query()->firstOrCreate(['id' => 0]));
|
|
});
|
|
|
|
it('schedules RegenerateSslCertJob with onOneServer to prevent multi-server double dispatch', function () {
|
|
$schedule = app(Schedule::class);
|
|
|
|
$event = collect($schedule->events())->first(
|
|
fn ($e) => str_contains((string) $e->description, 'RegenerateSslCertJob')
|
|
);
|
|
|
|
expect($event)->not->toBeNull();
|
|
expect($event->onOneServer)->toBeTrue();
|
|
});
|
|
|
|
it('schedules ssh mux cleanup locally on every scheduler host', function () {
|
|
$schedule = app(Schedule::class);
|
|
|
|
$event = collect($schedule->events())->first(
|
|
fn ($e) => (string) $e->description === 'cleanup:ssh-mux'
|
|
);
|
|
|
|
expect($event)->not->toBeNull();
|
|
expect($event->onOneServer)->toBeFalse();
|
|
expect($event->getSummaryForDisplay())->toBe('cleanup:ssh-mux');
|
|
});
|
|
|
|
it('schedules every production job with onOneServer', function () {
|
|
$schedule = app(Schedule::class);
|
|
|
|
$jobEvents = collect($schedule->events())->filter(
|
|
fn ($e) => str_contains((string) $e->description, 'App\\Jobs\\')
|
|
);
|
|
|
|
expect($jobEvents)->not->toBeEmpty();
|
|
|
|
$jobEvents->each(function ($event) {
|
|
expect($event->onOneServer)->toBeTrue(
|
|
"Scheduled job [{$event->description}] is missing ->onOneServer()"
|
|
);
|
|
});
|
|
});
|
|
|
|
it('does not schedule Stripe subscription reconciliation automatically', function () {
|
|
$schedule = app(Schedule::class);
|
|
|
|
$event = collect($schedule->events())->first(
|
|
fn ($event) => str_contains((string) $event->description, 'SyncStripeSubscriptions')
|
|
);
|
|
|
|
expect($event)->toBeNull();
|
|
});
|
|
|
|
it('schedules stuck resource cleanup in the background once per day', function () {
|
|
$schedule = app(Schedule::class);
|
|
|
|
$event = collect($schedule->events())->first(
|
|
fn ($event) => str_contains($event->command, 'cleanup:stucked-resources')
|
|
);
|
|
|
|
expect($event)->not->toBeNull()
|
|
->and($event->expression)->toBe('17 3 * * *')
|
|
->and($event->onOneServer)->toBeTrue()
|
|
->and($event->withoutOverlapping)->toBeTrue()
|
|
->and($event->runInBackground)->toBeTrue();
|
|
});
|
|
|
|
it('runs one scheduled job dispatcher for each schedule type from the scheduler instead of a queue', function () {
|
|
$events = collect(app(Schedule::class)->events());
|
|
|
|
$dispatchers = $events->filter(fn ($event) => str_contains((string) $event->command, 'scheduled:dispatch'));
|
|
|
|
expect($dispatchers->map(fn ($event) => str((string) $event->command)->after('--type=')->value())->values()->all())
|
|
->toBe(['backups', 'tasks', 'volume-backups', 'docker-cleanups']);
|
|
$dispatchers->each(function ($event) {
|
|
expect($event->expression)->toBe('* * * * *')
|
|
->and($event->onOneServer)->toBeTrue()
|
|
->and($event->withoutOverlapping)->toBeTrue()
|
|
->and($event->runInBackground)->toBeTrue();
|
|
});
|
|
// Each type has its own overlap lock.
|
|
expect($dispatchers->map->mutexName()->unique())->toHaveCount(4)
|
|
->and($events->contains(fn ($event) => str_contains((string) $event->description, 'ScheduledJobManager')))->toBeFalse();
|
|
});
|
|
|
|
it('schedules GitHub runner reconciliation every minute on Coolify Cloud', function () {
|
|
config()->set('constants.coolify.self_hosted', false);
|
|
|
|
$schedule = new Schedule;
|
|
$kernel = app(Kernel::class);
|
|
(fn () => $this->schedule($schedule))->call($kernel);
|
|
|
|
$event = collect($schedule->events())->first(
|
|
fn ($event) => str_contains((string) $event->description, 'ReconcileGithubRunnersJob')
|
|
);
|
|
|
|
expect(isCloud())->toBeTrue()
|
|
->and($event)->not->toBeNull()
|
|
->and($event->expression)->toBe('* * * * *')
|
|
->and($event->onOneServer)->toBeTrue();
|
|
});
|