在应用发布等场景中,常需要结束应用进程,此时应用程序可能正在执行一些关键操作,来不及完成事务或者清理资源,从而导致各种不确定异常发生。因此,研究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个退出点。