Skip to content

New subscribers receiving old messages #65

Open
@tbuehlmann

Description

@tbuehlmann

There seems to be a bug involving the Listener's last_id. It is used for memoizing the Message that was sent last so it knows what to send next. The thing is, while there's no subscriber connected and new Messages are broadcasted, the last_id number doesn't change, so the next connecting subscriber will receive all the "pending" Messages from the past.

Here's an example with a background job that broadcasts a Message every 5 seconds:

00:00 background job broadcasts message #1
00:05 background job broadcasts message #2
00:06 client connects (last_id is initialized to 2) and receives all messages with id > 2 (which is none)
00:10 background job broadcasts message #3 (client receives it, last_id is set to 3)
00:15 background job broadcasts message #4 (client receives it, last_id is set to 4)
00:16 client disconnects (last_id stays at 4, this is important)
00:20 background job broadcasts message #5
00:25 background job broadcasts message #6
00:30 background job broadcasts message #7
00:35 background job broadcasts message #8
00:40 background job broadcasts message #9
00:41 client connects (last_id is still 4) and receives all messages with id > 4 (which is 5 messages from the past)

Here's a test for this behaviour:

test "broadcasting_history" do
  @tx_adapter.broadcast("channel", "1")
  @tx_adapter.broadcast("channel", "2")

  # first client, doesn't see old messages
  subscribe_as_queue("channel") do |queue|
    assert_empty queue

    @tx_adapter.broadcast("channel", "3")
    @tx_adapter.broadcast("channel", "4")

    assert_equal "3", queue.pop
    assert_equal "4", queue.pop
  end

  @tx_adapter.broadcast("channel", "5")
  @tx_adapter.broadcast("channel", "6")
  @tx_adapter.broadcast("channel", "7")
  @tx_adapter.broadcast("channel", "8")
  @tx_adapter.broadcast("channel", "9")

  # second client, sees old messages
  subscribe_as_queue("channel") do |queue|
    assert_empty queue # raises as queue has 5 items
  end
end

I thought about instant-trimming after broadcasting, but that's not really an option as there could be multiple Listeners (like in two different Puma instances) and one Listener deleting a Message before the other Listener could send it is bad.

So we might need a last_id per channel and reset it when the last subscriber for that channel disconnects (or something else entirely I didn't think of). If there was a last_id per channel (like a hash), the SolidCable::Message.broadcastable scope would need to change as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions