Pyzmq: Ctrl-C doesn't kill python on windows

Created on 14 Apr 2011  路  23Comments  路  Source: zeromq/pyzmq

Like the title says - python 2.7, pyzmq and zeromq are both 2.1.4.

This simple code https://gist.github.com/920133 doesn't die when you hit ctrl-c in the terminal.

Windows

Most helpful comment

Just an FYI to help you guys out. When ctrl+c doesn't work on Windows, you can use ctrl+break. That will shut the process down without having to close cmd or using the Task Manager.

All 23 comments

Can you write a small C program and verify that behavior is different to libzmq itself?

If not, I can do it, but I don't have convenient access to a Windows VM today.

Tried it in c and ctrl-c worked fine.

https://gist.github.com/920327

Okay, thanks. I'll look into it once I have some access to Windows.

I've done some exploring, and I'm not sure this isn't a _Python_ issue, rather than pyzmq.

I don't know of a way to get reliable signal handling in extension code. ctrl-C definitely works fine on everything _but_ Windows, and I'm not sure how the implementations differ.

I can definitely reproduce this (windows, ... pyzmq 2.1.7) It's rather annoying because (on windows) there is no way to stop processes using zmq except by killing them in the task manager...

Under win32 you may need to do some weird stuff to make Ctrl-C work. Here is how I used to do this in Xitami, ages ago:

SetConsoleCtrlHandler (control_handler, TRUE);
control_break = FALSE;
...

BOOL WINAPI
control_handler (DWORD control_type)
{
    switch (control_type)
    {
        case CTRL_BREAK_EVENT:
        case CTRL_C_EVENT:
            control_break = TRUE;
            return (TRUE);
    }
    return (FALSE);
}

Thanks, I'll see if I can't figure it out with this.

The confusing thing is that ctrl-c _does_ appear to work with a C program, but _not_ a Python program.

David Beazley's talk on Python's GIL explains very well why signal handling starts to fail the moment you call into C code, or use threads, or even carry out some Python operations that the interpreter considers atomic enough not to interrupt: http://www.dabeaz.com/python/GIL.pdf (see page 22 for the gory details).

This is definitely a Python issue.

I am familiar with Beazley's talk, but it does not seem to clarify why this is a Windows-specific issue. SIGINT works perfectly on posix. This issue requires _both_ Python and Windows - posix+Python interrupts fine, as does Windows+C. But somehow on Windows Python prevents the event from interrupting the libzmq call.

OK, I haven't used Windows in several years so I can't speak to this particular issue.

I can confirm that this issue still exists.

This is a serious issue. This basically makes pyzmq un-workable on windows..

If you are a Windows dev and know how to deal with the issue, a fix would be greatly appreciated. But I have neither the time nor relevant expertise to work it out, so it will not be fixed until someone steps up. There are also quite a few users who have been using pyzmq on Windows with this issue, so it is clearly not always made 'un-workable' by this, even if it is super annoying in certain cases.

I think this has something to do how Windows is handling I/O polling, because select has the same issue. This example reacts to a SIGINT just after the timeout has finished.

>>> import select
>>> import socket
>>> s = socket.socket()
>>> select.select([s], [], [], 10)  # pauses 10 seconds
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyboardInterrupt

Just an FYI to help you guys out. When ctrl+c doesn't work on Windows, you can use ctrl+break. That will shut the process down without having to close cmd or using the Task Manager.

Also, there's always the scorched earth solution for blocking scripts in Win32 taskkill /F /IM python.exe That is going to kill ALL instances of python.exe for your account ( or global if admin ).

@devdave I just heard that a killXYZ.bat seems to be quite a common thing on the Deskops of Windows developers. :-D

Nevertheless, I'd also like this bug to be fixed.

I just stumbled on the this issue while integrating Tornado and PyZMQ. I know that CherryPy uses pywin32 to shut down cleanly on CTRL-C and CTRL-break events.

If you look at the documentation for SetConsoleCtrlHandler(), as suggested by @hintjens, you see:

