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:
Task
in controller action and wait for its results;exit_on_close
with no luck)?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.