防止MSYS的“bash”杀死捕获^C的进程

33

我有一个控制台模式的Windows应用程序(从Unix移植而来),最初设计是在接收到^C(Unix中的SIGINT)时进行干净的退出。在这种情况下,干净的退出涉及等待,可能需要相当长的时间,以关闭远程网络连接。(我知道这不是^C的正常行为,但我不能改变它。)该程序是单线程的。

我可以使用signal(SIGINT)(与Unix下相同)或SetConsoleCtrlHandler捕获^C。无论哪种方法,在CMD.EXE下运行程序时都能正常工作。但是,如果我使用带有MSYS的“bash” shell(我正在使用MinGW环境构建程序,因为这允许我重用Unix makefile),那么程序会在接收到^C后的某个随机短时间内(小于100毫秒)被强制终止。这是不可接受的,因为如我所提到的,该程序需要等待远程网络连接关闭。

很可能人们想在MSYS bash下运行此程序。而且,这种效果会破坏测试套件。我无法找到任何在程序内部(理想情况)或shell设置上解决问题的方法。有人能推荐什么吗?


7
在Windows上,您可以随意使用<signal.h>,但是当您在控制台窗口中键入control-C时,操作系统不会生成SIGINT信号,因此它对您没有任何帮助。 - zwol
4
<signal.h>在Windows上与Microsoft CRT一起使用对我很有效。当我在控制台窗口中键入Ctrl+C时,使用signal()注册的处理程序将被调用,并带有SIGINT信号。 - Brian Nixon
1
我关心的连接并不是陈旧的 - 它们是活跃的。无论如何,我相信此时整个问题只是MSYS中的一个错误。如果我能找出如何解决这个错误就好了,但我不会过分强调它 - 他们已经十年没有碰过Cygwin的分支了!当然有漏洞。(有人知道一个足够类Unix的shell可以让autoconf满意,但完全不需要MSYS或Cygwin吗?) - zwol
3
@Zack 听起来你已经找到了答案,也许是时候关闭这个问题了,这样像我这样的人就不会浪费时间试图回答它了? ;) - Roman Starkov
1
请注意:这可能是由于mintty终端在支持本地控制台程序时的限制所致,正如https://superuser.com/questions/1039098/how-to-make-mintty-close-gracefully-on-ctrl-c/1156410中提到的那样... - Anon
显示剩余16条评论
6个回答

8
我有完全相同的问题 - 我编写了一个带有SIGINT/SIGTERM处理程序的程序。该处理程序执行清理工作,有时需要一段时间。当我在msys bash中运行程序时,按下ctrl-c会导致我的SIGINT处理程序触发,但它无法完成 - 在它完成清理工作之前,程序被终止(“从外部”)。
借鉴phs的答案以及类似问题的这个答案:https://dev59.com/pH_aa4cB1Zd3GeqP67iG#23678996,我提出了以下解决方案。它非常简单,可能有一些副作用我还没有发现,但它解决了我的问题。
创建一个~/.bashrc文件,并加入以下内容:
trap '' SIGINT

就是这样。这个方法可以捕获sigint信号,防止msys bash从“外部”终止您的程序。然而,它仍然会将SIGINT信号传递到您的程序中,使其能够进行优雅的清理/关闭。我无法确切地告诉您为什么它会以这种方式工作,但它确实可以 - 至少对我来说如此。

祝好运!


我猜这可能与Cygwin Bash的信号处理程序干扰中断系统调用有关。我记得POSIX信号处理程序不应执行任何系统调用以避免干扰。我不知道Bash在Linux中如何实现陷阱,但它似乎总是完成陷阱并将其设置为空处理程序。一个示例脚本显示了trap的不稳定行为 - 在Cygwin中,当使用timeout 2s(发送SIGTERM)调用时,https://gist.github.com/ilatypov/2d8d8043ef6592ebd6064906b773c6c7 - eel ghEEz
发现一个副作用 - ^c 清空当前输入的命令不再起作用。(例如,输入 asdf 然后按下 ^c) - Lexi
FYI -- 这个技巧显然适用于32位但不适用于64位...不确定为什么。 - Dan Esparza

