Browser A subscribes to a channel via a turbo_stream_from tag
Browser A changes some state in JS via an HTTP patch and receives the updated state
Browser B receives the state update via broadcast_render_to over action cable and updates the view
Browser A… also redundantly receives the same updated state over Action Cable
I’ve implemented broadcast here only for the case that a single user is juggling two tabs or devices (all of it is scoped to a user), so in practice, 99.9% of Action Cable traffic will be exclusively for Browser A above, with no Browser B/C/D/etc connected
In this scenario, is there a clear way or an established pattern for opting the current client out of that broadcast_render_to? I’m worried not just about the waste, but for the potential of a race condition (user clicks two things quickly and the Action Cable update overwrites one)
Since X-Turbo-Request-Id header is being send with every Turbo request already and Turbo.session.recentRequests` is publicly accessible, you could probably reuse the mechanism.
Thanks a lot for the reply @radanskoric! (And for the deep dive article – that helped me earlier this year)
You’re right in that I am using a custom stream action to update a few (large JSON) data attributes that is broadcast in an after_commit hook on a particular model.
If I’m reading you right, I could attach the Turbo UUID from the JS patch() Fetch request and then filter out the stream from the client later – but since all the stream does is overwrite a data attribute, they’ll be equivalent, so it’s a no-op. Rather, what I’m trying to do is to prevent the redundant broadcast back to the particular client that was responsible for the PATCH that led to the model’s being updated at all. The total payload can get to be pretty large and it’s wasteful to send it over the wire unnecessarily back to the browser that triggered the change
Ahh, you want to prevent the broadcast itself. You were mentioning a race condition so I thought you want to avoid a stale update. I misunderstood.
I have no idea how you’d do that but I’m expecting it would be very hard. I don’t think ActionCable supports any kind of conditional broadcasting. Subscribed and authorised clients get everything broadcast on the channel. Adding logic to that would complicate the whole stack. I wouldn’t be surprised if you need to monkey patch a lot of things in ActionCable to make this work. If someone knows different, please share!
But, if the payload is so large that it’s worth it to avoid double sending, may I suggest flipping the logic? Don’t return anything in the initial response and rely on everyone, including the submitting client, receiving the broadcast?
As I wrote the last Reply I thought of another option: instead of broadcasting the payload, broadcast just a notification to fetch the fresh data. Then you’re brodcasting a tiny mesage and the sending client can ignore it, while the others fetch fresh data.
Yeah, that’s a good idea. I just implemented the XHR/Fetch anyway so that clients will start polling while the cable is disconnected, so I can just extend that here and only push down the FYI
For anyone who finds this via Google later, that means my implementation will look like:
Browser A takes an action that updates a thing via a PATCH request, sending both the update AND the action cable subscriber ID, which I’m hacking at to get
Server updates the thing (which I guess may as well add a updated_by_subscriber column), and then in an after_commit runs the broadcast_render_to, which will only render a stream with the signal clients should fetch + that subscriber ID