Real-time Phoenix (Pragprog)

@sb8244, Hi Steve, I’m trying to wrap my head around the flow of messages from the client to a channel(Lesson 3), standing in my way is the correlation between the socket, transport and channel pieces.
Please correct me on the below if I’m wrong.

Initial connection setup

  1. Request from client via communication library is handled by the Transport process on server.
  2. Transport process delegates/forward socket and other data to the Socket module
  3. Socket module invokes connect/id function to authenticate and do assignment where applicable
  4. Socket module communicates back to the Transport process

Incoming message (phx_join or a diff msg)

  1. Can I assume this will be routed directly from the Transport process to the Channel module, will the Transport process introspect the Socket module, use the channel macro to forward the request to the appropriate Channel or it’s has this information already via socket struct returned by Socket module in the initial connection setup phase?
  2. If the above is wrong; will the Transport process delegate to the Socket module which will then call then call the Channel module

Thank you.

Delegate is definitely correct here. It’s important to understand this is just a function call in the same process. It’s not crossing a process boundary here.

That makes this not quite true. The “communication back” is simply the result of the delegation. See phoenix/lib/phoenix/socket.ex at 9d7444f8cbfeb91b6d03f6e375f7be3ccd5d2d6c · phoenixframework/phoenix · GitHub for source code there.

It’s way easier than that in this case. The channel macro sets up a bunch of functions that use pattern matching to call the right one. This means there is no lookup, it’s simply functions that exist from compilation.

Connected Channels on the Socket are tracked via a map. So it’s simply a lookup of topic → Channel at that point (phoenix/lib/phoenix/socket.ex at 9d7444f8cbfeb91b6d03f6e375f7be3ccd5d2d6c · phoenixframework/phoenix · GitHub).

1 Like

I think I have the right picture now, your assertion on the “communication back” is correct; the return value of the connect function to the Transport process from the Socket module. Subsequently the Transport process uses pattern matching setup within the Socket module by the channel macro to invoke the right Channel. In effect we have two processes - (Transport and Channel with the Socket module acting as a link via it functions between them.)

@sb8244, I was hoping to get some input from you on the above, hope I’m on the right path and thanks a lot for the wonderful work and also taken time to assist. I’m most grateful.
Thanks Steve.

I’m working through this book and it’s great.

I have a question about acceptance tests and discrepancy between what the book says and what it says in the docs for Ecto SQL Sandbox.

The docs say it is important to put the Sandbox plug at the top of endpoint.ex before any other plugs. However the book says to put it as the final plug definition in that file.

Is there a reason to put it last instead of first?

1 Like

I would suggest following the docs in this case—I clearly missed that. Reading the code, the edge cases would come up if you were using the database in another Plug, since you wouldn’t have a connection. That’s not the case for our project, but may happen in one of yours.

1 Like

Why is iex(1)> HelloSocketsWeb.Endpoint.broadcast("ping", "test", %{data: "test"}) throwing this error message: Disconnected (code: 1011, reason: "") ?
So far, all the examples I’ve run have been working well. Here’s the setup:

##lib/hello_sockets_web/channels/ping_channel.ex
   channel "ping", HelloSocketsWeb.PingChannel
   channel "ping:*", HelloSocketsWeb.PingChannel
   channel "wild:*", HelloSocketsWeb.WildcardChannel
## lib/hello_sockets_web/channels/ping_channel.ex
defmodule HelloSocketsWeb.PingChannel do
	use Phoenix.Channel

	def join(_topic, _payload, socket) do
		{:ok, socket}
	end

	def handle_in("ping", _payload, socket) do
		{:reply, {:ok, %{ping: "pong"}}, socket}
	end
end

Does the discount code still available?

Where is this error code coming from? If it’s coming from the wscat session, that is because you have to send data over it within ~40 seconds or it disconnects.

The only discounts I know of are from the sister forum (not sure if they want me posting here), or if you’ve ever bought a Pragmatic book before (coupon in the back to buy another for 30% off).

Yes, the error is coming from wscat. I tried again while timing it with a stopwatch but it keeps generating the same error in less than 40 secs. I have my terminal split to two: one running $ iex -S mix phx.server and the other running wscat. The terminal running phoenix is all in red when the wscat returns the Disconnected (code: 1011, reason: "") error.
Here is the error message from the phoenix server:

