Summary
TTBooking\TicketAllocator\Concerns\Broadcastable declares:
public string $queue = 'event-sourcing';
Illuminate\Broadcasting\BroadcastManager::queue() reads that property when picking the queue for the BroadcastEvent job, and it does so before any framework-level routing (Laravel 13.21.1):
$queue = match (true) {
method_exists($event, 'broadcastQueue') => $event->broadcastQueue(),
isset($event->broadcastQueue) => $event->broadcastQueue,
isset($event->queue) => $event->queue, // ← the trait wins here
default => null,
};
if (is_null($queue)) { // ← never reached for package events
$queue = $this->getAttributeValue($event, QueueAttribute::class, 'queue')
?? $this->resolveQueueFromQueueRoute($event)
?? null;
}
So an application cannot move package broadcasts to a dedicated queue: both Queue::route(ShouldBroadcast::class, ...) (Laravel 11+) and #[Queue] are short-circuited by the property. The only workarounds are patching the vendor file or overriding every event class.
Why it matters
Publishing to a websocket server is a network call that fails transiently, and its retry policy needs to differ from the projector queue's:
- the projector queue is usually one worker (
balance: simple, processes: 1) with a small tries and no backoff — retries fire back to back within milliseconds;
- a failed publish needs a spaced-out ladder, otherwise every retry hits the same network blip and the event is lost for good.
Numbers from a 5-day window on our install (self-hosted Reverb, transient TLS timeouts on the route between the app host and the websocket host), failed BroadcastEvent jobs by queue:
| queue |
failed jobs |
event-sourcing (package events) |
266 |
broadcasts (app events, retried with backoff) |
31 |
The 266 are TicketMetricsAdjusted (144), TicketClosed (41), TicketCreated (36), TicketBound (28), Operator* (16) — every one a UI update that never reached the operators' screens. The application-level fix (dedicated queue + backoff ladder) had been deployed for two weeks and, unnoticed, applied to none of the package events.
There is a second effect: publishes and projector jobs share one single-process queue, so a stalled publish (up to connect_timeout) blocks the projector jobs queued behind it, and server-side state lags during an episode as well.
Proposal
Add a broadcastQueue() method to the trait, backwards compatible by default:
public function broadcastQueue(): ?string
{
return config('ticket-allocator.broadcast_queue', $this->queue);
}
with 'broadcast_queue' => env('TICKET_ALLOCATOR_BROADCAST_QUEUE') in the package config — unset means today's behaviour. Returning null is useful in itself: BroadcastManager then falls through to #[Queue] / Queue::route() / the default queue, letting the app apply its own global broadcast policy.
A method rather than a public $broadcastQueue property on purpose — broadcastWith() in the same trait builds the payload by reflecting over public properties, so a new public property is the riskier option.
Happy to send a PR if this shape looks right.
Summary
TTBooking\TicketAllocator\Concerns\Broadcastabledeclares:Illuminate\Broadcasting\BroadcastManager::queue()reads that property when picking the queue for theBroadcastEventjob, and it does so before any framework-level routing (Laravel 13.21.1):So an application cannot move package broadcasts to a dedicated queue: both
Queue::route(ShouldBroadcast::class, ...)(Laravel 11+) and#[Queue]are short-circuited by the property. The only workarounds are patching the vendor file or overriding every event class.Why it matters
Publishing to a websocket server is a network call that fails transiently, and its retry policy needs to differ from the projector queue's:
balance: simple,processes: 1) with a smalltriesand nobackoff— retries fire back to back within milliseconds;Numbers from a 5-day window on our install (self-hosted Reverb, transient TLS timeouts on the route between the app host and the websocket host), failed
BroadcastEventjobs by queue:event-sourcing(package events)broadcasts(app events, retried with backoff)The 266 are
TicketMetricsAdjusted(144),TicketClosed(41),TicketCreated(36),TicketBound(28),Operator*(16) — every one a UI update that never reached the operators' screens. The application-level fix (dedicated queue + backoff ladder) had been deployed for two weeks and, unnoticed, applied to none of the package events.There is a second effect: publishes and projector jobs share one single-process queue, so a stalled publish (up to
connect_timeout) blocks the projector jobs queued behind it, and server-side state lags during an episode as well.Proposal
Add a
broadcastQueue()method to the trait, backwards compatible by default:with
'broadcast_queue' => env('TICKET_ALLOCATOR_BROADCAST_QUEUE')in the package config — unset means today's behaviour. Returningnullis useful in itself:BroadcastManagerthen falls through to#[Queue]/Queue::route()/ the default queue, letting the app apply its own global broadcast policy.A method rather than a
public $broadcastQueueproperty on purpose —broadcastWith()in the same trait builds the payload by reflecting over public properties, so a new public property is the riskier option.Happy to send a PR if this shape looks right.