##// END OF EJS Templates
Kernel subprocess parent polling is now implemented for Windows.
epatters -
Show More
@@ -11,11 +11,16 b' Things to do:'
11 * Implement event loop and poll version.
11 * Implement event loop and poll version.
12 """
12 """
13
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
14 # Standard library imports.
18 # Standard library imports.
15 import __builtin__
19 import __builtin__
16 from code import CommandCompiler
20 from code import CommandCompiler
17 import os
21 import os
18 import sys
22 import sys
23 from threading import Thread
19 import time
24 import time
20 import traceback
25 import traceback
21
26
@@ -27,6 +32,9 b' from IPython.external.argparse import ArgumentParser'
27 from session import Session, Message, extract_header
32 from session import Session, Message, extract_header
28 from completer import KernelCompleter
33 from completer import KernelCompleter
29
34
35 #-----------------------------------------------------------------------------
36 # Kernel and stream classes
37 #-----------------------------------------------------------------------------
30
38
31 class InStream(object):
39 class InStream(object):
32 """ A file like object that reads from a 0MQ XREQ socket."""
40 """ A file like object that reads from a 0MQ XREQ socket."""
@@ -210,7 +218,6 b' class Kernel(object):'
210 self.history = []
218 self.history = []
211 self.compiler = CommandCompiler()
219 self.compiler = CommandCompiler()
212 self.completer = KernelCompleter(self.user_ns)
220 self.completer = KernelCompleter(self.user_ns)
213 self.poll_ppid = False
214
221
215 # Build dict of handlers for message types
222 # Build dict of handlers for message types
216 msg_types = [ 'execute_request', 'complete_request',
223 msg_types = [ 'execute_request', 'complete_request',
@@ -325,10 +332,6 b' class Kernel(object):'
325
332
326 def start(self):
333 def start(self):
327 while True:
334 while True:
328 if self.poll_ppid and os.getppid() == 1:
329 print>>sys.__stderr__, "KILLED KERNEL. No parent process."
330 os._exit(1)
331
332 ident = self.reply_socket.recv()
335 ident = self.reply_socket.recv()
333 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
336 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
334 msg = self.reply_socket.recv_json()
337 msg = self.reply_socket.recv_json()
@@ -341,6 +344,34 b' class Kernel(object):'
341 else:
344 else:
342 handler(ident, omsg)
345 handler(ident, omsg)
343
346
347 #-----------------------------------------------------------------------------
348 # Kernel main and launch functions
349 #-----------------------------------------------------------------------------
350
351 class UnixPoller(Thread):
352
353 def __init__(self):
354 super(UnixPoller, self).__init__()
355 self.daemon = True
356
357 def run(self):
358 while True:
359 if os.getppid() == 1:
360 os._exit(1)
361 time.sleep(5.0)
362
363 class WindowsPoller(Thread):
364
365 def __init__(self, handle):
366 super(WindowsPoller, self).__init__()
367 self.daemon = True
368 self.handle = handle
369
370 def run(self):
371 from _subprocess import WaitForSingleObject, WAIT_OBJECT_0, INFINITE
372 result = WaitForSingleObject(self.handle, INFINITE)
373 if result == WAIT_OBJECT_0:
374 os._exit(1)
344
375
345 def bind_port(socket, ip, port):
376 def bind_port(socket, ip, port):
346 """ Binds the specified ZMQ socket. If the port is less than zero, a random
377 """ Binds the specified ZMQ socket. If the port is less than zero, a random
@@ -367,8 +398,13 b' def main():'
367 help='set the PUB channel port [default: random]')
398 help='set the PUB channel port [default: random]')
368 parser.add_argument('--req', type=int, metavar='PORT', default=0,
399 parser.add_argument('--req', type=int, metavar='PORT', default=0,
369 help='set the REQ channel port [default: random]')
400 help='set the REQ channel port [default: random]')
370 parser.add_argument('--require-parent', action='store_true',
401 if sys.platform == 'win32':
371 help='ensure that this process dies with its parent')
402 parser.add_argument('--parent', type=int, metavar='HANDLE',
403 default=0, help='kill this process if the process '
404 'with HANDLE dies')
405 else:
406 parser.add_argument('--parent', action='store_true',
407 help='kill this process if its parent dies')
372 namespace = parser.parse_args()
408 namespace = parser.parse_args()
373
409
374 # Create a context, a session, and the kernel sockets.
410 # Create a context, a session, and the kernel sockets.
@@ -398,15 +434,18 b' def main():'
398 kernel = Kernel(session, reply_socket, pub_socket)
434 kernel = Kernel(session, reply_socket, pub_socket)
399
435
400 # Configure this kernel/process to die on parent termination, if necessary.
436 # Configure this kernel/process to die on parent termination, if necessary.
401 if namespace.require_parent:
437 if namespace.parent:
402 if sys.platform == 'linux2':
438 if sys.platform == 'linux2':
403 import ctypes, ctypes.util, signal
439 import ctypes, ctypes.util, signal
404 PR_SET_PDEATHSIG = 1
440 PR_SET_PDEATHSIG = 1
405 libc = ctypes.CDLL(ctypes.util.find_library('c'))
441 libc = ctypes.CDLL(ctypes.util.find_library('c'))
406 libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
442 libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
407
443 elif sys.platform == 'win32':
408 elif sys.platform != 'win32':
444 poller = WindowsPoller(namespace.parent)
409 kernel.poll_ppid = True
445 poller.start()
446 else:
447 poller = UnixPoller()
448 poller.start()
410
449
411 # Start the kernel mainloop.
450 # Start the kernel mainloop.
412 kernel.start()
451 kernel.start()
@@ -430,7 +469,7 b' def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):'
430 If set, the kernel process is guaranteed to survive if this process
469 If set, the kernel process is guaranteed to survive if this process
431 dies. If not set, an effort is made to ensure that the kernel is killed
470 dies. If not set, an effort is made to ensure that the kernel is killed
432 when this process dies. Note that in this case it is still good practice
471 when this process dies. Note that in this case it is still good practice
433 to attempt to kill kernels manually before exiting.
472 to kill kernels manually before exiting.
434
473
435 Returns
474 Returns
436 -------
475 -------
@@ -463,15 +502,22 b' def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):'
463 command = 'from IPython.zmq.kernel import main; main()'
502 command = 'from IPython.zmq.kernel import main; main()'
464 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
503 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
465 '--pub', str(pub_port), '--req', str(req_port) ]
504 '--pub', str(pub_port), '--req', str(req_port) ]
466
467 if independent:
505 if independent:
468 if sys.platform == 'win32':
506 if sys.platform == 'win32':
469 proc = Popen(['start', '/b'] + arguments, shell=True)
507 proc = Popen(['start', '/b'] + arguments, shell=True)
470 else:
508 else:
471 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
509 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
472
473 else:
510 else:
474 proc = Popen(arguments + ['--require-parent'])
511 if sys.platform == 'win32':
512 from _subprocess import DuplicateHandle, GetCurrentProcess, \
513 DUPLICATE_SAME_ACCESS
514 pid = GetCurrentProcess()
515 handle = DuplicateHandle(pid, pid, pid, 0,
516 True, # Inheritable by new processes.
517 DUPLICATE_SAME_ACCESS)
518 proc = Popen(arguments + ['--parent', str(int(handle))])
519 else:
520 proc = Popen(arguments + ['--parent'])
475
521
476 return proc, xrep_port, pub_port, req_port
522 return proc, xrep_port, pub_port, req_port
477
523
General Comments 0
You need to be logged in to leave comments. Login now