33

I have a console-mode Windows application (ported from Unix) that was originally designed to do a clean exit when it received ^C (Unix SIGINT). A clean exit in this case involves waiting, potentially quite a long time, for remote network connections to close down. (I know this is not the normal behavior of ^C but I am not in a position to change it.) The program is single-threaded.

I can trap ^C with either signal(SIGINT) (as under Unix) or with SetConsoleCtrlHandler. Either works correctly when the program is run under CMD.EXE. However, if I use the "bash" shell that comes with MSYS (I am using the MinGW environment to build the program, as this allows me to reuse the Unix makefiles) then the program is forcibly terminated some random, short time (less than 100 milliseconds) after the ^C. This is unacceptable, since as I mentioned, the program needs to wait for remote network connections to close down.

It is very likely that people will want to run this program under MSYS bash. Also, this effect breaks the test suite. I have not been able to find any way to work around the problem either from within the program (ideal) or by settings on the shell (acceptable). Can anyone recommend anything?

zwol
  • 121,956
  • 33
  • 219
  • 328
  • Why can't you use `` on Windows? It's part of the C standard. – Kerrek SB Aug 16 '11 at 21:52
  • 7
    You can use `` all you want on Windows, but the OS does not generate `SIGINT` when you type control-C at a console window, so it doesn't do you any good. – zwol Aug 16 '11 at 21:55
  • 1
    Is there a way to make the network connection more robust? Even if you handle the program shutdown properly, what will happen when someone trips over the power cord? – Mark Ransom Aug 16 '11 at 22:20
  • A simple `handle_console_event()` that just prints out what event type it received and returns `TRUE` works fine for me in a small console program that does nothing in `main()` but set up the handler, prints "hello world", then go into an infinite loop. It doesn't get terminated when I press Ctrl-C - it just prints out the event in the handler. I have to kill it some other way. Are you sure there's not some other problem causing the process to be terminated (maybe in the awakened main thread)? – Michael Burr Aug 16 '11 at 22:31
  • 4
    `` works for me with the Microsoft CRT on Windows. My handler registered with `signal()` gets called with `SIGINT` when I type Ctrl+C in the console window. – Brian Nixon Aug 16 '11 at 23:03
  • @Michael Burr: I'm sure. I wrote a test program that is (essentially) the same as your test program, and no matter what I return from `handle_console_event`, ctrl-C kills it. If I *don't return* - if I put an infinite loop in the handler - it still gets killed. – zwol Aug 16 '11 at 23:06
  • @Brian Nixon: I'm on Windows XP and using MinGW, which I *think* just wraps MSVCRT. What are you using? – zwol Aug 16 '11 at 23:07
  • @Michael Burr: Correction, ctrl-C only kills it if I run it from the MinGW shell. If I run it from CMD.EXE I get the behavior you describe. I'll just go bang my head on the wall for a while now. – zwol Aug 16 '11 at 23:10
  • @Zack: I'm not familiar with the MinGW shell (is that MSYS?). I'd assume that source is available, though that doesn't mean it would necessarily be easy to figure out. But if you can find out what it's doing you might be able to devise a workaround (or force your users to use cmd.exe or a 'fixed' MinGW shell that you provide). – Michael Burr Aug 16 '11 at 23:22
  • @Zack: I'm linking MSVCRT directly from C/C++ compiled with Visual C++. The implementation of `signal()` in the run-time library uses `SetConsoleControlHandler()` to install a handler that calls the function I register. Sounds like MinGW is interfering somewhere. – Brian Nixon Aug 16 '11 at 23:34
  • 1
    @Brian: Actually, I think that was my mistake: see http://stackoverflow.com/questions/7085604/sending-c-to-python-subprocess-objects-on-windows -- *generating* CTRL_C_EVENT from another process doesn't seem to be supported at the kernel32 level, which made me think the signal handlers weren't doing anything constructive. – zwol Aug 16 '11 at 23:50
  • Why not `shutdown` the sockets? Waiting for socket timeouts isn't very expedient ^C behaviour. Assuming death isn't due to `SIGPIPE`. – Steve-o Aug 17 '11 at 02:16
  • Is it possible that you're mistaken in thinking you need to wait for network connections to shutdown and that you really should just be using `SO_REUSEADDR`? (Almost any server should use this socket option.) – R.. GitHub STOP HELPING ICE Aug 17 '11 at 02:35
  • I am already using `SO_REUSEADDR`. The documented and expected-by-users behavior (on Unix) is "on the first SIGINT, close listening sockets but *continue processing traffic* on already-open connections until they are all closed, then exit. On a second SIGINT, or on SIGTERM, forcibly close all connections and exit immediately." I'm trying to get as close to that on Windows as I can manage. (Connections to this server are minutes to hours long, and knocking them out from under the client is highly disruptive.) – zwol Aug 17 '11 at 03:17
  • @Zack: Is it possible for the client or the server to send a "heartbeat" signal? It could be a very short data sequence that's used only to tell the server to keep the connection alive. That way if the client is forcibly closed (due to Ctrl+C or power failure), then you won't have stale connections. – In silico Aug 17 '11 at 03:49
  • 1
    The connections I'm concerned with are not stale - they're active. Anyway I'm pretty sure at this point that the entire problem is just down to a bug in MSYS. Be nice if I could figure out how to work around that bug, but I'm not going to beat on it very hard - they haven't touched their fork of Cygwin in ten years! Of course it's buggy. (Anyone know a shell that's unixy enough to make autoconf happy but doesn't need MSYS or Cygwin at all?) – zwol Aug 17 '11 at 04:13
  • 3
    @Zack Sounds like you've found the answer - perhaps it's time to close this question, so that people like me don't spend time trying to answer it? ;) – Roman Starkov Aug 17 '11 at 22:17
  • I agree with @Steve-o. A Ctrl-C is an indication the user wants to quit immediately. Having a long wait for a socket is not a good idea, after a Ctrl-C was issued. – Rudy Velthuis Sep 02 '11 at 08:57
  • 1
    Think this is the bug: http://sourceforge.net/tracker/index.php?func=detail&aid=1333217&group_id=2435&atid=102435 – asc99c Sep 03 '11 at 09:15
  • 1
    Note: the likely cause of this is due to [limitations in mintty terminal when supporting native console programs](https://superuser.com/a/1156410/265087) as mentioned over on https://superuser.com/questions/1039098/how-to-make-mintty-close-gracefully-on-ctrl-c/1156410 ... – Anon Oct 04 '18 at 06:13
  • @Anon Please make that an answer, after reviewing the discussions you linked to, I think you've genuinely found the root cause of the problem. I no longer work on the program that prompted the question, but this is nonetheless one of my most popular questions and I'd like to give it an accepted answer. – zwol Oct 04 '18 at 17:30

6 Answers6

8

I had the exact same problem - I had written a program with a SIGINT/SIGTERM handler. That handler did clean-up work which sometimes took awhile. When I ran the program from within msys bash, ctrl-c would cause my SIGINT handler to fire, but it would not finish - the program was terminated ("from the outside", as it were) before it could complete its clean-up work.

Building on phs's answer, and this answer to a similar question: https://stackoverflow.com/a/23678996/2494650, I came up with the following solution. It's insanely simple, and it might have some side-effects that I've yet to discover, but it fixed the problem for me.

Create a ~/.bashrc file with the following line:

trap '' SIGINT

That's it. This traps the sigint signal and prevents msys bash from terminating your program "from the outside". However, it somehow still lets the SIGINT signal through to your program, allowing it to do its graceful cleanup/shutdown. I can't tell you exactly why it works this way, but it does - at least for me.

Good luck!

Community
  • 1
  • 1
Quercus
  • 264
  • 2
  • 8
  • I guess this could be related to the Cygwin Bash's signal handler intefering with interrupted system calls. I remember that POSIX signal handlers are not expected to perform any system calls to avoid interference. I do not know how Bash implements trap in Linux but it appears to always complete the trap with both resetting the trap and setting it to an empty handler. A sample script shows unstable behaviour of trap - in Cygwin when invoked with timeout 2s (which sends SIGTERM), https://gist.github.com/ilatypov/2d8d8043ef6592ebd6064906b773c6c7 – eel ghEEz Nov 04 '16 at 15:56
  • Spotted a side effect - ^c to empty the current typed command no longer works. (eg type asdf and then ^c) – Lex R Mar 12 '17 at 11:59
  • FYI -- this trick apparently [works for 32-bit but not for 64-bit](https://stackoverflow.com/q/38824561/19020) ... not sure why. – Dan Esparza Oct 12 '18 at 13:15
2

This could be due to the infamous mintty "Input/Output interaction with alien programs" problem (aka mintty issue #56). In this case it is manifesting as Ctrl-C abruptly killing the program rather than being passed down to the program as a signal to be caught and handled. Evidence for this theory is based on zwol's extensive explanation: "console-mode Windows application", "[application is] designed to do a clean exit when it received ^C", "[application] works correctly when the program is run under CMD.EXE" but "[when using the terminal] that comes with MSYS [...] program is forcibly terminated" (at the time of writing (2018) MSYS defaults to using mintty as its terminal).

Unfortunately mintty isn't a full Windows console replacement and various behaviours expected by "native" Windows programs are not implemented. However, you might have some joy wrapping such native programs in winpty when running them within mintty...

Other questions also describe this behaviour: see https://superuser.com/questions/606201/how-to-politely-kill-windows-process-from-cygwin and https://superuser.com/questions/1039098/how-to-make-mintty-close-gracefully-on-ctrl-c .

Anon
  • 4,044
  • 2
  • 26
  • 46
1

Arg - 5 minute edit on comment. Here's what I wanted to write:

As a workaround, instead of trying to trap the CTRL-C event which is also being propagated to the shell I'd propose turning off the ENABLED_PROCESSED_INPUT on stdin so that CTRL-C is reported as a keyboard input instead of as a signal:

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

You could then process keyboard input in your main thread while the rest of the program does its thing in a separate thread and set an event to cleanup when CTRL-C is received.

Anthill
  • 1,154
  • 9
  • 20
  • I like this idea. I'll try it next time I cycle back to Windows issues and let you know how it goes. – zwol Sep 07 '11 at 21:07
0

When you run your program with MSYS bash, do you run the executable directly, or is there a wrapping (bash) shell script?

If so, it may be registering a custom Ctrl-C handler with the trap command (that does a sleep followed by a kill.) If such a thing exists, alter or remove it.

If there is no trap registered, or there is no wrapping script, consider making such a script and adding your own trap to override the default behavior. You can see an example of how to use it here or on bash's man page (in the SHELL BUILTINS section).

phs
  • 10,072
  • 3
  • 54
  • 80
  • I run the executable directly. I would like to think that there is a better solution than creating a wrapper script just to deal with this MSYS bug (and I suspect it wouldn't work -- see the bug report linked from the comments on the question; this is a problem deep in the guts of MSYS). – zwol Sep 04 '11 at 22:33
0

Ctrl-C is SIGINT? I thought Ctrl-Z was SIGINT, but Ctrl-C is SIGTERM. Check that.

Nulik
  • 4,803
  • 7
  • 36
  • 90
  • 1
    Well, technically speaking, Windows doesn't have SIGINT *or* SIGTERM. Ctrl-Z generates EOF on standard input, and Ctrl-C causes a "console control event" to be generated. There are two documented console control events: Ctrl-C and Ctrl-BREAK. MSVCRT appears to map both of those to SIGINT internally. See http://msdn.microsoft.com/en-us/library/ms683155%28v=VS.85%29.aspx – zwol Sep 06 '11 at 16:41
  • Even on UNIX, Ctrl-C usually sends SIGINT and Ctrl-Z usually sends SIGTSTP (terminal stop). – CB Bailey Sep 06 '11 at 19:34
0

Do you have a CYGWIN environment setting (in control panel/environment variables)? Try setting CYGWIN=notty and restart open a new MSYS bash shell - does the problem persist?

Anthill
  • 1,154
  • 9
  • 20
  • My bad - in that case why not use your own custom keyboard handler? Instead of trying to trap the CTRL-C event which is also being propagated to the shell I'd propose: – Anthill Sep 07 '11 at 08:49
  • Could you elaborate on that? I haven't the least idea how to do such a thing. – zwol Sep 07 '11 at 14:53
  • Hi Zack - not enough place in the comments - see my answer about disabling CTRL-C signal processing on stdin. If you need an example of a simple keyboard handler which traps CTRL-C as an input instead of a signal let me know and I'll post an example. – Anthill Sep 07 '11 at 16:18