mtrudel/bandit

LiveView PubSub Subscription Issue

efleming opened this issue · 3 comments

I have a LiveView page I am working with, the page has a subscription to a PubSub topic to receive updates. I have a handle_info callback defined for the subscription. When using cowboy, this all works fine, when using Bandit (bandit 1.0.0-pre.16) I receive an error:

[error] GenServer #PID<0.6749.0> terminating
** (FunctionClauseError) no function clause matching in Bandit.HTTP1.Handler.handle_info/2
    (bandit 1.0.0-pre.16) project/deps/thousand_island/lib/thousand_island/handler.ex:5: Bandit.HTTP1.Handler.handle_info({Project.Accounts, [:user, :updated], #Project.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 9, org_id: 3, org: %Project.Orgs.Org{__meta__: #Ecto.Schema.Metadata<:loaded, "orgs">, id: 3, parent_path: "1.2", parent: #Ecto.Association.NotLoaded<association :parent is not loaded>, name: "My Org", path: "1.2.3", inserted_at: ~N[2023-10-06 20:35:08], updated_at: ~N[2023-10-11 15:30:49]}, first_name: "FirstName", last_name: "LastName", email: "firstlast@email.com", phone_number: "5555551111", confirmed_at: nil, inserted_at: ~N[2023-10-12 15:34:42], updated_at: ~N[2023-10-12 16:23:20], ...>}, {%ThousandIsland.Socket{socket: #Port<0.29>, transport_module: ThousandIsland.Transports.TCP, read_timeout: 60000, span: %ThousandIsland.Telemetry{span_name: :connection, telemetry_span_context: #Reference<0.3702468754.2544107526.246215>, start_time: -576459807178663489, start_metadata: %{telemetry_span_context: #Reference<0.3702468754.2544107526.246215>, remote_address: {127, 0, 0, 1}, remote_port: 60848, parent_telemetry_span_context: #Reference<0.3702468754.2544107521.249876>}}}, %{opts: %{http_1: [], http_2: [], websocket: []}, plug: {Phoenix.Endpoint.SyncCodeReloadPlug, {ProjectWeb.Endpoint, []}}, handler_module: Bandit.HTTP1.Handler, http_1_enabled: true, http_2_enabled: true, websocket_enabled: true, requests_processed: 9}})
    (stdlib 5.0.2) gen_server.erl:1077: :gen_server.try_handle_info/3
    (stdlib 5.0.2) gen_server.erl:1165: :gen_server.handle_msg/6
    (stdlib 5.0.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
Last message: {Project.Accounts, [:user, :updated], #Project.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 9, org_id: 3, org: %Project.Orgs.Org{__meta__: #Ecto.Schema.Metadata<:loaded, "orgs">, id: 3, parent_path: "1.2", parent: #Ecto.Association.NotLoaded<association :parent is not loaded>, name: "My Org", path: "1.2.3", inserted_at: ~N[2023-10-06 20:35:08], updated_at: ~N[2023-10-11 15:30:49]}, first_name: "FirstName", last_name: "LastName", email: "firstlast@email.com", phone_number: "5555551111", confirmed_at: nil, inserted_at: ~N[2023-10-12 15:34:42], updated_at: ~N[2023-10-12 16:23:20], ...>}
State: {%ThousandIsland.Socket{socket: #Port<0.29>, transport_module: ThousandIsland.Transports.TCP, read_timeout: 60000, span: %ThousandIsland.Telemetry{span_name: :connection, telemetry_span_context: #Reference<0.3702468754.2544107526.246215>, start_time: -576459807178663489, start_metadata: %{telemetry_span_context: #Reference<0.3702468754.2544107526.246215>, remote_address: {127, 0, 0, 1}, remote_port: 60848, parent_telemetry_span_context: #Reference<0.3702468754.2544107521.249876>}}}, %{opts: %{http_1: [], http_2: [], websocket: []}, plug: {Phoenix.Endpoint.SyncCodeReloadPlug, {ProjectWeb.Endpoint, []}}, handler_module: Bandit.HTTP1.Handler, http_1_enabled: true, http_2_enabled: true, websocket_enabled: true, requests_processed: 9}}

Here is a an excerpt from the LiveView:

defmodule Project.UserLive.Index do
  @moduledoc false
  use ProjectWeb, :live_view

  alias Project.Accounts
  alias Project.Accounts.User

  @impl true
  def mount(params, _session, socket) do
    Accounts.subscribe()

    {:ok, socket}
  end

  @impl true
  def handle_info({Project.Accounts, [:user | _], _}, socket) do
    {:noreply, socket}
  end
end

Here is an excerpt of where the events get fired (Project.Accounts):

defmodule Project.Accounts do
  @topic inspect(__MODULE__)

  def subscribe do
    Phoenix.PubSub.subscribe(Project.PubSub, @topic)
  end

  def delete_user(%User{} = user) do
    user
    |> Repo.delete()
    |> broadcast_change([:user, :deleted])
  end

  defp broadcast_change({:ok, result}, event) do
    Phoenix.PubSub.broadcast(Project.PubSub, @topic, {__MODULE__, event, result})

    {:ok, result}
  end
end

I believe this is a dupe of #84, #92, #141 and #218. #141 has the best description of what's going on, but the issue is that your mount code needs to discriminate on the value of connected? since LiveView calls this function at two places in the connection lifecycle.

@mtrudel Thanks for the info, I apologizer for opening this issue.

No problem! Happy to help!