summaryrefslogtreecommitdiffstats
path: root/src/console/zmq/utils/win32.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/console/zmq/utils/win32.py')
-rwxr-xr-xsrc/console/zmq/utils/win32.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/console/zmq/utils/win32.py b/src/console/zmq/utils/win32.py
new file mode 100755
index 00000000..ea758299
--- /dev/null
+++ b/src/console/zmq/utils/win32.py
@@ -0,0 +1,132 @@
+"""Win32 compatibility utilities."""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) PyZMQ Developers
+# Distributed under the terms of the Modified BSD License.
+#-----------------------------------------------------------------------------
+
+import os
+
+# No-op implementation for other platforms.
+class _allow_interrupt(object):
+ """Utility for fixing CTRL-C events on Windows.
+
+ On Windows, the Python interpreter intercepts CTRL-C events in order to
+ translate them into ``KeyboardInterrupt`` exceptions. It (presumably)
+ does this by setting a flag in its "control control handler" and
+ checking it later at a convenient location in the interpreter.
+
+ However, when the Python interpreter is blocked waiting for the ZMQ
+ poll operation to complete, it must wait for ZMQ's ``select()``
+ operation to complete before translating the CTRL-C event into the
+ ``KeyboardInterrupt`` exception.
+
+ The only way to fix this seems to be to add our own "console control
+ handler" and perform some application-defined operation that will
+ unblock the ZMQ polling operation in order to force ZMQ to pass control
+ back to the Python interpreter.
+
+ This context manager performs all that Windows-y stuff, providing you
+ with a hook that is called when a CTRL-C event is intercepted. This
+ hook allows you to unblock your ZMQ poll operation immediately, which
+ will then result in the expected ``KeyboardInterrupt`` exception.
+
+ Without this context manager, your ZMQ-based application will not
+ respond normally to CTRL-C events on Windows. If a CTRL-C event occurs
+ while blocked on ZMQ socket polling, the translation to a
+ ``KeyboardInterrupt`` exception will be delayed until the I/O completes
+ and control returns to the Python interpreter (this may never happen if
+ you use an infinite timeout).
+
+ A no-op implementation is provided on non-Win32 systems to avoid the
+ application from having to conditionally use it.
+
+ Example usage:
+
+ .. sourcecode:: python
+
+ def stop_my_application():
+ # ...
+
+ with allow_interrupt(stop_my_application):
+ # main polling loop.
+
+ In a typical ZMQ application, you would use the "self pipe trick" to
+ send message to a ``PAIR`` socket in order to interrupt your blocking
+ socket polling operation.
+
+ In a Tornado event loop, you can use the ``IOLoop.stop`` method to
+ unblock your I/O loop.
+ """
+
+ def __init__(self, action=None):
+ """Translate ``action`` into a CTRL-C handler.
+
+ ``action`` is a callable that takes no arguments and returns no
+ value (returned value is ignored). It must *NEVER* raise an
+ exception.
+
+ If unspecified, a no-op will be used.
+ """
+ self._init_action(action)
+
+ def _init_action(self, action):
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ return
+
+if os.name == 'nt':
+ from ctypes import WINFUNCTYPE, windll
+ from ctypes.wintypes import BOOL, DWORD
+
+ kernel32 = windll.LoadLibrary('kernel32')
+
+ # <http://msdn.microsoft.com/en-us/library/ms686016.aspx>
+ PHANDLER_ROUTINE = WINFUNCTYPE(BOOL, DWORD)
+ SetConsoleCtrlHandler = kernel32.SetConsoleCtrlHandler
+ SetConsoleCtrlHandler.argtypes = (PHANDLER_ROUTINE, BOOL)
+ SetConsoleCtrlHandler.restype = BOOL
+
+ class allow_interrupt(_allow_interrupt):
+ __doc__ = _allow_interrupt.__doc__
+
+ def _init_action(self, action):
+ if action is None:
+ action = lambda: None
+ self.action = action
+ @PHANDLER_ROUTINE
+ def handle(event):
+ if event == 0: # CTRL_C_EVENT
+ action()
+ # Typical C implementations would return 1 to indicate that
+ # the event was processed and other control handlers in the
+ # stack should not be executed. However, that would
+ # prevent the Python interpreter's handler from translating
+ # CTRL-C to a `KeyboardInterrupt` exception, so we pretend
+ # that we didn't handle it.
+ return 0
+ self.handle = handle
+
+ def __enter__(self):
+ """Install the custom CTRL-C handler."""
+ result = SetConsoleCtrlHandler(self.handle, 1)
+ if result == 0:
+ # Have standard library automatically call `GetLastError()` and
+ # `FormatMessage()` into a nice exception object :-)
+ raise WindowsError()
+
+ def __exit__(self, *args):
+ """Remove the custom CTRL-C handler."""
+ result = SetConsoleCtrlHandler(self.handle, 0)
+ if result == 0:
+ # Have standard library automatically call `GetLastError()` and
+ # `FormatMessage()` into a nice exception object :-)
+ raise WindowsError()
+else:
+ class allow_interrupt(_allow_interrupt):
+ __doc__ = _allow_interrupt.__doc__
+ pass