Odaeus

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

Showing Posts 1 to 6

bortzmeyer

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

Odaeus OP

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

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.

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.

Odaeus

Odaeus OP

Ah ha, great explanation of what’s happening, thank you! :bowing_man:

Odaeus

Odaeus OP

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.

— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews