3
votes

I have Phoenix application with complex business logic behind HTTP endpoint. This logic includes interaction with database and few external services and once request processing has been started it must not be interrupted till all operations will be done.

But it seems like Cowboy or Ranch kills request handler process (Phoenix controller) if client suddenly closes connection, which leads to partially executed business process. To debug this I have following code in controller action:

Process.flag(:trap_exit, true)

receive do
  msg -> Logger.info("Message: #{inspect msg}")
after
  10_000 -> Logger.info("Timeout")
end

And to simulate connection closing I set timeout: curl --request POST 'http://localhost:4003' --max-time 3. After 3 seconds in IEx console I see that process is about to exit: Message: {:EXIT, #PID<0.4196.0>, :shutdown}.

So I need to make controller complete its job and reply to client if it is still there or do nothing if connection is lost. Which will be the best way to achieve this:

  • trap exits in controller action and ignore exit messages;
  • spawn not linked Task in controller action and wait for its results;
  • somehow configure Cowboy/Ranch so it will not kill handler process, if it is possible (tried exit_on_close with no luck)?
1

1 Answers

2
votes

Handling processes will be killed after the request end, that is their purpose. If you want to process some data in the background, then start additional process. The simplest way to do so would be 2nd method you have proposed, but with slight modification of using Task.Supervisor.

So in your application supervisor you start Task.Supervisor with name of your choice:

children = [
  {Task.Supervisor, name: MyApp.TaskSupervisor}
]

Supervisor.start_link(children, strategy: :one_for_one)

And then in your request handler:

parent = self()
ref = make_ref()

Task.Supervisor.start_child(MyApp.TaskSupervisor, fn() ->
  send(parent, {ref, do_long_running_stuff()})
end)

receive do
  {^ref, result} -> notify_user(result)
end

That way you do not need to worry about handling situation when user is no longer there to receive message.