Each console process has its own list of application-defined HandlerRoutine functions that handle CTRL+C and CTRL+BREAK signals. The handler functions also handle signals generated by the system when the user closes the console, logs off, or shuts down the system. Initially, the handler list for each process contains only a default handler function that calls the ExitProcess function. A console process adds or removes additional handler functions by calling the SetConsoleCtrlHandler function, which does not affect the list of handler functions for other processes.

My guess is that Python already adds its own console control handler to translate CTRL-C events into KeyboardInterrupt exceptions in the main thread. The problem is that this handler is asynchronous -- it simply sets a flag to process when the interpreter runs next. However, since the interpreter is not running during a PyZMQ poll operation (the interpreter is blocked in a C function), this flag ends up never being checked.

There are two ways to check this:

  1. Use win32api.SetConsoleCtrlHandler() to register a console control handler that stops your poll operation and then returns 0, telling the system to continue executing handlers lower in the stack, inluding the Python handler that will raise the KeyboardInterrupt exception in the main thread.
  2. Give a timeout to the poll operation. In the worst case, you will wait until the poll operation completes before the KeyboardInterrupt exception is raised.

The touchy topic in the fix is that I'm not sure setting these handlers on each poll operation is desirable from a performance stand point (could be verified, but still). Ideally, the client program sets this only once at the start and stop of this program.

The other thing is that I'm not sure PyZMQ wants to introduce a dependency on pywin32.

If this is made optional (even on Windows), would you mind including it in the distribution?

Suppose I submit a pull request with the following:

  • a new handle_console_events context manager that registers the console control handler;
  • documentation explaining why, when, and how to use it;
  • resolves to a simple warning about not being able to trap CTRL-C and CTRL-BREAK events when on Windows _and_ pywin32 is not importable.

Would you include such a fix?

A dependency on pywin32 is definitely not acceptable. Plus, I'm not sure pyzmq can reliably make the right decision on behalf of all users. Perhaps the best that can be done is an _example_, registering the right handlers via pywin32.

You can easily do this with ctypes (in theory everything from pywin32 could be done with ctypes): https://gist.github.com/schlamar/7921ec587dd58a72a6d6.

Not sure how you want to stop polling though. The standard way is the "self pipe trick". Create a connected socketpair and listen always on one end. To exit polling you can just send data to the other end. But there might be a better solution for zmq.

Most interesting, if you run the example script in bash (on Windows), Ctrl+C works out of the box...

@minrk I wasn't suggeting that this be automatic. The optional built-in utility + example in the docs is what I had in mind.

@schlamar That's another reason you can't really do it automatically for everyone. In some cases, you probably want to have an extra inproc socket. However, I'm using the zmq.eventloop.ZMQIOLoop with Tornado, so I can simply use io_loop.stop().

Tell you what, I'll prepare a pull request and we can discuss and refine the implementation as feedback comes in.

An example would be great, thanks.

There it is. I'm waiting for feedback :-)

Forgot to add manual testing procedure. If you start the new example and press CTRL-C while in the display.py program, you should see a stack trace like this:

Traceback (most recent call last):
  File "display.py", line 45, in <module>
    main(sys.argv[1:])
  File "display.py", line 28, in main
    message = updates.recv_multipart()
  File "...\zmq\sugar\socket.py", line 299, in recv_multipart
    parts = [self.recv(flags, copy=copy, track=track)]
  File "socket.pyx", line 628, in zmq.backend.cython.socket.Socket.recv (zmq\backend\cython\socket.c:5616)
  File "socket.pyx", line 662, in zmq.backend.cython.socket.Socket.recv (zmq\backend\cython\socket.c:5436)
  File "socket.pyx", line 139, in zmq.backend.cython.socket._recv_copy (zmq\backend\cython\socket.c:1771)
  File "checkrc.pxd", line 11, in zmq.backend.cython.checkrc._check_rc (zmq\backend\cython\socket.c:5863)
KeyboardInterrupt

Without the new context manager, the process should simply hang until a new message arrives from a peer.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

badrobit picture badrobit  路  8Comments

d53dave picture d53dave  路  7Comments

mirceaulinic picture mirceaulinic  路  9Comments

f0t0n picture f0t0n  路  3Comments

kozo2 picture kozo2  路  7Comments