:gen_tcp closes connection when "Task.await"ing the accept/1 call

Consider following snippets:

# client
{:ok, conn} = :gen_tcp.connect({127, 0, 0, 1}, 1024, [active: false, reuseaddr: true])
:gen_tcp.recv(conn, 0) |> IO.inspect()
# server
{:ok, socket} = :gen_tcp.listen(1024, [active: false, reuseaddr: true])
{:ok, conn} = Task.async(fn -> :gen_tcp.accept(socket) end) |> Task.await()
:gen_tcp.send(conn, "hello")

The clients recv will fail with {:error, :closed}, but why? What is Task.await doing to the sockets PID that makes this not work?
Is there a way to asynchronously wait for a single connection on the server side?

1 Like

The process that accepts the connection becomes the owner of it. When the process dies, which in your example happens immediately after you accept, it will also close the connections it owns.

To solve this problem you can do the sending in the same process as you accept or you can hand over ownership of the connection socket using :gen_tcp.controlling_process/2.

6 Likes

Thank you, that makes sense.

This is what the server looks like now:

main_proc = self()

{:ok, conn} = Task.async(fn ->
  sock_opts = [active: false, reuseaddr: true]
  with  {:ok, socket} <- :gen_tcp.listen(1024, sock_opts),
        {:ok, conn} <- :gen_tcp.accept(socket),
        :ok <- :gen_tcp.controlling_process(conn, main_proc) do
          {:ok, conn}
        else
          error -> error
        end
end)
|> Task.await()

:gen_tcp.send(conn, "hello")

Please let me know if this is a terrible anti-pattern :slight_smile:

1 Like

There is an anti-pattern in the sense that the async task is not necessary since the main process is not doing anything while the task is running so you could just as well skip using a task. The only time I would do something like that is if the process is allocating a lot of memory you want quickly garbage collected when it exits.

But if your code is only a snippet from a larger code base where the main process is doing work in the mean time or you spawn multiple tasks then there is nothing wrong with it :slight_smile:

1 Like

You can also accomplish this ‘for free’ with Thousand Island (shameless plug!), plus get all the supervision / scalability / telemetry for free

6 Likes

Thanks for the hint, I will be using ThousandIsland from now on!