fix(previews): stop the preview DNS poll from re-queuing file sync jobs

The preview Domains component parsed the compose file on every render,
also on the 2-second DNS poll. Each parse queues a ServerFilesFromServerJob,
so a new file sync ran each time a worker started the previous one.

- Parse the compose services once on mount and on save, and keep them in
  a locked property.

Refs #11062
This commit is contained in:
Andras Bacsai
2026-10-24 00:43:59 +02:00
parent e0b6ac1121
commit 14cdafdbe6
2 changed files with 40 additions and 4 deletions
@@ -28,6 +28,15 @@ class PreviewDomains extends Component
public ?string $newDomainService = null;
/**
* Parsed once on mount and on save. Each parse queues a file sync job, so the
* 2-second DNS poll must not parse the compose file on every render.
*
* @var array<int, string>
*/
#[Locked]
public array $composeServices = [];
public array $editingDomainParts = ['scheme' => 'https', 'host' => '', 'port' => '', 'path' => ''];
public ?int $editingIndex = null;
@@ -45,7 +54,8 @@ class PreviewDomains extends Component
$this->authorize('view', $this->preview->application);
$this->refreshDomains();
if ($this->preview->application->build_pack === 'dockercompose') {
$this->newDomainService = $this->composeServices()[0] ?? null;
$this->composeServices = $this->composeServices();
$this->newDomainService = $this->composeServices[0] ?? null;
}
}
@@ -53,7 +63,6 @@ class PreviewDomains extends Component
{
return view('livewire.project.application.preview-domains', [
'isCompose' => $this->preview->application->build_pack === 'dockercompose',
'composeServices' => $this->composeServices(),
]);
}
@@ -61,7 +70,7 @@ class PreviewDomains extends Component
{
$this->authorize('update', $this->preview->application);
if ($this->preview->application->build_pack === 'dockercompose'
&& ($this->newDomainService === null || ! in_array($this->newDomainService, $this->composeServices(), true))) {
&& ($this->newDomainService === null || ! in_array($this->newDomainService, $this->composeServices, true))) {
$this->addError('newDomainService', 'Select a valid Compose service.');
return;
@@ -96,7 +105,7 @@ class PreviewDomains extends Component
$domain = $this->domainRows[$index]['url'] ?? DomainPortOverrides::withoutPort($domain);
$this->newDomainParts = DomainUrlParts::empty();
$this->newDomainService = $this->preview->application->build_pack === 'dockercompose'
? ($this->composeServices()[0] ?? null)
? ($this->composeServices[0] ?? null)
: null;
$this->forceUseUnknownPort = false;
$this->dispatch('close-preview-domain-add', previewId: $this->preview->id);
@@ -455,6 +464,7 @@ class PreviewDomains extends Component
if ($this->preview->application->build_pack === 'dockercompose') {
try {
$composeServices = $this->composeServices(failOnError: true);
$this->composeServices = $composeServices;
} catch (\Throwable) {
$this->refreshDomains();
$this->dispatch('error', 'Compose configuration could not be parsed. Preview domains were not changed.');
@@ -1,6 +1,7 @@
<?php
use App\Jobs\CheckDomainDnsJob;
use App\Jobs\ServerFilesFromServerJob;
use App\Livewire\Project\Application\PreviewDomains;
use App\Models\Application;
use App\Models\ApplicationPreview;
@@ -11,7 +12,9 @@ use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
use Illuminate\Bus\UniqueLock;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Str;
@@ -1053,3 +1056,26 @@ it('preserves readonly compose preview redirects when saving an address', functi
'api' => ['domain' => '', 'redirect' => 'www'],
]);
});
it('does not queue file sync jobs when polling dns checks for compose previews', function () {
Queue::fake();
$this->application->update([
'build_pack' => 'dockercompose',
'docker_compose_raw' => "services:\n web:\n image: nginx:alpine\n volumes:\n - web-data:/data\n api:\n image: nginx:alpine\n volumes:\n - api-data:/data\nvolumes:\n web-data:\n api-data:\n",
]);
$preview = createPreviewForPortTests($this->application->fresh(), 101, [
'docker_compose_domains' => json_encode(['web' => ['domain' => 'https://compose-preview-poll.invalid']]),
]);
$component = Livewire::test(PreviewDomains::class, ['preview' => $preview]);
$queuedOnMount = Queue::pushed(ServerFilesFromServerJob::class);
// A worker that starts the queued job releases its unique lock, so the next parse queues a new one.
Cache::lock(UniqueLock::getKey($queuedOnMount->first()))->forceRelease();
$component->call('pollDnsChecks')->call('pollDnsChecks')->call('pollDnsChecks');
expect($queuedOnMount)->toHaveCount(1)
->and(Queue::pushed(ServerFilesFromServerJob::class))->toHaveCount(1)
->and($component->get('composeServices'))->toBe(['web', 'api']);
});