Support queue workers running on separate servers #277

Merged
pxlrbt merged 1 commit from fix/multi-server-queue into main 2026-08-15 11:21:26 +00:00
pxlrbt commented 2026-08-15 11:15:56 +00:00 (Migrated from github.com)

Refs #268.

Queued exports assumed the worker and the web server share a filesystem and a cache store. Three things had to change.

1. Notifications no longer need a shared cache

ExportFinishedEvent fires on the worker and the notification was parked in cache() (the default store) for the web server to drain in Filament::serving(). On the file driver each machine has its own cache, so the web side never saw it — the reported symptom.

Database notifications don't need that hop: they go to the database, which both sides read anyway. So when the panel has them enabled, the worker now sends the notification directly and skips the cache entirely.

That needs to know which panel the export came from, so ExportFinishedEvent gained an optional $panelId (appended, so existing listeners keep working). Everyone else falls back to the cache handoff exactly as before — flash/persistent notifications genuinely need the user's next request, so there's no way around a shared store for those.

2. The export disk is now configurable

register() did config()->set('filesystems.disks.filament-excel', [...local...]) unconditionally, so defining your own disk in config/filesystems.php had no effect — pointing exports at S3 was impossible. It now only fills in the default when the key is absent.

3. Downloads work on non-local disks

routes/web.php used Storage::disk(...)->path(), which only exists on local drivers, so even a configurable disk wouldn't have helped. Now streams via $disk->download() and deletes in app()->terminating(), preserving the delete-after-download behaviour.

Plus a README section on both requirements.

Verified

Ran the notification routing against a real container with an array cache store: the no-panel and unknown-panel cases fall back to the cache with the right key and payload, repeat exports append rather than overwrite, and a guest export is dropped instead of caching under a null key. Also checked the disk default is applied when absent and a user-defined S3 disk survives untouched.

The database-notification path itself needs a booted panel, so it's verified by reading rather than by running — worth a manual smoke test before release.

One note: I originally guarded that path with class_exists(Filament::class), which is wrong — the facade class exists whenever the package is installed, but Filament::getPanel() needs the filament container binding. The check caught it; it now guards on app()->bound('filament').

Conflicts

Touches the same lines in ExcelExport::export() and FilamentExcelServiceProvider as #275, so whichever lands second will need a small rebase. The app()->bound('filament') guard here is also the more accurate version of the class_exists checks in #275 — worth aligning them when merging.

Refs #268. Queued exports assumed the worker and the web server share a filesystem *and* a cache store. Three things had to change. ### 1. Notifications no longer need a shared cache `ExportFinishedEvent` fires on the worker and the notification was parked in `cache()` (the default store) for the web server to drain in `Filament::serving()`. On the `file` driver each machine has its own cache, so the web side never saw it — the reported symptom. Database notifications don't need that hop: they go to the database, which both sides read anyway. So when the panel has them enabled, the worker now sends the notification directly and skips the cache entirely. That needs to know which panel the export came from, so `ExportFinishedEvent` gained an optional `$panelId` (appended, so existing listeners keep working). Everyone else falls back to the cache handoff exactly as before — flash/persistent notifications genuinely need the user's next request, so there's no way around a shared store for those. ### 2. The export disk is now configurable `register()` did `config()->set('filesystems.disks.filament-excel', [...local...])` unconditionally, so defining your own disk in `config/filesystems.php` had no effect — pointing exports at S3 was impossible. It now only fills in the default when the key is absent. ### 3. Downloads work on non-local disks `routes/web.php` used `Storage::disk(...)->path()`, which only exists on local drivers, so even a configurable disk wouldn't have helped. Now streams via `$disk->download()` and deletes in `app()->terminating()`, preserving the delete-after-download behaviour. Plus a README section on both requirements. ### Verified Ran the notification routing against a real container with an array cache store: the no-panel and unknown-panel cases fall back to the cache with the right key and payload, repeat exports append rather than overwrite, and a guest export is dropped instead of caching under a null key. Also checked the disk default is applied when absent and a user-defined S3 disk survives untouched. The database-notification path itself needs a booted panel, so it's verified by reading rather than by running — worth a manual smoke test before release. One note: I originally guarded that path with `class_exists(Filament::class)`, which is wrong — the facade class exists whenever the package is installed, but `Filament::getPanel()` needs the `filament` container binding. The check caught it; it now guards on `app()->bound('filament')`. ### Conflicts Touches the same lines in `ExcelExport::export()` and `FilamentExcelServiceProvider` as #275, so whichever lands second will need a small rebase. The `app()->bound('filament')` guard here is also the more accurate version of the `class_exists` checks in #275 — worth aligning them when merging.
Sign in to join this conversation.
No description provided.