Odaeus

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

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.accept and blocks
  • second spawned process calls :gen_tcp.connect and blocks
  • first spawned process wakes up, prints out sockname, and sends conn to the main process
  • Then there’s a race between the three processes:
    • the second spawned process is now connected, gen_tcp.connect unblocks 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 sockname on conn, which is shut down or shutting down

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

bortzmeyer

Good question :slight_smile: I “know” it mostly by analogy with Unix processes, where sockets are just indexes to a local-to-the-process table.

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

We're in Beta

About us Mission Statement