Passing PID from Task.Supervisor.start_child to function

I’m new to Elixir and have been working through the getting started guide. In the Mix and OTP, DocTests, Patterns and with section, it talks about rewriting the KVServer.Command.run/1 function to be run(command, pid) to facilitate unit testing.

To achieve this, the KVServer.serve/1 function would need to be re-written to pass this argument to the newly implemented run/2 function:

defp serve(socket, pid) do
  msg =
    with {:ok, data} <- read_line(socket),
          {:ok, command} <- KVServer.Command.parse(data),
          do: KVServer.Command.run(command, pid)

  write_line(socket, msg)
  serve(socket)
end

However, in the loop_acceptor/1 method, where we call the serve function, we’d need to obtain the PID from the call to Task.Supervisor.start_child and pass it as an argument to the function given as an argument to the Task.Supervisor.start_child function call:

defp loop_acceptor(socket) do
  {:ok, client} = :gen_tcp.accept(socket)
  {:ok, pid} = Task.Supervisor.start_child(KVServer.TaskSupervisor, fn -> serve(client, pid) end)
  :ok = :gen_tcp.controlling_process(client, pid)
  loop_acceptor(socket)
end

I’m confused as to how to get this PID and pass it as an argument so that it can be passed to run/2. Could anyone help shed some light on this?

Maybe by using self() in Task.Supervisor.start_child's closure? That is, if I understood your problem correctly.

  {:ok, pid} = Task.Supervisor.start_child(KVServer.TaskSupervisor, fn ->
    serve(client, self())
  end)

You can also do it in serve.

Thanks. I’ve updated the serve function and the run functions:

defp serve(socket, pid) do
  msg =
    with {:ok, data} <- read_line(socket),
          {:ok, command} <- KVServer.Command.parse(data),
          do: KVServer.Command.run(command, self())

  write_line(socket, msg)
  serve(socket)
end
def run(command, pid)

def run({:create, bucket}, pid) do
  KV.Registry.create(pid, bucket)
  {:ok, "OK\r\n"}
end

def run({:get, bucket, key}, pid) do
  lookup(bucket, fn pid ->
    value = KV.Bucket.get(pid, key)
    {:ok, "#{value}\r\nOK\r\n"}
  end, pid)
end

...

defp lookup(bucket, callback, pid) do
  case KV.Registry.lookup(pid, bucket) do
    {:ok, pid} -> callback.(pid)
    :error -> {:error, :not_found}
  end
end

But get the following error:

  1) test server interaction (KVServerTest)
     test/kv_server_test.exs:17
     ** (MatchError) no match of right hand side value: {:error, :closed}
     code: assert send_and_recv(socket, "GET shopping eggs\r\n") == "NOT FOUND\r\n"
     stacktrace:
       test/kv_server_test.exs:36: KVServerTest.send_and_recv/2
       test/kv_server_test.exs:19: (test)

     The following output was logged:
     
     21:36:41.435 [info]  Application kv exited: :stopped
     
     21:36:41.451 [error] Task #PID<0.253.0> started from #PID<0.141.0> terminating
     ** (ArgumentError) argument error
         (stdlib) :ets.lookup(#PID<0.253.0>, "shopping")
         (kv) lib/kv/registry.ex:20: KV.Registry.lookup/2
         (kv_server) lib/kv_server/command.ex:77: KVServer.Command.lookup/3
         (kv_server) lib/kv_server.ex:30: KVServer.serve/1
         (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
         (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
     Function: #Function<0.66257808/0 in KVServer.loop_acceptor/1>
         Args: []

I suspect I’ve either misunderstood what is being suggested by the guide or misunderstood some aspect of the example being constructed. Why is the application exiting and why am I getting an argument error?