Odaeus
Gen_tcp accept socket across processes gives `:einval`
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
Marked As Solved
al2o3cr
There’s the concept of a “controlling process” in gen_tcp that makes sending socket references between processes a little tricky.
BUT
The issue you’re encountering is simpler: a common cause of sockname returning EINVAL is if the socket has already shut down.
Here’s my read of how things happen in your code:
- main process calls
:gen_tcp.listen - first spawned process calls
:gen_tcp.acceptand blocks - second spawned process calls
:gen_tcp.connectand blocks - first spawned process wakes up, prints out
sockname, and sendsconnto the main process - Then there’s a race between the three processes:
- the second spawned process is now connected,
gen_tcp.connectunblocks and the SECOND PROCESS EXITS. One end of the TCP connection starts shutting down - the FIRST PROCESS EXITS, taking the other end of the TCP connection down
- the main process tries to call
socknameonconn, which is shut down or shutting down
- the second spawned process is now connected,
A 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 to spawn, which forces the processes to stay alive longer. Doing that in your example results in the code working.
Also Liked
bortzmeyer
Good question
I “know” it mostly by analogy with Unix processes, where sockets are just indexes to a local-to-the-process table.
Last Post!
Odaeus
For completeness, it looks like one way to get this to work on the accepter side is to use :gen_tcp.controlling_process/2 before send/2 with the value of the destination process.
Popular in Questions
Other popular 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
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #hex
- #security









