entry_point.py
235 lines
| 8.4 KiB
| text/x-python
|
PythonLexer
epatters
|
r2778 | """ Defines helper functions for creating kernel entry points and process | ||
launchers. | ||||
""" | ||||
# Standard library imports. | ||||
Brian Granger
|
r2873 | import os | ||
epatters
|
r2778 | import socket | ||
epatters
|
r2944 | from subprocess import Popen, PIPE | ||
epatters
|
r2778 | import sys | ||
# System library imports. | ||||
import zmq | ||||
# Local imports. | ||||
Fernando Perez
|
r2853 | from IPython.core.ultratb import FormattedTB | ||
epatters
|
r2778 | from IPython.external.argparse import ArgumentParser | ||
Fernando Perez
|
r2853 | from IPython.utils import io | ||
epatters
|
r2778 | from exitpoller import ExitPollerUnix, ExitPollerWindows | ||
from displayhook import DisplayHook | ||||
from iostream import OutStream | ||||
from session import Session | ||||
Brian Granger
|
r2910 | from heartbeat import Heartbeat | ||
epatters
|
r2778 | |||
def bind_port(socket, ip, port): | ||||
""" Binds the specified ZMQ socket. If the port is zero, a random port is | ||||
chosen. Returns the port that was bound. | ||||
""" | ||||
connection = 'tcp://%s' % ip | ||||
if port <= 0: | ||||
port = socket.bind_to_random_port(connection) | ||||
else: | ||||
connection += ':%i' % port | ||||
socket.bind(connection) | ||||
return port | ||||
def make_argument_parser(): | ||||
""" Creates an ArgumentParser for the generic arguments supported by all | ||||
kernel entry points. | ||||
""" | ||||
parser = ArgumentParser() | ||||
parser.add_argument('--ip', type=str, default='127.0.0.1', | ||||
help='set the kernel\'s IP address [default: local]') | ||||
parser.add_argument('--xrep', type=int, metavar='PORT', default=0, | ||||
help='set the XREP channel port [default: random]') | ||||
parser.add_argument('--pub', type=int, metavar='PORT', default=0, | ||||
help='set the PUB channel port [default: random]') | ||||
parser.add_argument('--req', type=int, metavar='PORT', default=0, | ||||
help='set the REQ channel port [default: random]') | ||||
Brian Granger
|
r2910 | parser.add_argument('--hb', type=int, metavar='PORT', default=0, | ||
help='set the heartbeat port [default: random]') | ||||
epatters
|
r2778 | |||
if sys.platform == 'win32': | ||||
parser.add_argument('--parent', type=int, metavar='HANDLE', | ||||
default=0, help='kill this process if the process ' | ||||
'with HANDLE dies') | ||||
else: | ||||
parser.add_argument('--parent', action='store_true', | ||||
help='kill this process if its parent dies') | ||||
return parser | ||||
epatters
|
r2795 | def make_kernel(namespace, kernel_factory, | ||
out_stream_factory=None, display_hook_factory=None): | ||||
epatters
|
r2980 | """ Creates a kernel, redirects stdout/stderr, and installs a display hook | ||
and exception handler. | ||||
epatters
|
r2778 | """ | ||
epatters
|
r2980 | # If running under pythonw.exe, the interpreter will crash if more than 4KB | ||
# of data is written to stdout or stderr. This is a bug that has been with | ||||
# Python for a very long time; see http://bugs.python.org/issue706263. | ||||
if sys.executable.endswith('pythonw.exe'): | ||||
blackhole = file(os.devnull, 'w') | ||||
sys.stdout = sys.stderr = blackhole | ||||
sys.__stdout__ = sys.__stderr__ = blackhole | ||||
Fernando Perez
|
r2853 | # Install minimal exception handling | ||
epatters
|
r2888 | sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor', | ||
ostream=sys.__stdout__) | ||||
Fernando Perez
|
r2853 | |||
epatters
|
r2778 | # Create a context, a session, and the kernel sockets. | ||
Fernando Perez
|
r2912 | io.raw_print("Starting the kernel at pid:", os.getpid()) | ||
epatters
|
r2778 | context = zmq.Context() | ||
session = Session(username=u'kernel') | ||||
reply_socket = context.socket(zmq.XREP) | ||||
xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep) | ||||
Fernando Perez
|
r2875 | io.raw_print("XREP Channel on port", xrep_port) | ||
epatters
|
r2778 | |||
pub_socket = context.socket(zmq.PUB) | ||||
pub_port = bind_port(pub_socket, namespace.ip, namespace.pub) | ||||
Fernando Perez
|
r2875 | io.raw_print("PUB Channel on port", pub_port) | ||
epatters
|
r2778 | |||
req_socket = context.socket(zmq.XREQ) | ||||
req_port = bind_port(req_socket, namespace.ip, namespace.req) | ||||
Fernando Perez
|
r2875 | io.raw_print("REQ Channel on port", req_port) | ||
epatters
|
r2778 | |||
Brian Granger
|
r2910 | hb = Heartbeat(context, (namespace.ip, namespace.hb)) | ||
hb.start() | ||||
io.raw_print("Heartbeat REP Channel on port", hb.port) | ||||
epatters
|
r2778 | # Redirect input streams and set a display hook. | ||
epatters
|
r2795 | if out_stream_factory: | ||
sys.stdout = out_stream_factory(session, pub_socket, u'stdout') | ||||
sys.stderr = out_stream_factory(session, pub_socket, u'stderr') | ||||
if display_hook_factory: | ||||
sys.displayhook = display_hook_factory(session, pub_socket) | ||||
epatters
|
r2778 | |||
# Create the kernel. | ||||
return kernel_factory(session=session, reply_socket=reply_socket, | ||||
pub_socket=pub_socket, req_socket=req_socket) | ||||
def start_kernel(namespace, kernel): | ||||
""" Starts a kernel. | ||||
""" | ||||
# Configure this kernel/process to die on parent termination, if necessary. | ||||
if namespace.parent: | ||||
if sys.platform == 'win32': | ||||
poller = ExitPollerWindows(namespace.parent) | ||||
else: | ||||
poller = ExitPollerUnix() | ||||
poller.start() | ||||
# Start the kernel mainloop. | ||||
kernel.start() | ||||
def make_default_main(kernel_factory): | ||||
""" Creates the simplest possible kernel entry point. | ||||
""" | ||||
def main(): | ||||
namespace = make_argument_parser().parse_args() | ||||
epatters
|
r2795 | kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook) | ||
epatters
|
r2778 | start_kernel(namespace, kernel) | ||
return main | ||||
Brian Granger
|
r2910 | def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0, | ||
epatters
|
r2778 | independent=False, extra_arguments=[]): | ||
""" Launches a localhost kernel, binding to the specified ports. | ||||
Parameters | ||||
---------- | ||||
code : str, | ||||
A string of Python code that imports and executes a kernel entry point. | ||||
xrep_port : int, optional | ||||
The port to use for XREP channel. | ||||
pub_port : int, optional | ||||
The port to use for the SUB channel. | ||||
req_port : int, optional | ||||
The port to use for the REQ (raw input) channel. | ||||
Brian Granger
|
r2910 | hb_port : int, optional | ||
The port to use for the hearbeat REP channel. | ||||
epatters
|
r2778 | independent : bool, optional (default False) | ||
If set, the kernel process is guaranteed to survive if this process | ||||
dies. If not set, an effort is made to ensure that the kernel is killed | ||||
when this process dies. Note that in this case it is still good practice | ||||
to kill kernels manually before exiting. | ||||
extra_arguments = list, optional | ||||
A list of extra arguments to pass when executing the launch code. | ||||
Returns | ||||
------- | ||||
A tuple of form: | ||||
(kernel_process, xrep_port, pub_port, req_port) | ||||
where kernel_process is a Popen object and the ports are integers. | ||||
""" | ||||
# Find open ports as necessary. | ||||
ports = [] | ||||
Brian Granger
|
r2910 | ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \ | ||
int(req_port <= 0) + int(hb_port <= 0) | ||||
epatters
|
r2778 | for i in xrange(ports_needed): | ||
sock = socket.socket() | ||||
sock.bind(('', 0)) | ||||
ports.append(sock) | ||||
for i, sock in enumerate(ports): | ||||
port = sock.getsockname()[1] | ||||
sock.close() | ||||
ports[i] = port | ||||
if xrep_port <= 0: | ||||
xrep_port = ports.pop(0) | ||||
if pub_port <= 0: | ||||
pub_port = ports.pop(0) | ||||
if req_port <= 0: | ||||
req_port = ports.pop(0) | ||||
Brian Granger
|
r2910 | if hb_port <= 0: | ||
hb_port = ports.pop(0) | ||||
epatters
|
r2778 | |||
# Build the kernel launch command. | ||||
arguments = [ sys.executable, '-c', code, '--xrep', str(xrep_port), | ||||
Brian Granger
|
r2910 | '--pub', str(pub_port), '--req', str(req_port), | ||
'--hb', str(hb_port) ] | ||||
epatters
|
r2778 | arguments.extend(extra_arguments) | ||
# Spawn a kernel. | ||||
epatters
|
r2944 | if sys.platform == 'win32': | ||
epatters
|
r2980 | |||
epatters
|
r2944 | # If using pythonw, stdin, stdout, and stderr are invalid. Popen will | ||
# fail unless they are suitably redirected. We don't read from the | ||||
# pipes, but they must exist. | ||||
redirect = PIPE if sys.executable.endswith('pythonw.exe') else None | ||||
epatters
|
r2980 | |||
epatters
|
r2944 | if independent: | ||
proc = Popen(['start', '/b'] + arguments, shell=True, | ||||
stdout=redirect, stderr=redirect, stdin=redirect) | ||||
epatters
|
r2778 | else: | ||
from _subprocess import DuplicateHandle, GetCurrentProcess, \ | ||||
DUPLICATE_SAME_ACCESS | ||||
pid = GetCurrentProcess() | ||||
handle = DuplicateHandle(pid, pid, pid, 0, | ||||
epatters
|
r2913 | True, # Inheritable by new processes. | ||
epatters
|
r2778 | DUPLICATE_SAME_ACCESS) | ||
epatters
|
r2944 | proc = Popen(arguments + ['--parent', str(int(handle))], | ||
stdout=redirect, stderr=redirect, stdin=redirect) | ||||
epatters
|
r2980 | |||
# Clean up pipes created to work around Popen bug. | ||||
if redirect is not None: | ||||
proc.stdout.close() | ||||
proc.stderr.close() | ||||
proc.stdin.close() | ||||
epatters
|
r2944 | else: | ||
if independent: | ||||
proc = Popen(arguments, preexec_fn=lambda: os.setsid()) | ||||
epatters
|
r2778 | else: | ||
proc = Popen(arguments + ['--parent']) | ||||
Brian Granger
|
r2910 | return proc, xrep_port, pub_port, req_port, hb_port | ||