SetConsoleCtrlHandler实现资源释放

今天决定对程序产生的不完整的临时文件进行下优化,当然这些临时文件是非预期的,在程序中都进行了处理,但是在某些情况下确实是发生了。

现象分析

首先可以确认的是这些临时文件都是程序在退出时候未处理生成的。当然程序全部是正常退出的,并没有异常崩溃,但是退出的方法有多种,比如Ctrl+C按键,比如直接点击控制台的关闭按钮,但是程序却没有在退出的时候调用析构进行资源释放。

诚然很多资源在进程关闭后可以被操作系统回收(先不谈规范性),比如内存、线程等。但是并非所有的资源都是系统默认资源,比如我们的应用中,程序是实时的写MP4媒体文件,如果结尾处不进行文件更新,那么文件肯定是不可用的。不过我们很多的释放都在析构函数也实现了一份,很大程度上都可以保证得到运行。不过仔细观察日志,可以发现确实是程序在某些情况下退出的时候没有处理释放。

从Win32的机制讲,处理程序退出的方法就是SetConsoleCtrlHandler,一般我们的处理逻辑大概如下

BOOL WINAPI ConsoleHandler(DWORD event) {
switch(event) {
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT: {
log("catch signal %d", event);
PostThreadMessage(appid, WM_QUIT, 0, 0);
}
break;
}
}

基本上就是在获取事件后给进程发消息,通知进程退出,逻辑大概如下

int main(int argc, char* argv[]) {
...
SetConsoleCtrlHandler((PHANDLER_ROUTINE)ConsoleHandler,TRUE);
...
if(app.InitApp()) {
app.Run();
}
app.ExitApp();
...
}

深入排查

上面的代码基本都是比较通用的代码,在程序退出的时候基本都是可以看到日志中打印的catch日志的,说明这个机制是没有问题的。然后继续在程序退出过程中添加日志,这个时候就可以发现在Ctrl+C等情况下,退出日志基本是完整的,不过在直接关闭控制台按钮等方式下,日志却只打印了部分就结束了。这说明直接关闭程序应该是来不及释放资源就结束了的。仔细阅读下MSDN文档 SetConsoleCtrlHandler ,并不能获取到特别有用的信息。

直接写一段测试代码测试验证下

int pid = 0;
HANDLE g_handle = CreateEvent(NULL,FALSE,FALSE,NULL);

BOOL WINAPI ConsoleHandler(DWORD event)
{
printf("收到 %d 信号\n", event);
PostThreadMessage(pid, WM_QUIT, 0, 0);
WaitForSingleObject(g_handle,INFINITE);

return TRUE;
}

int _tmain(int argc, _TCHAR* argv[])
{
pid = GetCurrentThreadId();
SetConsoleCtrlHandler((PHANDLER_ROUTINE)ConsoleHandler, TRUE);

MSG tMsg;
while(GetMessage(&tMsg, 0, 0, 0))
{}

MessageBoxA(0, "OnExit", "test", 0);
SetEvent(g_handle);
CloseHandle(g_handle);
return 0;
}

分别通过Ctrl+C、关闭按钮关闭,任务管理器中结束进程,仔细观察就可以发现,Ctrl+C方式基本可以保证程序正常结束。但是后面两种,在不操作的情况下,在几秒后会被系统Kill。看来有些信号是有时限的啊,但是如果没有WaitForSingleObject的阻塞,除了Ctrl+C方式外,其他两种情况程序迅速就结束了。

从网上提供的资料得到以下结论:

  • CTRL_CLOSE_EVENT 超时时间为5秒
  • CTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENT 超时时间为20秒

注销关机没测试,不过CTRL_CLOSE_EVENT确实是大概在5秒的样子。

优化程序

知道了原因,接下来就是修改了,直接根据上面的测试例子改改就行,不过由于关键的CTRL_CLOSE_EVENT时间只有5秒,也就意味着尽量把特别重要的释放优先进行。并且一定要在信号函数内部阻塞,而且为了确保程序不会阻塞卡死在信号函数内(毕竟还有Ctrl+C等情况),根据上面的超时时间,20秒是个不错的选择,即

WaitForSingleObject(g_handle, 20000);

修改完成之后测试,之前会产生临时文件的场景基本已经OK了,当然这其中某些情况下只有5秒,并不能保证100%的可靠,不过应该也只能做到这个地步了。

PS:看到关机事件的超时是20秒,想到好多人吐槽关机慢就禁不住呵呵一笑,想我大Linux的SIGKILL还真是简单暴力啊!

最近的文章

探究WinSW封装Nginx服务及日志清理

如前述,我们项目中目前还在Win32平台,切换到了Nginx架构的RTMP服务器。当然也是长时间不规范的直接双击运行的,虽说Nginx的架构保证进程活着问题不大,但是无法应对突然的停电,停电之后无人去操作启动,造成了无人觉察的故障,这也是一个极大的风险。 技术选型当然我们的目标是把nginx做成一个 …

技术 继续阅读
更早的文章

VC的Release版本无法找到入口问题

在之前的版本上改了很多功能之后,一直用的Debug版本在本地调试,没有什么问题。今天给同事提供了老版本和新版本的Release的EXE程序,结果反馈新版本的程序运行不了。 缘由按照惯例,肯定是没安装redist_x86.exe环境,转念一想我们打包可是自带了msvcrt.dll等CRT库的,况且测试 …

技术 继续阅读