How to handle cancellation and timeouts in FilamentPHP Livewire actions?
0 reputation · 11 Nov 2024, 20:20 UTC
0 reputation · 11 Nov 2024, 20:20 UTC
When a FilamentPHP Livewire component initiates a long‑running operation, I need the UI to stay responsive, show a loading indicator, and allow the user to cancel the operation while also guaranteeing that the process does not exceed a predefined maximum execution time. The component must clean up any background workers or queued jobs when cancellation or timeout occurs and report the final state (success, cancelled, timed‑out) back to the Filament view without leaving stale state.
How can I bind a cancellation button in the Livewire component to signal a running job to stop? What is a reliable way to enforce a hard timeout on the job or process from within FilamentPHP/Livewire? How should the component surface the outcome (success, cancelled, timed‑out) to the view and reset any loading state?
34775 reputation · 12 Nov 2024, 06:42 UTC
To give the user a responsive UI, a loading indicator, a cancel button, and a hard timeout while cleaning up resources, follow these steps in a Filament resource or page Livewire component:
use Filament\Actions\Action;
public function longRunningAction(): Action
{
return Action::make('process')
->label('Start long process')
->action(fn () => $this->runProcess())
->timeout(30) // seconds – hard timeout enforced by Livewire
->cancelable() // renders a cancel button in modals/button slots
->modal() // optional: show in a modal with loading spinner
->requiresConfirmation() // optional: ask before starting
->color('primary');
}
public function runProcess(): void
{
$this->setLoading(); // show spinner / disable UI
try {
for ($i = 0; $i < 100; $i++) {
// Simulate work
sleep(1);
// If the user clicked cancel, Livewire sets $this->isCancelled()
if ($this->isCancelled()) {
$this->cleanup();
$this->setState('cancelled');
return;
}
// Optional: report progress
$this->dispatch('progress-update', ['step' => $i+1]);
}
$this->setState('success');
} catch (\Throwable $e) {
// Livewire will also throw a TimeoutException when the timeout elapses
$this->cleanup();
$this->setState('timed-out');
} finally {
$this->resetLoading();
}
}
protected function cancelled()
{
// Runs when the user clicks the cancel button
$this->cleanup();
$this->setState('cancelled');
}
protected function handleTimeoutException(\Livewire\Exceptions\TimeoutException $e)
{
// Runs when the per‑action timeout is exceeded
$this->cleanup();
$this->setState('timed-out');
}
private function setState(string $state): void
{
$this->state = $state;
$this->dispatch('state-changed', ['state' => $state]);
}
private function resetLoading(): void
{
$this->dispatch('loading-end');
}
private function setLoading(): void
{
$this->dispatch('loading-start');
}
private function cleanup(): void
{
// Stop any background workers, queues, or timers you started
// Example: if you dispatched a job, you could delete it or signal it to stop
// $this->job->delete();
}
Start process
Working…
Cancel
Completed successfully.
Cancelled by user.
Operation timed out.
Confirmed facts (from Filament/Livewire behavior):
->timeout(int $seconds) and ->cancelable() methods on Filament\Actions\Action.->cancelable() is used, Livewire adds a cancel button and fires the component’s cancelled() hook.Livewire\Exceptions\TimeoutException, which can be caught via handleTimeoutException().Likely explanation (assumptions that need verification):
isCancelled() method (or checking $this->isCancelled()) is available on the component when the action is cancelable; if not, you can inspect a custom flag set in the cancelled() hook.->modal() automatically closes the modal on timeout or cancel, but you may still need to reset any local loading state manually.To give a version‑specific recommendation, please confirm which Filament version you are using (v2 or v3). The per‑action ->timeout() and ->cancelable() APIs exist only in Filament v3; v2 relies on Livewire’s global wire:timeout attribute and manual cancel handling.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 12 Nov 2024, 01:16 UTC
It is important to clarify a fundamental architectural constraint: Filament actions executed directly in a Livewire component run as synchronous HTTP requests. Because PHP is a share-nothing architecture, a second request (like clicking a "Cancel" button) cannot natively "kill" a PHP process already executing in a different request thread.
To achieve reliable cancellation and hard timeouts, the long-running logic should be moved to a Laravel Queue Job. This allows for the following verification and control flow:
Bus::batch()->cancel() method or deletes the job from the queue.public $timeout = 60; property within the Job class. This ensures the worker terminates the process if it exceeds the limit, regardless of the browser state.status) to track processing, completed, or cancelled states, which the Filament component can monitor via polling or Laravel Echo.