Skip to content

Commit 8c68844

Browse files
authored
Use the PubSub.Sender behavior for fastlaning (#6869)
Detailed description in #6869.
1 parent bd18018 commit 8c68844

9 files changed

Lines changed: 164 additions & 44 deletions

File tree

‎installer/templates/phx_umbrella/apps/app_name/mix.exs.eex‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ defmodule <%= @app_module %>.MixProject do
3737
defp deps do
3838
[
3939
{:dns_cluster, "~> 0.2.0"},
40-
{:phoenix_pubsub, "~> 2.1"}<%= if @ecto do %>,
40+
{:phoenix_pubsub, "~> 2.4"}<%= if @ecto do %>,
4141
{:ecto_sql, "~> 3.13"},
4242
{:<%= @adapter_app %>, ">= 0.0.0"},
4343
{:jason, "~> 1.2"}<% end %><%= if @mailer do %>,

‎installer/test/phx_new_umbrella_test.exs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
122122

123123
assert_file(app_path(@app, "mix.exs"), fn file ->
124124
assert file =~ "mod: {PhxUmb.Application, []}"
125-
assert file =~ "{:phoenix_pubsub, \"~> 2.1\"}"
125+
assert file =~ "{:phoenix_pubsub, \"~> 2.4\"}"
126126
end)
127127

128128
assert_file(app_path(@app, "test/test_helper.exs"))

‎lib/phoenix/channel/server.ex‎

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ defmodule Phoenix.Channel.Server do
22
@moduledoc false
33
use GenServer, restart: :temporary
44

5+
@behaviour Phoenix.PubSub.Sender
6+
57
require Logger
68

79
alias Phoenix.PubSub
@@ -89,52 +91,44 @@ defmodule Phoenix.Channel.Server do
8991
## Channel API
9092

9193
@doc """
92-
Hook invoked by Phoenix.PubSub dispatch.
94+
Hook invoked by Phoenix.PubSub to deliver a message to a channel subscription.
95+
96+
Broadcasts not intercepted by the channel are encoded once per
97+
serializer and sent directly to the transport process.
9398
"""
94-
def dispatch(subscribers, from, %Broadcast{event: event} = msg) do
95-
Enum.reduce(subscribers, %{}, fn
96-
{pid, _}, cache when pid == from ->
97-
cache
98-
99-
{pid, {:fastlane, fastlane_pid, serializer, event_intercepts}}, cache ->
100-
if event in event_intercepts do
101-
send(pid, msg)
99+
def send(pid, {transport_pid, serializer, intercepts}, %Broadcast{event: event} = msg, cache) do
100+
if event in intercepts do
101+
send(pid, msg)
102+
cache
103+
else
104+
case cache do
105+
%{^serializer => encoded_msg} ->
106+
send(transport_pid, encoded_msg)
102107
cache
103-
else
104-
case cache do
105-
%{^serializer => encoded_msg} ->
106-
send(fastlane_pid, encoded_msg)
107-
cache
108-
109-
%{} ->
110-
encoded_msg = serializer.fastlane!(msg)
111-
send(fastlane_pid, encoded_msg)
112-
Map.put(cache, serializer, encoded_msg)
113-
end
114-
end
115-
116-
{pid, _}, cache ->
117-
send(pid, msg)
118-
cache
119-
end)
120-
121-
:ok
122-
end
123108

124-
def dispatch(entries, :none, message) do
125-
for {pid, _} <- entries do
126-
send(pid, message)
109+
_ ->
110+
encoded_msg = serializer.fastlane!(msg)
111+
send(transport_pid, encoded_msg)
112+
Map.put(cache || %{}, serializer, encoded_msg)
113+
end
127114
end
115+
end
128116

129-
:ok
117+
def send(pid, _meta, msg, cache) do
118+
send(pid, msg)
119+
cache
130120
end
131121

122+
# TODO: Remove in Phoenix 1.10
123+
#
124+
# Nodes running Phoenix < 1.8.16 expect cluster-wide channel broadcasts to use
125+
# this module as dispatcher, otherwise their channels receive broadcasts
126+
# they cannot handle. Therefore we still broadcast with it, and it is also
127+
# invoked for broadcasts coming from older nodes. Subscriptions carry their
128+
# own sender, so we delegate to the default dispatching.
129+
@doc false
132130
def dispatch(entries, from, message) do
133-
for {pid, _} <- entries, pid != from do
134-
send(pid, message)
135-
end
136-
137-
:ok
131+
PubSub.dispatch(entries, from, message)
138132
end
139133

140134
@doc """
@@ -440,8 +434,8 @@ defmodule Phoenix.Channel.Server do
440434
end
441435

442436
Process.monitor(transport_pid)
443-
fastlane = {:fastlane, transport_pid, serializer, channel.__intercepts__()}
444-
PubSub.subscribe(pubsub_server, topic, metadata: fastlane)
437+
sender = {__MODULE__, {transport_pid, serializer, channel.__intercepts__()}}
438+
PubSub.subscribe(pubsub_server, topic, sender: sender)
445439

446440
{:noreply, %{socket | joined: true}}
447441
end

‎mix.exs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ defmodule Phoenix.MixProject do
8282
{:plug, "~> 1.14"},
8383
{:plug_crypto, "~> 2.2"},
8484
{:telemetry, "~> 0.4 or ~> 1.0"},
85-
{:phoenix_pubsub, "~> 2.1"},
85+
{:phoenix_pubsub, "~> 2.4"},
8686
{:phoenix_template, "~> 1.0"},
8787
{:websock_adapter, "~> 0.5"},
8888

‎mix.lock‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"mint_web_socket": {:hex, :mint_web_socket, "1.0.6", "5ffcf350df5b90f2d7a04adf877165228804993714592512374218d4679e325a", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "0c360e9012413f1c115a63532601eb5d63731aab7010949178769760686c1698"},
2626
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
2727
"phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
28-
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"},
28+
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.4.0", "be29b070ec9298df8c3c90a2ae0712d4c9e37c214f6a6ca81341bd19f1bde00b", [:mix], [], "hexpm", "80776f727460ed104c95ecddcacc5098d3cd31f40e5f05741e1219abfb20f4a8"},
2929
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
3030
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
3131
"plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"},

‎test/phoenix/channel_test.exs‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ defmodule Phoenix.Channel.ChannelTest do
44
@pubsub __MODULE__.PubSub
55
import Phoenix.Channel
66

7+
defmodule ClusterAdapter do
8+
# Sends what would be broadcast to other nodes to the test process
9+
@behaviour Phoenix.PubSub.Adapter
10+
11+
def node_name(_adapter_name), do: node()
12+
13+
def child_spec(opts) do
14+
test_pid = Keyword.fetch!(opts, :test_pid)
15+
agent_opts = [name: opts[:adapter_name]]
16+
%{id: __MODULE__, start: {Agent, :start_link, [fn -> test_pid end, agent_opts]}}
17+
end
18+
19+
def broadcast(adapter_name, topic, message, dispatcher) do
20+
send(Agent.get(adapter_name, & &1), {:cluster_broadcast, topic, message, dispatcher})
21+
:ok
22+
end
23+
24+
def direct_broadcast(adapter_name, _node_name, topic, message, dispatcher) do
25+
broadcast(adapter_name, topic, message, dispatcher)
26+
end
27+
end
28+
729
setup_all do
830
start_supervised! {Phoenix.PubSub, name: @pubsub, pool_size: 1}
931
:ok
@@ -95,6 +117,39 @@ defmodule Phoenix.Channel.ChannelTest do
95117
}
96118
end
97119

120+
# TODO: Remove in Phoenix 1.10
121+
test "broadcasts to other nodes with Phoenix.Channel.Server as dispatcher" do
122+
pubsub = __MODULE__.ClusterPubSub
123+
start_supervised!({Phoenix.PubSub, name: pubsub, adapter: ClusterAdapter, test_pid: self()})
124+
125+
socket = %Phoenix.Socket{
126+
pubsub_server: pubsub,
127+
topic: "sometopic",
128+
channel_pid: spawn_link(fn -> :ok end),
129+
joined: true
130+
}
131+
132+
broadcast(socket, "event1", %{key: :val})
133+
134+
assert_receive {:cluster_broadcast, "sometopic", %Phoenix.Socket.Broadcast{event: "event1"},
135+
Phoenix.Channel.Server}
136+
137+
broadcast!(socket, "event2", %{key: :val})
138+
139+
assert_receive {:cluster_broadcast, "sometopic", %Phoenix.Socket.Broadcast{event: "event2"},
140+
Phoenix.Channel.Server}
141+
142+
broadcast_from(socket, "event3", %{key: :val})
143+
144+
assert_receive {:cluster_broadcast, "sometopic", %Phoenix.Socket.Broadcast{event: "event3"},
145+
Phoenix.Channel.Server}
146+
147+
broadcast_from!(socket, "event4", %{key: :val})
148+
149+
assert_receive {:cluster_broadcast, "sometopic", %Phoenix.Socket.Broadcast{event: "event4"},
150+
Phoenix.Channel.Server}
151+
end
152+
98153
test "pushing to transport" do
99154
socket = %Phoenix.Socket{
100155
serializer: Phoenix.ChannelTest.NoopSerializer,

‎test/phoenix/endpoint/endpoint_test.exs‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ defmodule Phoenix.Endpoint.EndpointTest do
4242
use Phoenix.Endpoint, otp_app: :phoenix
4343
end
4444

45+
defmodule TaggingSender do
46+
@behaviour Phoenix.PubSub.Sender
47+
48+
def send(pid, tag, message, state) do
49+
Kernel.send(pid, {tag, message})
50+
state
51+
end
52+
end
53+
4554
setup_all do
4655
ExUnit.CaptureLog.capture_log(fn -> start_supervised!(Endpoint) end)
4756
start_supervised!({Phoenix.PubSub, name: :endpoint_pub})
@@ -307,6 +316,22 @@ defmodule Phoenix.Endpoint.EndpointTest do
307316
}
308317
end
309318

319+
test "pubsub broadcasts are delivered through the subscription sender" do
320+
Endpoint.subscribe("sendertopic", sender: {TaggingSender, :tagged})
321+
some = spawn(fn -> :ok end)
322+
323+
Endpoint.broadcast("sendertopic", "event1", %{key: :val})
324+
assert_receive {:tagged, %Phoenix.Socket.Broadcast{event: "event1", topic: "sendertopic"}}
325+
326+
Endpoint.broadcast_from!(some, "sendertopic", "event2", %{key: :val})
327+
assert_receive {:tagged, %Phoenix.Socket.Broadcast{event: "event2", topic: "sendertopic"}}
328+
329+
Endpoint.local_broadcast("sendertopic", "event3", %{key: :val})
330+
assert_receive {:tagged, %Phoenix.Socket.Broadcast{event: "event3", topic: "sendertopic"}}
331+
332+
refute_received %Phoenix.Socket.Broadcast{}
333+
end
334+
310335
test "loads cache manifest from specified application" do
311336
config =
312337
put_in(

‎test/phoenix/presence_test.exs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ defmodule Phoenix.PresenceTest do
5252
end
5353
end
5454

55+
defmodule TaggingSender do
56+
@behaviour Phoenix.PubSub.Sender
57+
58+
def send(pid, tag, message, state) do
59+
Kernel.send(pid, {tag, message})
60+
state
61+
end
62+
end
63+
5564
defmodule CustomDispatcherPresence do
5665
use Phoenix.Presence, otp_app: :phoenix, dispatcher: CustomDispatcher
5766
end
@@ -189,6 +198,21 @@ defmodule Phoenix.PresenceTest do
189198
assert MyPresence.list(topic) == %{}
190199
end
191200

201+
test "handle_diff broadcasts through the subscription sender", %{topic: topic} = config do
202+
pid = spawn(fn -> :timer.sleep(:infinity) end)
203+
Phoenix.PubSub.subscribe(config.pubsub, topic, sender: {TaggingSender, :tagged})
204+
MyPresence.track(pid, topic, "u1", %{name: "u1"})
205+
206+
assert_receive {:tagged,
207+
%Broadcast{
208+
topic: ^topic,
209+
event: "presence_diff",
210+
payload: %{joins: %{"u1" => _}, leaves: %{}}
211+
}}
212+
213+
refute_received %Broadcast{}
214+
end
215+
192216
test "handle_diff with custom dispatcher", %{topic: topic} = config do
193217
pid = spawn(fn -> :timer.sleep(:infinity) end)
194218
Phoenix.PubSub.subscribe(config.pubsub, topic)

‎test/phoenix/test/channel_test.exs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,28 @@ defmodule Phoenix.Test.ChannelTest do
454454
assert_graceful_exit(pid)
455455
end
456456

457+
test "push broadcasts sent directly through pubsub" do
458+
subscribe_and_join!(socket(UserSocket), Channel, "foo:ok")
459+
broadcast = %Broadcast{topic: "foo:ok", event: "default", payload: %{"foo" => "bar"}}
460+
Phoenix.PubSub.broadcast!(Phoenix.Test.ChannelTest.PubSub, "foo:ok", broadcast)
461+
assert_push "default", %{"foo" => "bar"}
462+
end
463+
464+
test "handles broadcasts dispatched by Phoenix.Channel.Server from older nodes" do
465+
Process.flag(:trap_exit, true)
466+
{:ok, _, socket} = subscribe_and_join(socket(UserSocket), Channel, "foo:ok")
467+
pubsub = Phoenix.Test.ChannelTest.PubSub
468+
469+
broadcast = %Broadcast{topic: "foo:ok", event: "default", payload: %{"foo" => "bar"}}
470+
Phoenix.PubSub.local_broadcast(pubsub, "foo:ok", broadcast, Phoenix.Channel.Server)
471+
assert_push "default", %{"foo" => "bar"}
472+
473+
broadcast = %Broadcast{topic: "foo:ok", event: "stop", payload: %{"foo" => "bar"}}
474+
Phoenix.PubSub.local_broadcast(pubsub, "foo:ok", broadcast, Phoenix.Channel.Server)
475+
assert_receive {:terminate, :shutdown}
476+
assert_graceful_exit(socket.channel_pid)
477+
end
478+
457479
## handle_info
458480

459481
test "handles messages and stops" do

0 commit comments

Comments
 (0)