diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index 1df14a1..b1e947b 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -62,35 +62,35 @@ class IPythonWidget(FrontendWidget): if __name__ == '__main__': - from IPython.external.argparse import ArgumentParser - from IPython.frontend.qt.kernelmanager import QtKernelManager - - # Don't let Qt swallow KeyboardInterupts. import signal - signal.signal(signal.SIGINT, signal.SIG_DFL) + from IPython.frontend.qt.kernelmanager import QtKernelManager - # Parse command line arguments. - parser = ArgumentParser() - parser.add_argument('--ip', type=str, default='127.0.0.1', - help='set the kernel\'s IP address [default localhost]') - parser.add_argument('--xreq', type=int, metavar='PORT', default=5575, - help='set the XREQ Channel port [default %(default)i]') - parser.add_argument('--sub', type=int, metavar='PORT', default=5576, - help='set the SUB Channel port [default %(default)i]') - namespace = parser.parse_args() - - # Create KernelManager - ip = namespace.ip - kernel_manager = QtKernelManager(xreq_address = (ip, namespace.xreq), - sub_address = (ip, namespace.sub)) + # Create a KernelManager. + kernel_manager = QtKernelManager() + kernel_manager.start_kernel() kernel_manager.start_listening() - # Launch application + # Don't let Qt or ZMQ swallow KeyboardInterupts. + # FIXME: Gah, ZMQ swallows even custom signal handlers. So for now we leave + # behind a kernel process when Ctrl-C is pressed. + #def sigint_hook(signum, frame): + # QtGui.qApp.quit() + #signal.signal(signal.SIGINT, sigint_hook) + signal.signal(signal.SIGINT, signal.SIG_DFL) + + # Create the application, making sure to clean up nicely when we exit. app = QtGui.QApplication([]) + def quit_hook(): + kernel_manager.stop_listening() + kernel_manager.kill_kernel() + app.aboutToQuit.connect(quit_hook) + + # Launch the application. widget = IPythonWidget() widget.kernel_manager = kernel_manager widget.setWindowTitle('Python') widget.resize(640, 480) widget.show() app.exec_() + diff --git a/IPython/zmq/kernel.py b/IPython/zmq/kernel.py index 9298a6b..6b23f39 100755 --- a/IPython/zmq/kernel.py +++ b/IPython/zmq/kernel.py @@ -22,6 +22,7 @@ from code import CommandCompiler import zmq # Local imports. +from IPython.external.argparse import ArgumentParser from session import Session, Message, extract_header from completer import KernelCompleter @@ -282,23 +283,33 @@ def bind_port(socket, ip, port): socket.bind(connection) return port -def main(ip='127.0.0.1', rep_port=-1, pub_port=-1): - """ Start a kernel on 'ip' (default localhost) at the specified ports. If - ports are not specified, they are chosen at random. +def main(): + """ Main entry point for launching a kernel. """ + # Parse command line arguments. + 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=-1, + help='set the XREP Channel port [default: random]') + parser.add_argument('--pub', type=int, metavar='PORT', default=-1, + help='set the PUB Channel port [default: random]') + namespace = parser.parse_args() + + # Create context, session, and kernel sockets. print >>sys.__stdout__, "Starting the kernel..." - context = zmq.Context() session = Session(username=u'kernel') reply_socket = context.socket(zmq.XREP) - rep_port = bind_port(reply_socket, ip, rep_port) - print >>sys.__stdout__, "XREP Channel on port", rep_port + xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep) + print >>sys.__stdout__, "XREP Channel on port", xrep_port pub_socket = context.socket(zmq.PUB) - pub_port = bind_port(pub_socket, ip, pub_port) + pub_port = bind_port(pub_socket, namespace.ip, namespace.pub) print >>sys.__stdout__, "PUB Channel on port", pub_port + # Redirect input streams and set a display hook. sys.stdout = OutStream(session, pub_socket, u'stdout') sys.stderr = OutStream(session, pub_socket, u'stderr') sys.displayhook = DisplayHook(session, pub_socket) @@ -313,9 +324,9 @@ def main(ip='127.0.0.1', rep_port=-1, pub_port=-1): print >>sys.__stdout__, "Use Ctrl-\\ (NOT Ctrl-C!) to terminate." kernel.start() -def launch_kernel(): - """ Launches a kernel on this machine and binds its to channels to open - ports as it determined by the OS. +def launch_kernel(xrep_port=-1, pub_port=-1): + """ Launches a localhost kernel, binding to the specified ports. For any + port that is left unspecified, a port is chosen by the operating system. Returns a tuple of form: (kernel_process [Popen], rep_port [int], sub_port [int]) @@ -323,9 +334,10 @@ def launch_kernel(): import socket from subprocess import Popen - # Find some open ports. + # Find open ports as necessary. ports = [] - for i in xrange(2): + ports_needed = int(xrep_port < 0) + int(pub_port < 0) + for i in xrange(ports_needed): sock = socket.socket() sock.bind(('', 0)) ports.append(sock) @@ -333,17 +345,17 @@ def launch_kernel(): port = sock.getsockname()[1] sock.close() ports[i] = port - rep_port, sub_port = ports - + if xrep_port < 0: + xrep_port = ports.pop() + if pub_port < 0: + pub_port = ports.pop() + # Spawn a kernel. - command = 'from IPython.zmq.kernel import main;' \ - 'main(rep_port=%i, pub_port=%i)' - proc = Popen([sys.executable, '-c', command % (rep_port, sub_port)]) - - return proc, rep_port, sub_port - + command = 'from IPython.zmq.kernel import main; main()' + proc = Popen([ sys.executable, '-c', command, + '--xrep', str(xrep_port), '--pub', str(pub_port) ]) + return proc, xrep_port, pub_port + if __name__ == '__main__': - base_port = 5575 - main(rep_port = base_port, - pub_port = base_port + 1) + main() diff --git a/IPython/zmq/kernelmanager.py b/IPython/zmq/kernelmanager.py index baaebfa..bdc81e3 100644 --- a/IPython/zmq/kernelmanager.py +++ b/IPython/zmq/kernelmanager.py @@ -6,6 +6,7 @@ TODO: Create logger to handle debugging and console messages. # Standard library imports. from Queue import Queue, Empty +from subprocess import Popen from threading import Thread import time import traceback @@ -18,8 +19,12 @@ from zmq.eventloop import ioloop # Local imports. from IPython.utils.traitlets import HasTraits, Any, Bool, Int, Instance, Str, \ Type +from kernel import launch_kernel from session import Session +# Constants. +LOCALHOST = '127.0.0.1' + class MissingHandlerError(Exception): pass @@ -49,19 +54,22 @@ class ZmqSocketChannel(Thread): Thread.__init__(self) def get_address(self): - """ Get the channel's address. + """ Get the channel's address. By the default, a channel is on + localhost with no port specified (a negative port number). """ return self._address def set_adresss(self, address): """ Set the channel's address. Should be a tuple of form: - (ip address [str], port [int]) - or 'None' to indicate that no address has been specified. + (ip address [str], port [int]). + or None, in which case the address is reset to its default value. """ # FIXME: Validate address. if self.is_alive(): raise RuntimeError("Cannot set address on a running channel!") else: + if address is None: + address = (LOCALHOST, -1) self._address = address address = property(get_address, set_adresss) @@ -314,6 +322,7 @@ class KernelManager(HasTraits): rep_channel_class = Type(RepSocketChannel) # Protected traits. + _kernel = Instance(Popen) _sub_channel = Any _xreq_channel = Any _rep_channel = Any @@ -347,17 +356,26 @@ class KernelManager(HasTraits): self.rep_channel.stop() def start_kernel(self): - """Start a localhost kernel. If ports have been specified, use them. - Otherwise, choose an open port at random. + """Start a localhost kernel. If ports have been specified via the + address attributes, use them. Otherwise, choose open ports at random. """ - # TODO: start a kernel. - self.start_listening() + xreq, sub = self.xreq_address, self.sub_address + if xreq[0] != LOCALHOST or sub[0] != LOCALHOST: + raise RuntimeError("Can only launch a kernel on localhost." + "Make sure that the '*_address' attributes are " + "configured properly.") + + self._kernel, xrep, pub = launch_kernel(xrep_port=xreq[1], + pub_port=sub[1]) + self.xreq_address = (LOCALHOST, xrep) + self.sub_address = (LOCALHOST, pub) def kill_kernel(self): - """Kill the running kernel. + """Kill the running kernel, if there is one. """ - # TODO: kill the kernel. - self.stop_listening() + if self._kernel: + self._kernel.kill() + self._kernel = None @property def is_alive(self):