Passing PID from Task.Supervisor.start_child to function

supervisor

#1

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?


#2

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.


#3

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?