123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- """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 "console 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
|