[error] GenServer #PID<0.431.0> terminating
** (Jason.DecodeError) unexpected byte at position 0: 0x48 ('H')
    (jason 1.2.0) lib/jason.ex:78: Jason.decode!/2
    (phoenix 1.4.16) lib/phoenix/socket/serializers/v2_json_serializer.ex:30: Phoenix.Socket.V2.JSONSerializer.decode!/2
    (phoenix 1.4.16) lib/phoenix/socket.ex:564: Phoenix.Socket.__in__/2
    (phoenix 1.4.16) lib/phoenix/endpoint/cowboy2_handler.ex:120: Phoenix.Endpoint.Cowboy2Handler.websocket_handle/2
    (cowboy 2.7.0) /home/uzisky/_phx/hello_sockets/deps/cowboy/src/cowboy_websocket.erl:482: :cowboy_websocket.handler_call/6
    (cowboy 2.7.0) /home/uzisky/_phx/hello_sockets/deps/cowboy/src/cowboy_http.erl:231: :cowboy_http.loop/1
    (stdlib 3.12.1) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:DOWN, #Reference<0.589201659.3935567875.121381>, :process, #PID<0.428.0>, {%Jason.DecodeError{data: "HelloSocketsWeb.Endpoint.broadcast(\"ping\", \"test\", %{data: \"test\"})", position: 0, token: nil}, [{Jason, :decode!, 2, [file: 'lib/jason.ex', line: 78]}, {Phoenix.Socket.V2.JSONSerializer, :decode!, 2, [file: 'lib/phoenix/socket/serializers/v2_json_serializer.ex', line: 30]}, {Phoenix.Socket, :__in__, 2, [file: 'lib/phoenix/socket.ex', line: 564]}, {Phoenix.Endpoint.Cowboy2Handler, :websocket_handle, 2, [file: 'lib/phoenix/endpoint/cowboy2_handler.ex', line: 120]}, {:cowboy_websocket, :handler_call, 6, [file: '/home/uzisky/_phx/hello_sockets/deps/cowboy/src/cowboy_websocket.erl', line: 482]}, {:cowboy_http, :loop, 1, [file: '/home/uzisky/_phx/hello_sockets/deps/cowboy/src/cowboy_http.erl', line: 231]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}]}}
State: %Phoenix.Socket{assigns: %{}, channel: HelloSocketsWeb.PingChannel, channel_pid: #PID<0.431.0>, endpoint: HelloSocketsWeb.Endpoint, handler: HelloSocketsWeb.UserSocket, id: nil, join_ref: "1", joined: true, private: %{log_handle_in: :debug, log_join: :info}, pubsub_server: HelloSockets.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "ping", transport: :websocket, transport_pid: #PID<0.428.0>}
[error] Ranch listener HelloSocketsWeb.Endpoint.HTTP had connection process started with :cowboy_clear:start_link/4 at #PID<0.428.0> exit with reason: {%Jason.DecodeError{data: "HelloSocketsWeb.Endpoint.broadcast(\"ping\", \"test\", %{data: \"test\"})", position: 0, token: nil}, [{Jason, :decode!, 2, [file: 'lib/jason.ex', line: 78]}, {Phoenix.Socket.V2.JSONSerializer, :decode!, 2, [file: 'lib/phoenix/socket/serializers/v2_json_serializer.ex', line: 30]}, {Phoenix.Socket, :__in__, 2, [file: 'lib/phoenix/socket.ex', line: 564]}, {Phoenix.Endpoint.Cowboy2Handler, :websocket_handle, 2, [file: 'lib/phoenix/endpoint/cowboy2_handler.ex', line: 120]}, {:cowboy_websocket, :handler_call, 6, [file: '/home/uzisky/_phx/hello_sockets/deps/cowboy/src/cowboy_websocket.erl', line: 482]}, {:cowboy_http, :loop, 1, [file: '/home/uzisky/_phx/hello_sockets/deps/cowboy/src/cowboy_http.erl', line: 231]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}]}

It looks like you copied the broadcast function into the wscat process rather than the iex terminal:

%Jason.DecodeError{data: "HelloSocketsWeb.Endpoint.broadcast(\"ping\", \"test\", %{data: \"test\"})", position: 0, token: nil}
1 Like

:man_facepalming: That’s correct. Thanks.

1 Like

It’s always the small things! :upside_down_face:

I’ve read about half of it by now. It’s an excellent book.

The topic is introduced gradually in an ordered way. I love how wscat is used from the beginning to let you see clearly what is going on for real. Also inspection in developer tools. That way, when you get to the abstractions, you understand what is happening.

This book does something that I believe is not very common, which is to take selected shortcuts in key places that won’t hinder understanding, but allow the book to go faster to the point. Like bootstrapping the main application with some initial code, seed data, and writing tests that illustrate how you’d do them, but without going all the way to full coverage, since it would not add to your understanding and can save work/overhead.

Congrats @sb8244.

2 Likes

Thanks @sb8244 but sadly I don’t have any pragmatic books yet. If you have any discount code please let me know thank you :grin:

Please see this:

If you’d like to get set-up, just let me know :023:

1 Like

@sb8244, Any code snippet on how to use the replacement for HelloSocketsWeb.Endpoint.broadcast(....) on Phoenix 1.5? According to the changelog for Phoenix.PubSub 2.0:

Phoenix.PubSub 2.0 has been released with a more flexible and powerful fastlane mechanism. We use this opportunity to also move Phoenix.PubSub out of the endpoint and explicitly into your supervision tree. To update, you will need to remove or update the {:phoenix_pubsub, "~> 1.x"} entry in your mix.exs to at least “2.0”.

I’ve not upgraded yet, but just in case.

I put together a PR to demonstrate updating the final project to Phoenix 1.5.0-rc0

It’s a pretty painless upgrade, which I think captures the ethos of Phoenix well. The only thing you have to do is start PubSub yourself. Everything else had deprecation warnings

1 Like

Phoenix 1.5.1 version is already available :slight_smile:

1 Like

Oops. I missed that! I didn’t realize 1.5.0 had left rc0. I’ll go ahead and update now :upside_down_face:

edit: No changes, just a bump there!

1 Like

Quite literally, there’s not a better time to buy Elixir books than this week. Use the discount code Frameworks2020 for 50% off!

Check out the other available books in the newsletter (https://media.pragprog.com/newsletters/2020-05-11.html). You’ll see Programming Phoenix >= 1.4 and Adopting Elixir are there. Those are both excellent books!

3 Likes