2
这可能是由于臭名昭著的mintty“与外部程序的输入/输出交互”问题(又称mintty问题#56)所致。在这种情况下,它表现为Ctrl-C突然终止程序,而不是作为信号传递给程序以被捕获和处理。这个理论的证据基于zwol的广泛解释:“控制台模式的Windows应用程序”,“[应用程序]设计为在收到^C时进行清理退出”,“[应用程序]在CMD.EXE下运行程序时工作正常”,但是“[使用MSYS附带的终端]时...程序被强制终止”(在撰写本文时(2018年),MSYS默认使用mintty作为其终端)。

不幸的是,mintty不能完全替代Windows控制台,并且没有实现“本地”Windows程序所期望的各种行为。不过,当在mintty中运行这些本地程序时,您可以尝试使用winpty进行包装...

其他问题也描述了这种行为: 参见 https://superuser.com/questions/606201/how-to-politely-kill-windows-process-from-cygwinhttps://superuser.com/questions/1039098/how-to-make-mintty-close-gracefully-on-ctrl-c


1

Arg - 对评论进行5分钟编辑。这是我想写的内容:

作为一种解决方法,我建议关闭标准输入的ENABLED_PROCESSED_INPUT,以便将CTRL-C报告为键盘输入而不是信号,而不是尝试捕获也传递到shell的CTRL-C事件:

DWORD mode;
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(hstdin, &mode);
SetConsoleMode(hstdin, mode & ~ENABLE_PROCESSED_INPUT); /* disable CTRL-C processing as a signal */

你可以在主线程中处理键盘输入,同时让程序的其余部分在单独的线程中运行,并在收到 CTRL-C 信号时设置一个事件以进行清理。


我喜欢这个想法。下次我遇到Windows问题时,我会尝试它并告诉你效果如何。 - zwol

0

当您使用MSYS bash运行程序时,您是直接运行可执行文件,还是有一个包装(bash)shell脚本?

如果是这样,它可能会使用trap命令注册自定义Ctrl-C处理程序(执行sleep,然后执行kill)。如果存在这样的东西,请更改或删除它。

如果没有注册trap,或者没有包装脚本,请考虑制作这样的脚本并添加自己的trap以覆盖默认行为。您可以在此处bash的手册页(SHELL BUILTINS部分)中看到如何使用它的示例。


我直接运行可执行文件。我认为有比创建包装脚本更好的解决方案来处理这个MSYS bug(我怀疑它不会起作用——请参见问题评论中链接的错误报告;这是MSYS内部深层次的问题)。 - zwol

0

Ctrl-C 是 SIGINT 吗?我以为 Ctrl-Z 是 SIGINT,但是 Ctrl-C 是 SIGTERM。请检查一下。


1
严格来说,Windows 没有 SIGINT 或 SIGTERM。Ctrl-Z 会在标准输入上生成 EOF,而 Ctrl-C 则会导致生成“控制台控制事件”。有两个记录的控制台控制事件:Ctrl-C 和 Ctrl-BREAK。MSVCRT 看起来会将这两个事件都映射到内部的 SIGINT。请参阅 http://msdn.microsoft.com/en-us/library/ms683155%28v=VS.85%29.aspx。 - zwol
即使在UNIX上,Ctrl-C通常发送SIGINT信号,而Ctrl-Z通常发送SIGTSTP信号(终端停止)。 - CB Bailey

0
你有CYGWIN环境设置(在控制面板/环境变量中)吗?尝试设置CYGWIN=notty并重新启动打开一个新的MSYS bash shell - 问题是否仍然存在?

我的错 - 那么为什么不使用您自己的自定义键盘处理程序呢?我建议使用它,而不是尝试捕获也传递到 shell 的 CTRL-C 事件: - Anthill
能否详细说明一下?我完全不知道如何做这样的事情。 - zwol
嗨,Zack - 评论区没有足够的空间 - 请查看我的答案,了解如何禁用stdin上的CTRL-C信号处理。如果您需要一个简单的键盘处理程序示例,可以捕获CTRL-C作为输入而不是信号,请告诉我,我会发布一个示例。 - Anthill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接