PageRenderTime 65ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/IPython/parallel/engine/kernelstarter.py

https://github.com/tinyclues/ipython
Python | 230 lines | 124 code | 30 blank | 76 comment | 37 complexity | c67289ec935f119dae89c9d22d7fa5f2 MD5 | raw file
  1. """KernelStarter class that intercepts Control Queue messages, and handles process management.
  2. Authors:
  3. * Min RK
  4. """
  5. #-----------------------------------------------------------------------------
  6. # Copyright (C) 2010-2011 The IPython Development Team
  7. #
  8. # Distributed under the terms of the BSD License. The full license is in
  9. # the file COPYING, distributed as part of this software.
  10. #-----------------------------------------------------------------------------
  11. from zmq.eventloop import ioloop
  12. from IPython.zmq.session import Session
  13. class KernelStarter(object):
  14. """Object for resetting/killing the Kernel."""
  15. def __init__(self, session, upstream, downstream, *kernel_args, **kernel_kwargs):
  16. self.session = session
  17. self.upstream = upstream
  18. self.downstream = downstream
  19. self.kernel_args = kernel_args
  20. self.kernel_kwargs = kernel_kwargs
  21. self.handlers = {}
  22. for method in 'shutdown_request shutdown_reply'.split():
  23. self.handlers[method] = getattr(self, method)
  24. def start(self):
  25. self.upstream.on_recv(self.dispatch_request)
  26. self.downstream.on_recv(self.dispatch_reply)
  27. #--------------------------------------------------------------------------
  28. # Dispatch methods
  29. #--------------------------------------------------------------------------
  30. def dispatch_request(self, raw_msg):
  31. idents, msg = self.session.feed_identities()
  32. try:
  33. msg = self.session.unserialize(msg, content=False)
  34. except:
  35. print ("bad msg: %s"%msg)
  36. msgtype = msg['header']['msg_type']
  37. handler = self.handlers.get(msgtype, None)
  38. if handler is None:
  39. self.downstream.send_multipart(raw_msg, copy=False)
  40. else:
  41. handler(msg)
  42. def dispatch_reply(self, raw_msg):
  43. idents, msg = self.session.feed_identities()
  44. try:
  45. msg = self.session.unserialize(msg, content=False)
  46. except:
  47. print ("bad msg: %s"%msg)
  48. msgtype = msg['header']['msg_type']
  49. handler = self.handlers.get(msgtype, None)
  50. if handler is None:
  51. self.upstream.send_multipart(raw_msg, copy=False)
  52. else:
  53. handler(msg)
  54. #--------------------------------------------------------------------------
  55. # Handlers
  56. #--------------------------------------------------------------------------
  57. def shutdown_request(self, msg):
  58. """"""
  59. self.downstream.send_multipart(msg)
  60. #--------------------------------------------------------------------------
  61. # Kernel process management methods, from KernelManager:
  62. #--------------------------------------------------------------------------
  63. def _check_local(addr):
  64. if isinstance(addr, tuple):
  65. addr = addr[0]
  66. return addr in LOCAL_IPS
  67. def start_kernel(self, **kw):
  68. """Starts a kernel process and configures the manager to use it.
  69. If random ports (port=0) are being used, this method must be called
  70. before the channels are created.
  71. Parameters:
  72. -----------
  73. ipython : bool, optional (default True)
  74. Whether to use an IPython kernel instead of a plain Python kernel.
  75. """
  76. self.kernel = Process(target=make_kernel, args=self.kernel_args,
  77. kwargs=self.kernel_kwargs)
  78. def shutdown_kernel(self, restart=False):
  79. """ Attempts to the stop the kernel process cleanly. If the kernel
  80. cannot be stopped, it is killed, if possible.
  81. """
  82. # FIXME: Shutdown does not work on Windows due to ZMQ errors!
  83. if sys.platform == 'win32':
  84. self.kill_kernel()
  85. return
  86. # Don't send any additional kernel kill messages immediately, to give
  87. # the kernel a chance to properly execute shutdown actions. Wait for at
  88. # most 1s, checking every 0.1s.
  89. self.xreq_channel.shutdown(restart=restart)
  90. for i in range(10):
  91. if self.is_alive:
  92. time.sleep(0.1)
  93. else:
  94. break
  95. else:
  96. # OK, we've waited long enough.
  97. if self.has_kernel:
  98. self.kill_kernel()
  99. def restart_kernel(self, now=False):
  100. """Restarts a kernel with the same arguments that were used to launch
  101. it. If the old kernel was launched with random ports, the same ports
  102. will be used for the new kernel.
  103. Parameters
  104. ----------
  105. now : bool, optional
  106. If True, the kernel is forcefully restarted *immediately*, without
  107. having a chance to do any cleanup action. Otherwise the kernel is
  108. given 1s to clean up before a forceful restart is issued.
  109. In all cases the kernel is restarted, the only difference is whether
  110. it is given a chance to perform a clean shutdown or not.
  111. """
  112. if self._launch_args is None:
  113. raise RuntimeError("Cannot restart the kernel. "
  114. "No previous call to 'start_kernel'.")
  115. else:
  116. if self.has_kernel:
  117. if now:
  118. self.kill_kernel()
  119. else:
  120. self.shutdown_kernel(restart=True)
  121. self.start_kernel(**self._launch_args)
  122. # FIXME: Messages get dropped in Windows due to probable ZMQ bug
  123. # unless there is some delay here.
  124. if sys.platform == 'win32':
  125. time.sleep(0.2)
  126. @property
  127. def has_kernel(self):
  128. """Returns whether a kernel process has been specified for the kernel
  129. manager.
  130. """
  131. return self.kernel is not None
  132. def kill_kernel(self):
  133. """ Kill the running kernel. """
  134. if self.has_kernel:
  135. # Pause the heart beat channel if it exists.
  136. if self._hb_channel is not None:
  137. self._hb_channel.pause()
  138. # Attempt to kill the kernel.
  139. try:
  140. self.kernel.kill()
  141. except OSError, e:
  142. # In Windows, we will get an Access Denied error if the process
  143. # has already terminated. Ignore it.
  144. if not (sys.platform == 'win32' and e.winerror == 5):
  145. raise
  146. self.kernel = None
  147. else:
  148. raise RuntimeError("Cannot kill kernel. No kernel is running!")
  149. def interrupt_kernel(self):
  150. """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
  151. well supported on all platforms.
  152. """
  153. if self.has_kernel:
  154. if sys.platform == 'win32':
  155. from parentpoller import ParentPollerWindows as Poller
  156. Poller.send_interrupt(self.kernel.win32_interrupt_event)
  157. else:
  158. self.kernel.send_signal(signal.SIGINT)
  159. else:
  160. raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
  161. def signal_kernel(self, signum):
  162. """ Sends a signal to the kernel. Note that since only SIGTERM is
  163. supported on Windows, this function is only useful on Unix systems.
  164. """
  165. if self.has_kernel:
  166. self.kernel.send_signal(signum)
  167. else:
  168. raise RuntimeError("Cannot signal kernel. No kernel is running!")
  169. @property
  170. def is_alive(self):
  171. """Is the kernel process still running?"""
  172. # FIXME: not using a heartbeat means this method is broken for any
  173. # remote kernel, it's only capable of handling local kernels.
  174. if self.has_kernel:
  175. if self.kernel.poll() is None:
  176. return True
  177. else:
  178. return False
  179. else:
  180. # We didn't start the kernel with this KernelManager so we don't
  181. # know if it is running. We should use a heartbeat for this case.
  182. return True
  183. def make_starter(up_addr, down_addr, *args, **kwargs):
  184. """entry point function for launching a kernelstarter in a subprocess"""
  185. loop = ioloop.IOLoop.instance()
  186. ctx = zmq.Context()
  187. session = Session()
  188. upstream = zmqstream.ZMQStream(ctx.socket(zmq.DEALER),loop)
  189. upstream.connect(up_addr)
  190. downstream = zmqstream.ZMQStream(ctx.socket(zmq.DEALER),loop)
  191. downstream.connect(down_addr)
  192. starter = KernelStarter(session, upstream, downstream, *args, **kwargs)
  193. starter.start()
  194. loop.start()