On Windows, with Python 2.x, Ctrl-C generally does not interrupt socket calls.
In some cases, Ctrl-Break works. If it does, and if that's good enough for you, you're done.
But if Ctrl-Break doesn't work, or if that isn't acceptable as a workaround, the only option is to set your own console ctrl-key handler, with [SetConsoleControlHandler
](
https://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx).
There's a good discussion of this on the PyZMQ issue tracker, including a link to some sample code for working around it.
The code could be simpler if you can use the win32api
module from PyWin32
, but assuming you can't, I think this is the code you want:
from ctypes import WINFUNCTYPE, windll
from ctypes.wintypes import BOOL, DWORD
kernel32 = windll.LoadLibrary('kernel32')
PHANDLER_ROUTINE = WINFUNCTYPE(BOOL, DWORD)
SetConsoleCtrlHandler = kernel32.SetConsoleCtrlHandler
SetConsoleCtrlHandler.argtypes = (PHANDLER_ROUTINE, BOOL)
SetConsoleCtrlHandler.restype = BOOL
CTRL_C_EVENT = 0
CTRL_BREAK_EVENT = 1
@PHANDLER_ROUTINE
def console_handler(ctrl_type):
if ctrl_type in (CTRL_C_EVENT, CTRL_BREAK_EVENT):
# do something here
return True
return False
if __name__ == '__main__':
if not SetConsoleCtrlHandler(console_handler, True):
raise RuntimeError('SetConsoleCtrlHandler failed.')
The question is what to put in the # do something here
. If there were an easy answer, Python would already be doing it. :)
According to the docs, HandlerRoutine
functions actually run on a different thread. And, IIRC, closing the socket out from under the main thread will always cause its recv
to wake up and raise an exception. (Not the one you want, but still, something you can handle.)
However, I can't find documentation to prove that—it's definitely not recommended (and WinSock2 seems to officially allow the close
to fail with WSAEINPROGRESS
, even if no Microsoft implementation of WinSock2 actually does that…), but then you're just trying to bail out and quit here.
So, I believe just defining and installing the handler inside main
so you can write s.close()
as the # do something here
will work.
If you want to do this in a way that's guaranteed to be safe and effective, even with some weird WinSock2 implementation you've never heard of being, what you need to do is a bit more complicated. You need to make the recv
asynchronous, and use either Windows async I/O (which is very painful from Python 2.x, unless you use a heavy-duty library like Twisted), or write cross-platform select
-based code (which isn't very Windows-y, but works—as long as your use an extra socket
instead of the usual Unix solution of a pipe
). Like this (untested!) example:
# ctypes setup code from above
def main():
extrasock = socket.socket(socket.SOCK_DGRAM)
extrasock.bind(('127.0.0.1', 0))
extrasock.setblocking(False)
@PHANDLER_ROUTINE
def console_handler(ctrl_type):
if ctrl_type in (CTRL_C_EVENT, CTRL_BREAK_EVENT):
killsock = socket.socket(socket.SOCK_DGRAM)
killsock.sendto('DIE', extrasock.getsockname())
killsock.close()
return True
return False
if not SetConsoleCtrlHandler(console_handler, True):
raise RuntimeError('SetConsoleCtrlHandler failed.')
# your existing main code here, up to the try
# except that you need s.setblocking(False) too
try:
r, _, _ = select.select([s, extrasock], [], [])
if extrasock in r:
raise KeyboardInterrupt
if s in r:
readbuffer = readbuffer + s.recv(4096)
except KeyboardInterrupt:
# rest of your code here
If this makes no sense to you, the Sockets HOWTO actually has a pretty good explanation of how to use select
, and the pitfalls of using it on Windows.