Cowboy/Ranch 在客户端关闭连接时杀死处理程序
Cowboy/Ranch kills handler process when client closes connection
我的 Phoenix 应用程序在 HTTP 端点后面有复杂的业务逻辑。此逻辑包括与数据库和少量外部服务的交互,一旦请求处理开始,在所有操作完成之前不得中断。
但如果客户端突然关闭连接,似乎 Cowboy 或 Ranch 会杀死请求处理程序进程(Phoenix 控制器),从而导致部分执行的业务进程。为了调试这个,我在控制器操作中有以下代码:
Process.flag(:trap_exit, true)
receive do
msg -> Logger.info("Message: #{inspect msg}")
after
10_000 -> Logger.info("Timeout")
end
为了模拟连接关闭,我设置了超时:curl --request POST 'http://localhost:4003' --max-time 3
。
在 IEx 控制台 3 秒后,我看到进程即将退出:Message: {:EXIT, #PID<0.4196.0>, :shutdown}
.
所以我需要让控制器完成它的工作,并在它仍然存在时回复客户端,或者在连接丢失时不做任何事情。这将是实现这一目标的最佳方式:
- 陷阱在控制器操作中退出并忽略退出消息;
- 在控制器操作中未链接
Task
并等待其结果;
- 以某种方式配置 Cowboy/Ranch 以便它不会终止处理程序进程(如果可能的话)(尝试
exit_on_close
但没有成功)?
处理进程会在请求结束后被杀死,这就是它们的目的。如果你想在后台处理一些数据,然后启动额外的进程。最简单的方法是您提出的第二种方法,但对使用 Task.Supervisor
.
稍作修改
因此,在您的应用主管中,您开始 Task.Supervisor
使用您选择的名称:
children = [
{Task.Supervisor, name: MyApp.TaskSupervisor}
]
Supervisor.start_link(children, strategy: :one_for_one)
然后在您的请求处理程序中:
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
这样您就无需担心用户不再接收消息时的处理情况。
我的 Phoenix 应用程序在 HTTP 端点后面有复杂的业务逻辑。此逻辑包括与数据库和少量外部服务的交互,一旦请求处理开始,在所有操作完成之前不得中断。
但如果客户端突然关闭连接,似乎 Cowboy 或 Ranch 会杀死请求处理程序进程(Phoenix 控制器),从而导致部分执行的业务进程。为了调试这个,我在控制器操作中有以下代码:
Process.flag(:trap_exit, true)
receive do
msg -> Logger.info("Message: #{inspect msg}")
after
10_000 -> Logger.info("Timeout")
end
为了模拟连接关闭,我设置了超时:curl --request POST 'http://localhost:4003' --max-time 3
。
在 IEx 控制台 3 秒后,我看到进程即将退出:Message: {:EXIT, #PID<0.4196.0>, :shutdown}
.
所以我需要让控制器完成它的工作,并在它仍然存在时回复客户端,或者在连接丢失时不做任何事情。这将是实现这一目标的最佳方式:
- 陷阱在控制器操作中退出并忽略退出消息;
- 在控制器操作中未链接
Task
并等待其结果; - 以某种方式配置 Cowboy/Ranch 以便它不会终止处理程序进程(如果可能的话)(尝试
exit_on_close
但没有成功)?
处理进程会在请求结束后被杀死,这就是它们的目的。如果你想在后台处理一些数据,然后启动额外的进程。最简单的方法是您提出的第二种方法,但对使用 Task.Supervisor
.
因此,在您的应用主管中,您开始 Task.Supervisor
使用您选择的名称:
children = [
{Task.Supervisor, name: MyApp.TaskSupervisor}
]
Supervisor.start_link(children, strategy: :one_for_one)
然后在您的请求处理程序中:
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
这样您就无需担心用户不再接收消息时的处理情况。