You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
4.1 KiB

4 years ago
  1. # Copyright (c) IPython Development Team.
  2. # Distributed under the terms of the Modified BSD License.
  3. try:
  4. import ctypes
  5. except:
  6. ctypes = None
  7. import os
  8. import platform
  9. import signal
  10. import time
  11. try:
  12. from _thread import interrupt_main # Py 3
  13. except ImportError:
  14. from thread import interrupt_main # Py 2
  15. from threading import Thread
  16. from traitlets.log import get_logger
  17. import warnings
  18. class ParentPollerUnix(Thread):
  19. """ A Unix-specific daemon thread that terminates the program immediately
  20. when the parent process no longer exists.
  21. """
  22. def __init__(self):
  23. super(ParentPollerUnix, self).__init__()
  24. self.daemon = True
  25. def run(self):
  26. # We cannot use os.waitpid because it works only for child processes.
  27. from errno import EINTR
  28. while True:
  29. try:
  30. if os.getppid() == 1:
  31. get_logger().warning("Parent appears to have exited, shutting down.")
  32. os._exit(1)
  33. time.sleep(1.0)
  34. except OSError as e:
  35. if e.errno == EINTR:
  36. continue
  37. raise
  38. class ParentPollerWindows(Thread):
  39. """ A Windows-specific daemon thread that listens for a special event that
  40. signals an interrupt and, optionally, terminates the program immediately
  41. when the parent process no longer exists.
  42. """
  43. def __init__(self, interrupt_handle=None, parent_handle=None):
  44. """ Create the poller. At least one of the optional parameters must be
  45. provided.
  46. Parameters
  47. ----------
  48. interrupt_handle : HANDLE (int), optional
  49. If provided, the program will generate a Ctrl+C event when this
  50. handle is signaled.
  51. parent_handle : HANDLE (int), optional
  52. If provided, the program will terminate immediately when this
  53. handle is signaled.
  54. """
  55. assert(interrupt_handle or parent_handle)
  56. super(ParentPollerWindows, self).__init__()
  57. if ctypes is None:
  58. raise ImportError("ParentPollerWindows requires ctypes")
  59. self.daemon = True
  60. self.interrupt_handle = interrupt_handle
  61. self.parent_handle = parent_handle
  62. def run(self):
  63. """ Run the poll loop. This method never returns.
  64. """
  65. try:
  66. from _winapi import WAIT_OBJECT_0, INFINITE
  67. except ImportError:
  68. from _subprocess import WAIT_OBJECT_0, INFINITE
  69. # Build the list of handle to listen on.
  70. handles = []
  71. if self.interrupt_handle:
  72. handles.append(self.interrupt_handle)
  73. if self.parent_handle:
  74. handles.append(self.parent_handle)
  75. arch = platform.architecture()[0]
  76. c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int
  77. # Listen forever.
  78. while True:
  79. result = ctypes.windll.kernel32.WaitForMultipleObjects(
  80. len(handles), # nCount
  81. (c_int * len(handles))(*handles), # lpHandles
  82. False, # bWaitAll
  83. INFINITE) # dwMilliseconds
  84. if WAIT_OBJECT_0 <= result < len(handles):
  85. handle = handles[result - WAIT_OBJECT_0]
  86. if handle == self.interrupt_handle:
  87. # check if signal handler is callable
  88. # to avoid 'int not callable' error (Python issue #23395)
  89. if callable(signal.getsignal(signal.SIGINT)):
  90. interrupt_main()
  91. elif handle == self.parent_handle:
  92. get_logger().warning("Parent appears to have exited, shutting down.")
  93. os._exit(1)
  94. elif result < 0:
  95. # wait failed, just give up and stop polling.
  96. warnings.warn("""Parent poll failed. If the frontend dies,
  97. the kernel may be left running. Please let us know
  98. about your system (bitness, Python, etc.) at
  99. ipython-dev@scipy.org""")
  100. return