在应用发布等场景中,常需要结束应用进程,此时应用程序可能正在执行一些关键操作,来不及完成事务或者清理资源,从而导致各种不确定异常发生。因此,研究dotNet应用的优雅退出有比较高的价值。
结论
(心急也能吃热豆腐,先看结论)
Win10 x64 | Ubuntu18 Arm | |||||||||
Ctrl+C | 关窗口 | Alt+F4 | 杀进程 | TaskKill | 星尘Kill | Ctrl+C | Kill | Kill -9 | 星尘Kill | |
Console.CancelKeyPress | ✔ | ✖ | ✖ | ✖ | ✖ | ✖ | ✔ | ✖ | ✖ | ✖ |
AppDomain.CurrentDomain.ProcessExit | ✔ | ✔ | ✖ | ✖ | ✖ | ✖ | ✔ | ✔ | ✖ | ✔ |
AssemblyLoadContext.Default.Unloading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✖ | ✔ |
PosixSignalRegistration.Create(SIGINT) | ✔ | ✖ | ✖ | ✖ | ✖ | ✖ | ✔ | ✖ | ✖ | ✖ |
PosixSignalRegistration.Create(SIGTERM) | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ | ✔ | ✖ | ✔ |
app.Lifetime.ApplicationStopping.Register | ✔ | ✖ | ✖ | ✖ | ✖ | ✖ | ✔ | ✔ | ✖ | ✔ |
app.Lifetime.ApplicationStopped.Register | ✔ | ✖ | ✖ | ✖ | ✖ | ✖ | ✔ | ✔ | ✖ | ✔ |
NewLife.Model.Host.RegisterExit | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✖ | ✔ |
注:最后的 NewLife.Model.Host.RegisterExit 实际上是对前面5个退出点的封装,位于 NewLife.Core 中。
准备工作
在开始之前,我们以魔方为应用例子,修改一版带有退出日志的启动文件,给8个退出点都加入日志。
app.UseCube(builder.Environment); app.UseCubeHome(); app.UseAuthorization(); app.UseResponseCompression(); //app.MapControllerRoute(name: "default", pattern: "{controller=Index}/{action=Index}/{id?}"); //app.MapControllerRoute(name: "default2", pattern: "{area=Admin}/{controller=Index}/{action=Index}/{id?}"); app.RegisterService("SSO", null, builder.Environment.EnvironmentName, "/cube/info"); AssemblyLoadContext.Default.Unloading += ctx => { XTrace.WriteLine("Unloading!"); }; AppDomain.CurrentDomain.ProcessExit += (s, e) => { XTrace.WriteLine("ProcessExit!"); }; Console.CancelKeyPress += (s, e) => { XTrace.WriteLine("CancelKeyPress!"); }; PosixSignalRegistration.Create(PosixSignal.SIGINT, ctx => { XTrace.WriteLine("SIGINT"); }); PosixSignalRegistration.Create(PosixSignal.SIGTERM, ctx => { XTrace.WriteLine("SIGTERM"); }); NewLife.Model.Host.RegisterExit((s, e) => { XTrace.WriteLine("RegisterExit"); }); app.Lifetime.ApplicationStopping.Register(() => { XTrace.WriteLine("ApplicationStopping!"); }); app.Lifetime.ApplicationStopped.Register(() => { XTrace.WriteLine("ApplicationStopped!"); }); app.Run(); XTrace.WriteLine("Finish!"); Thread.Sleep(3000); XTrace.WriteLine("Exit!");
打包CubeSSO.zip,用于星尘远程发布。
在魔方中新建应用发布集sso,工作目录设置为相对于StarAgent的../apps/sso,待会要到该目录查看日志。
在应用部署集的版本管理中,新增版本并上传前面打包的CubeSSO.zip
使用最新版本,返回应用部署集
进入部署节点,新增两个节点,当前计算机(win10-x64)和A2工控机(ubuntu18-arm),作为待发布的目标服务器。
回到部署节点页面,勾选需要发布的两个节点,点击上方的“发布”。
在本机独立运行的StarAgent中,可以看到魔方sso已经启动。
查看工作目录,记住这里的 ../apps/sso/Log 日志目录
至此,所有准备工作已完成,后续测试的过程中需要多次发布或启动,不再详述细节。
Win10-x64测试
Ctrl+C测试
在窗口上按下Ctrl+C,魔方退出,检查日志,触发7个退出点(除了SIGTERM)。
09:04:46.994 25 Y P [Membership] Select * From Parameter 09:04:47.581 26 N - CancelKeyPress! 09:04:47.581 26 N - RegisterExit 09:04:47.582 26 N - SIGINT 09:04:47.582 26 N - RegisterExit 09:04:47.583 26 N - ApplicationStopping! 09:04:47.587 16 Y P ApplicationStopped! 09:04:47.593 16 Y P 取消注册 {"ServiceName":"SSO","ClientId":"10.0.0.3@40692","IP":"10.0.0.3,172.25.192.1,172.25.112.1,172.20.64.1,172.17.0.1,192.168.15.1,192.168.6.1","Version":"6.0.8527.15515","Address":"http://[::]:3380","Health":"/cube/info","Tag":"Production"} 09:04:47.618 1 N - Finish! 09:04:50.634 1 N - Exit! 09:04:50.635 2 N - Unloading! 09:04:50.635 2 N - RegisterExit 09:04:50.636 2 N - ProcessExit! 09:04:50.636 2 N - RegisterExit
关闭窗口测试
前一次关闭sso以后,星尘代理StarAgent检测(每30秒)到应用退出,会再次启动。
这一次,点击右上角的叉叉,检查日志,仅触发了3个退出点,aspnetcore自身的退出点都没有触发。
09:13:50.401 23 Y P [Membership] Select * From Parameter 09:13:52.992 2 N - Unloading! 09:13:52.992 2 N - RegisterExit 09:13:52.993 2 N - ProcessExit! 09:13:52.993 2 N - RegisterExit
Alt+F4测试
等sso再次启动后,按Alt+F4退出,检查日志,这次连ProcessExit都没有了。
09:15:38.219 5 Y P [Membership] Select * From Parameter 09:15:41.171 2 N - Unloading! 09:15:41.172 2 N - RegisterExit
任务管理器杀进程
等sso再次启动后,在任务管理器找到进程,结束进程,检查日志,只触发了2个退出点。
09:17:11.330 7 Y P [Membership] Select Count(*) From Parameter 09:17:24.581 2 N - Unloading! 09:17:24.582 2 N - RegisterExit
命令杀进程
等sso再次启动后,输入 TaskKill 命令,杀死目标进程,检查日志,触发2个退出点。
09:26:31.720 3 Y P [Membership] Select * From Parameter 09:26:33.942 2 N - Unloading! 09:26:33.942 2 N - RegisterExit
需要注意的是,TaskKill如果加上-f参数强制结束进程,也是不触发任何退出点。
星尘停止应用
在星尘发布的应用部署集中,进入部署节点,停止该节点,即时发命令停止应用进程,并不再拉起。
检查日志,所有退出点都没有触发。这里也是星尘发布需要优化的地方,希望能够优雅退出目标应用。
09:21:14.543 20 Y P [Membership] Select * From Parameter 09:21:29.557 11 Y P [Membership] Select * From Parameter
优化星尘代理StarAgent,在发布关闭消息后多等一会,再次测试。
检查日志,触发了2个退出点。
09:49:37.671 13 Y P [Membership] Select * From Parameter 09:49:47.127 2 N - Unloading! 09:49:47.127 2 N - RegisterExit
Ubuntu18-arm测试
Kill测试
等sso被StarAgent拉起后,使用命令“kill 2020”(这里的2020是当时进程id)杀死进程,检查日志。
14:18:40.283 11 Y P [Membership] Select * From Parameter 14:18:45.725 20 N .NET Signal Handler SIGTERM 14:18:45.740 20 N .NET Signal Handler RegisterExit 14:18:45.745 20 N .NET Signal Handler ApplicationStopping! 14:18:45.830 11 Y P ApplicationStopped! 14:18:45.852 11 Y P 取消注册 {"ServiceName":"SSO","ClientId":"10.0.0.24@2020","IP":"10.0.0.24","Version":"6.0.8527.15515","Address":"http://[::]:80","Health":"/cube/info","Tag":"Production"} 14:18:45.932 1 N - Finish! 14:18:48.932 1 N - Exit! 14:18:48.953 2 N - Unloading! 14:18:48.955 2 N - RegisterExit 14:18:48.973 2 N - ProcessExit! 14:18:48.973 2 N - RegisterExit
日志显示,触发了6个退出点。
Kill-9测试
等sso再次启动后,使用命令“kill -9 2092”(这里的2092是当时进程id)杀死进程,检查日志,发现没有触发任何退出点。
14:22:58.257 18 Y P [Membership] Select Count(*) From Parameter 14:22:58.259 20 Y P [Membership] Select * From Parameter
星尘杀
在星尘发布中心的应用部署集中,停止应用,检查日志,触发了6个退出点,包括aspnetcore的退出点。因此,星尘代理StarAgent在Linux环境中能够优雅结束目标应用进程。
14:43:09.668 22 Y P [Membership] Select * From Parameter 14:43:18.785 24 N .NET Signal Handler SIGTERM 14:43:18.791 24 N .NET Signal Handler RegisterExit 14:43:18.792 24 N .NET Signal Handler ApplicationStopping! 14:43:18.874 22 Y P ApplicationStopped! 14:43:18.901 22 Y P 取消注册 {"ServiceName":"SSO","ClientId":"10.0.0.24@2402","IP":"10.0.0.24","Version":"6.0.8527.15515","Address":"http://[::]:80","Health":"/cube/info","Tag":"Production"} 14:43:18.990 1 N - Finish! 14:43:21.991 1 N - Exit! 14:43:22.012 2 N - Unloading! 14:43:22.014 2 N - RegisterExit 14:43:22.033 2 N - ProcessExit! 14:43:22.033 2 N - RegisterExit
Ctrl+C测试
在发布中心停止sso应用后,改为ssh登录后手工启动应用,然后使用“Ctrl+C”结束应用,检查日志。
可以看到,共触发了7个退出点。