socketselixirgen-tcp

Accepted socket from task failing in other process


I'm trying to test some elixir code and came across some behavior of gen_tcp I don't understand. When I gen_tcp.accept a socket I may "access" it in the task I created it in, but not a different one. I figured this was a "controlling_process" issue but even when I added that any attempt to use :inet.getstat results in an invalid argument error. I also cannot do a receive on the socket, Elixir claims it's closed but for this question getstat was easier. See below:

defmodule ElixirQuestion do
  def serve_one_client(socket, pid) 
  do  
    {:ok, server_socket} = :gen_tcp.accept(socket)
    :ok = :gen_tcp.controlling_process(server_socket, pid)
    IO.inspect(:inet.getstat(server_socket))
    server_socket 
  end 
end

{:ok, socket} = :gen_tcp.listen(0, [:binary, 
                                      {:packet, :raw}, 
                                      {:active, false}])
{:ok, port_number} = :inet.port(socket)
server_task = Task.async(fn -> ElixirQuestion.serve_one_client(socket, self()) end)
{:ok, _client_socket} = :gen_tcp.connect('localhost', port_number, [active: false]) 
server_socket = Task.await(server_task)

IO.inspect(:inet.getstat(server_socket))

Expected output

{:ok,
 [recv_oct: 0, recv_cnt: 0, recv_max: 0, recv_avg: 0, recv_dvi: 0, send_oct: 0,
  send_cnt: 0, send_max: 0, send_avg: 0, send_pend: 0]}
{:ok,
 [recv_oct: 0, recv_cnt: 0, recv_max: 0, recv_avg: 0, recv_dvi: 0, send_oct: 0,
  send_cnt: 0, send_max: 0, send_avg: 0, send_pend: 0]}

Received output

{:ok,
 [recv_oct: 0, recv_cnt: 0, recv_max: 0, recv_avg: 0, recv_dvi: 0, send_oct: 0,
  send_cnt: 0, send_max: 0, send_avg: 0, send_pend: 0]}
{:error, :einval}

Elixir version

Erlang/OTP 19 [erts-8.3.5.3] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Elixir 1.4.5

Solution

  • self() inside Task.async will return the PID of the Task process. Since you want the value of the parent process here, you need to save this value outside the fn passed to Task.async and then use that inside.

    Changing:

    server_task = Task.async(fn -> ElixirQuestion.serve_one_client(socket, self()) end)
    

    to:

    me = self()
    server_task = Task.async(fn -> ElixirQuestion.serve_one_client(socket, me) end)
    

    gives me the output you expect:

    {:ok,
     [recv_oct: 0, recv_cnt: 0, recv_max: 0, recv_avg: 0, recv_dvi: 0, send_oct: 0,
      send_cnt: 0, send_max: 0, send_avg: 0, send_pend: 0]}
    {:ok,
     [recv_oct: 0, recv_cnt: 0, recv_max: 0, recv_avg: 0, recv_dvi: 0, send_oct: 0,
      send_cnt: 0, send_max: 0, send_avg: 0, send_pend: 0]}