Odaeus
Hi everyone,
I’m implementing something that needs to read a TCP stream for the first time and trying to get my head around gen_tcp. I start a GenServer to handle the connection and want to be able to make other calls to it so I thought spawning the blocking :gen_tcp.accept call would be efficient but it seems the socket doesn’t survive being transferred between processes. Does anyone know why? Or if I’m making an elementary mistake here?
Here’s a minimal script that demonstrates the problem:
# testtcp.exs
{:ok, socket} = :gen_tcp.listen(0, [])
{:ok, port} = :inet.port(socket) |> dbg()
current = self()
# Spawn a process just for accepting connections and send the new connection back to use when it
# happens.
spawn(fn ->
case :gen_tcp.accept(socket) do
{:ok, conn} ->
dbg(:inet.sockname(conn))
send(current, {:new_conn, conn} |> dbg())
end
end)
# Spawn to connect to the socket.
spawn(fn ->
dbg(:gen_tcp.connect('localhost', port, [], :infinity))
end)
receive do
{:new_conn, conn} ->
dbg({:received_conn, conn})
dbg(:inet.sockname(conn))
end
Which outputs:
[tcptest.exs:3: (file)]
:inet.port(socket) #=> {:ok, 40015}
[tcptest.exs:8: (file)]
:inet.sockname(conn) #=> {:ok, {{127, 0, 0, 1}, 40015}}
[tcptest.exs:14: (file)]
:gen_tcp.connect('localhost', port, [], :infinity) #=> {:ok, #Port<0.7>}
[tcptest.exs:9: (file)]
{:new_conn, conn} #=> {:new_conn, #Port<0.8>}
[tcptest.exs:19: (file)]
{:received_conn, conn} #=> {:received_conn, #Port<0.8>}
[tcptest.exs:20: (file)]
:inet.sockname(conn) #=> {:error, :einval}
(there’s no way to add line numbers to code blocks here right?)
You’ll notice the dbg output is slightly out of order, but it shows the change between calling :inet.sockname in the same process that accepted the socket (valid) and in the receive loop ({:error, :einval}).
Thanks for any help!
Andrew
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 6- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
bortzmeyer
Indeed, the data for a socket has a meaning which is local (to a process) and so sending it to another process in a message is useless. If you want a socket to be “sent” to another process, it has to be done when spawning (as you do in the first spawn of your example).
Odaeus
Thanks for the answer! May I ask how you know that? Is it an innate trait of the BEAM or perhaps documented somewhere I didn’t look? Just want to understand these kinds of limitations as I thought everything could be passed between processes. Though I realise that between nodes some things won’t work.
bortzmeyer
Good question
I “know” it mostly by analogy with Unix processes, where sockets are just indexes to a local-to-the-process table.
al2o3cr
There’s the concept of a “controlling process” in
gen_tcpthat makes sending socket references between processes a little tricky.BUT
The issue you’re encountering is simpler: a common cause of
socknamereturningEINVALis if the socket has already shut down.Here’s my read of how things happen in your code:
:gen_tcp.listen:gen_tcp.acceptand blocks:gen_tcp.connectand blockssockname, and sendsconnto the main processgen_tcp.connectunblocks and the SECOND PROCESS EXITS. One end of the TCP connection starts shutting downsocknameonconn, which is shut down or shutting downA quick way to check for conditions like this is to add
Process.sleep(10000)(or your favorite large number) at the end of each function passed tospawn, which forces the processes to stay alive longer. Doing that in your example results in the code working.Odaeus
Ah ha, great explanation of what’s happening, thank you!
Odaeus
For completeness, it looks like one way to get this to work on the accepter side is to use
:gen_tcp.controlling_process/2beforesend/2with the value of the destination process.