Repository navigation
Fix Phoenix.ChannelTest sockets violating Phoenix.Socket.t - #6870
Merged
SteffenDE merged 2 commits intoOct 7, 2026
Merged
Conversation
Both fields are nil until a channel is joined: Socket.user_connect builds the socket that Channel.Server.join/4 receives with neither set. The spec on Server.join/4 required a binary topic and a pid, so any caller passing an unjoined socket broke the contract.
Phoenix.ChannelTest stored its test supervisor in the transport field
as {Phoenix.ChannelTest, sup}, but Phoenix.Socket.t types transport as
an atom. Dialyzer then reported every test join as never returning.
The transport is now Phoenix.ChannelTest and the supervisor is kept in
socket.private, which channel join preserves, so sockets returned from
join/4 can still be used to join again.
Closes phoenixframework#5509.
Member
|
Thanks! |
SteffenDE
pushed a commit
that referenced
this pull request
Oct 9, 2026
* Type topic and channel_pid as nilable in Phoenix.Socket.t
Both fields are nil until a channel is joined: Socket.user_connect
builds the socket that Channel.Server.join/4 receives with neither set.
The spec on Server.join/4 required a binary topic and a pid, so any
caller passing an unjoined socket broke the contract.
* Keep the ChannelTest supervisor in socket.private
Phoenix.ChannelTest stored its test supervisor in the transport field
as {Phoenix.ChannelTest, sup}, but Phoenix.Socket.t types transport as
an atom. Dialyzer then reported every test join as never returning.
The transport is now Phoenix.ChannelTest and the supervisor is kept in
socket.private, which channel join preserves, so sockets returned from
join/4 can still be used to join again.
Closes #5509.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #5509.
#5509 was closed without a fix. On it, @chrismccord wrote: "I'd happily review a PR that fixes this, but I'm not a dialyzer user myself so it's not something I'll be able to prioritize."
Problem
Phoenix.Channel.Server.join/4is specced as taking aPhoenix.Socket.t(). Sockets built byPhoenix.ChannelTestdid not satisfy that type in two ways:socket/3andconnect/2settransport: {Phoenix.ChannelTest, sup}, whilePhoenix.Socket.ttypestransportasatom. The test supervisor was carried in the tuple so thatjoin/4could start channels under it.Phoenix.Socket.ttypestopicasString.t()andchannel_pidaspid, but both fields areniluntil a channel is joined. A socket built bysocket/3has both set tonil. So does a socket built byPhoenix.Socketat connect time, so production code passesServer.join/4sockets that do not match its spec either.Dialyzer concludes that the join can never succeed, so the documented test pattern is reported as having no return. A minimal project that depends on Phoenix by path and calls
socket/3 |> subscribe_and_join/3in one function andconnect/2followed bysubscribe_and_join/3in another reports onmain:With this branch:
The repro code:
Phoenix's CI does not run Dialyzer, so this repro is the evidence for the fix. I checked that each change on its own leaves the
socket/3case failing: with only the ChannelTest change, Dialyzer still rejects theniltopic and channel pid, and with only the type change, it still rejects the tuple transport.Changes
Two commits:
topicandchannel_pidas nilable inPhoenix.Socket.t. The type now matches what the struct holds before a join.transportstaysatom.socket.private.transportis nowPhoenix.ChannelTest, and the supervisor is stored under:phoenix_test_supervisorinsocket.private, the field documented for library use.connect/2adds it after the handler connects, becausePhoenix.Socketbuilds a fresh socket that does not copyprivate.Channel.Server.join/4keeps the socket'sprivateand the channel process merges it with the channel's own, so the socket returned fromjoin/4can still be used to join again, including from another process. The supervisor is still resolved when the socket is built, so the:test_processoption behaves as before.I kept the supervisor in
privateinstead of wideningtransportto accept a tuple. A tuple there would only be needed by test sockets, and every consumer that readstransportas an identifier would have to handle it.Side effect
For ChannelTest sockets, the
[:phoenix, :socket_connected]telemetry metadata and the connect log line now reportTransport: Phoenix.ChannelTestinstead of{Phoenix.ChannelTest, #PID<...>}.Tests
In
test/phoenix/test/channel_test.exs:join/3test now assertssocket.transport == Phoenix.ChannelTestinstead of matching the tuple.join/3test asserts that the joined socket can join again.connect/2test assertssocket.transport == Phoenix.ChannelTest.The existing tests that join from another process and re-join from a joined socket pass unchanged.
mix test --warnings-as-errorspasses locally.I also checked phoenix_live_view: it only calls the public
connect/2,subscribe_and_join/3andpush/3functions and never reads the transport tuple.lib/phoenix/test/channel_test.exandtest/phoenix/test/channel_test.exsalready failmix format --check-formattedonmain. This PR does not add any new formatting differences, and I left the existing ones alone to keep the diff focused.The CHANGELOG has a new "Bug fixes" section in the v1.9 entry.