Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Qt frontend shutdown behavior fixes and enhancements #164

Merged
11 commits merged into from

3 participants

@minrk
Owner

Ready for review.

) fixed error when resetting pykernel (it still reset, but printed an error)
2) fixed issue where closing a frontend, even secondary ones, always shutdown the kernel
3) shutdown_reply now goes out on the pub socket, so all clients are notified
3.a) this means that all clients can (and do) refresh the screen when a reset is called, just like the master frontend
4) kernel can stay alive after consoles are shutdown, and can be shutdown by any frontend at any point
4.a) this means that a shutdown request from any frontend can close all open frontends and the kernel, even if the kernel is detached, leaving no processes zombified.
4.b) 4.a required that a 'reset' element be added to shutdown_request/reply messages to identify the difference between a real shutdown message and stage 1 of a reset.

@fperez
Owner

Minor typo: http://github.com/ipython/ipython/pull/164#L0R373
reest -> reset

  • http://github.com/ipython/ipython/pull/164#L1R34
    Should document what 'existing' means. Object constructors are probably one of those places where we do want to be fairly explicit about documentation.

  • http://github.com/ipython/ipython/pull/164#L1R51
    leftover code?

  • The close console/close all should have keyboard accelerators ('c' and 'a' would be my choices, Esc is already the de-facto accelerator for Cancel).

  • The 'close console' doesn't seem to actually work in linux. I get:

Segmentation fault
dreamweaver[test]> Killed by parent poller!

And the kernel is completely gone. Did you test only on osx so far? This is on ubuntu 10.04, with zmq/pyzmq 2.0.8.

This addition to the messaging should be documented in the spec, so we don't fall out of sync. Currently the spec only says:

Message type: shutdown_request:

content = {
}

The restart field is fine, let's just make sure the spec document stays valid.

Summary:

most are minor nits easy to fix, but we need to figure out the linux crash before merging.

Thanks a lot, great work! I'll be available over the weekend for testing if you want.

@minrk
Owner

All issues addressed in intermediate commits except for the accelerators.

Sorry I hadn't tested on Linux - it turns out my strategy of closing just a Window was a bad practice, and only worked on OSX by coincidence. Much more official/supported approach used now that works on both.

As for the accelerators, I'll have to look this up, as I have no idea how to bind keys to buttons. I never looked at any Qt code before this week, so I'll have to do some searching, unless someone knows this one off the top of their head.

@fperez
Owner

Great job, I just merged and closed it, thanks!

I hope you'll be able to add the accelerators back, I made #166 to track this.

Great work, this is excellent functionality.

@ellisonbg
Owner
  • The choices in the close dialog are not very clear. Maybe change the text to something like "which would you like to close:" and then have choices of ('Cancel','Console Only','Kernel and Console').
  • How does this logic interplay with the --parent flag of ipkernel?
@minrk
Owner

It's not actually 'Kernel and Console', it's Kernel and all Consoles; that's why it's Close All. What do you think?

I didn't touch anything to do with the --parent flag. What should that change?

It's a hard thing to make clear, but I can have more explanation in the dialog box:

What would you like to close? Just this Console (leaving the Kernel running), or the kernel and all connected Consoles?

< Cancel | Just Console | Close Everything >

It really corresponds directly to the Close (cmd-W) vs Quit (cmd-Q) model of general applications. i.e. Close this Window (and leave everything else) vs. Quit this Application (and close/shutdown everything). It's certainly less clear with the whole kernel/interprocess model.

On OSX, this model is actually quite standard, and might not need any dialog - it's not normal at all for hitting the close button on a window to terminate an application (in fact, it's sufficiently uncommon for an application to do this, it should be considered a bad native UI). However, that's not the case on other platforms, and a general issue when building cross-platform apps, not at all specific to us.

@ellisonbg
Owner

Ahh, that is quite different, missed that. But then the current message is even more confusing. Does this really automatically close all frontends connected to that kernel? Do other frontends have any say so in the matter? In the future it is entirely possible that the qtconsole app could connect to multiple kernels (in tabs maybe). In that case, it really doesn't make sense to actually quit the other frontends. I guess I am thinking that it really should be:

Cancel | Console Only | Kernel and This Console

If ipkernel is started with the --parent flag, it starts a thread that watches for the parent pid. When it goes away, the thread kills the process. This is used to make sure the ipkernel dies if the parent goes away. Obviously, the "Close Console Only" option won't work when --parent is used.

It is much less clear, especially when other windows could be on other hosts.

@minrk
Owner

Kernel and This Console isn't useful because other Consoles can't do anything once the Kernel goes away.

'Close All' does close everything. With the current setup, if you close the Kernel, all the other frontends are useless anyway, so they should be closed. As it was before, if you closed the kernel (from anywhere), you would have a bunch of dangling frontends that can't do anything, unless the main parent decided to restart it. If you closed the master frontend, then you left possibly several Windows without the ability to ever do anything, and no information on what happened. If we change the frontend so that it's useful after the Kernel goes away, then the handling of a shutdown signal should be different, and that's easy to change. For now, though, I don't see any reason to keep Consoles open that have no ability to do anything.

The shutdown notice is handled by the qtconsole object, so changing that logic when new functionality comes in will be easy (it's in FrontendWidget._handle_shutdown_reply())
http://github.com/ipython/ipython/blob/master/IPython/frontend/qt/console/frontend_widget.py#L365

What you said about --parent is not true - it absolutely does work. The parent process is still alive, it's just not drawing a Console. That was the whole point of adding this - closing the initial console without losing the kernel.

@ellisonbg
Owner

Minimally if we want to keep the current logic, the other frontends should display a message with an "OK" button indicating that the kernel was shutdown. Having an app silently disappear would be quite confusing if you were not expecting it.

So how exactly do you keep the qt frontend that started the kernel alive but not drawing windows? Where is that code?

@minrk
Owner

I simply flip the switch that tells Qt to not automatically quit when the last Window is closed if Console-Only is chosen on the central frontend:
http://github.com/ipython/ipython/blob/master/IPython/frontend/qt/console/ipythonqt.py#L70

I can change it so there is a warning, and the user has the option to cancel the close. For instance, if they wanted to save (or print!) what they have. I don't think this is really necessary, at least on localhost - since there's no chance there that they don't know it's going down. Perhaps only display the message when the other Consoles are not connected via localhost?

Previously, there was no mechanism for other Consoles to even know that the Kernel was shutdown (only that the heartbeat failed). But now we can handle the shutdown however we want.

If I open 3 Consoles on one kernel locally, do you really think I should get at least 2 'OK' buttons, just to let me shut it down?

I think of it this way: If a user has multiple windows connected to a Kernel, there should be an easy way to close them all, and it should not involve more than one dialog, and certainly not one dialog per window.

But if you are sharing a Kernel remotely, I can definitely imagine a disappearing window being quite distressing.

@ellisonbg
Owner

I agree that the extra message/dialog is not needed on localhost, but I do think we want that on a remote frontend for saving/printing purposes.

@minrk
Owner

Okay, so what we want to change is:

1: more explicit dialog, but not change the behavior.

2: on notice of shutdown:
if someone else shutdown:
if I'm not connected via localhost:
prompt to close
else:
close

Is this correct?

@minrk
Owner

Do you want to review the new changes?

Now there is a growing number of cases:

Multiple windows on localhost: no prompts, all Console windows are equivalent - one resets, all reset; kernel shuts down, all close.

Windows not on localhost:
on shutdown: prompt to close
on reset: prompt to clear

Is this better?

@ellisonbg
Owner

Min,

I think this looks good. Fernando, how does this look to you?

Brian

@minrk
Owner

I just noticed another possible issue: Remote kernels have the ability to shutdown the kernel, and local consoles will be shutdown automatically without warning. This is because it's the connection to the kernel that gets checked for the warning, not the relationship with the sender (which is unknown).

The obvious solutions:
1) have the shutdown message identify the sender's location. This can actually be inferred if we use uuid.uuid1() for session IDs, because that includes a MAC address in uuid.node (last 12 bytes).
2) Prevent remote (non-localhost) Consoles from shutting down the Kernel - I'm not sure this is a bad idea, but it does restrict use cases. It is certainly the easiest answer.

@fperez

Perhaps instead of explicit False here, we could use

FrontendWidget._local_kernel

that way if the default ever changes, we don't have a mismatch because we forget to update the value in the init. It's less pretty, but it's the price we pay for our nicely organized class-level attributes.

Owner

Perhaps cleaner is:
if 'local_kernel' in kw:
self._local_kernel = kw['local_kernel']

That way we don't even set the variable if it's not in the args.

Owner

I've been thinking about this... When a variable isn't found in the instance dict, it triggers a second fallback lookup in the class dict, which is slower. So I think in general, we do want to apply to the instance all arguments specified declaratively in the class, to ensure that normal attribute lookups go in general through the instance and don't have to fall back. Thoughts?

Owner

Ah, I'm not nearly as familiar with the inner workings of objects. I was just thinking in terms of cleanliness of code.

I don't generally think of the class attributes as providing the defaults, so much as just facilitating introspection (>>> MyClass?) prior to instantiating an object, and a cleaner view of attribute names when writing code without digging through init logic.

Does self.attr go through the class every time, or just the first time it's requested?

Owner

That's the problem: if it's not set in the instance explicitly, it has to do a double lookup every single time.

Which is why I think with this pattern of class-based declarations (which I very much like) we should also follow up with setting things in the instance.

@fperez
Owner

The rest looks good.

As for the last point you raise, go with #2. Remote users already have a way to shut down the kernel if they want to: type exit(). So there's no need to have the remote console even attempt at shutting down the kernels, ever.

See, easy is right :)

@minrk
Owner

I worked out the accelerators.

Current status:
Close All is default (Enter)
Cancel is Esc
'Just Console' is the only one that's got non-standard behavior. I bound it to the native 'close' key-combo, which on OSX is cmd-W, since the difference between 'just this' and 'all' is the same as the difference between close and quit in regular apps.

But now that I've figured it out, all three can be anything we want.

@fperez
Owner

Merge branch 'minrk-keepkernel' into trunk

Improvements to the closing behavior of a Qt console regarding what
happens to the kernel and other consoles that may be active. Now,
there is fine control on who is closed and who remains active.

Closed by dc30bee (pull request).

@markvoorhies markvoorhies referenced this pull request from a commit in markvoorhies/ipython
@fperez fperez Merge branch 'minrk-keepkernel' into trunk
Improvements to the closing behavior of a Qt console regarding what
happens to the kernel and other consoles that may be active.  Now,
there is fine control on who is closed and who remains active.

Closes gh-164 (pull request).
dc30bee
@jtriley jtriley referenced this pull request from a commit in jtriley/ipython
@fperez fperez Merge branch 'keepkernel' of https://github.com/minrk/ipython into mi…
…nrk-keepkernel

Closes pull request gh-164.
f16f5cc
@fperez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@fperez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@damianavila damianavila referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@mattvonrocketstein mattvonrocketstein referenced this pull request from a commit in mattvonrocketstein/ipython
@fperez fperez Merge branch 'keepkernel' of https://github.com/minrk/ipython into mi…
…nrk-keepkernel

Closes pull request gh-164.
2aa8346
@mattvonrocketstein mattvonrocketstein referenced this pull request from a commit in mattvonrocketstein/ipython
@fperez fperez Merge branch 'minrk-keepkernel' into trunk
Improvements to the closing behavior of a Qt console regarding what
happens to the kernel and other consoles that may be active.  Now,
there is fine control on who is closed and who remains active.

Closes gh-164 (pull request).
169ecdb
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
38 IPython/frontend/qt/console/frontend_widget.py
@@ -101,6 +101,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
_CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
_ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
_input_splitter_class = InputSplitter
+ _local_kernel = False
#---------------------------------------------------------------------------
# 'object' interface
@@ -141,6 +142,9 @@ def __init__(self, *args, **kw):
# Connect signal handlers.
document = self._control.document()
document.contentsChange.connect(self._document_contents_change)
+
+ # set flag for whether we are connected via localhost
+ self._local_kernel = kw.get('local_kernel', FrontendWidget._local_kernel)
#---------------------------------------------------------------------------
# 'ConsoleWidget' public interface
@@ -366,14 +370,32 @@ def _handle_shutdown_reply(self, msg):
""" Handle shutdown signal, only if from other console.
"""
if not self._hidden and not self._is_from_this_session(msg):
- if not msg['content']['restart']:
- sys.exit(0)
- else:
- # we just got notified of a restart!
- time.sleep(0.25) # wait 1/4 sec to reset
- # lest the request for a new prompt
- # goes to the old kernel
- self.reset()
+ if self._local_kernel:
+ if not msg['content']['restart']:
+ sys.exit(0)
+ else:
+ # we just got notified of a restart!
+ time.sleep(0.25) # wait 1/4 sec to reset
+ # lest the request for a new prompt
+ # goes to the old kernel
+ self.reset()
+ else: # remote kernel, prompt on Kernel shutdown/reset
+ title = self.window().windowTitle()
+ if not msg['content']['restart']:
+ reply = QtGui.QMessageBox.question(self, title,
+ "Kernel has been shutdown permanently. Close the Console?",
+ QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ sys.exit(0)
+ else:
+ reply = QtGui.QMessageBox.question(self, title,
+ "Kernel has been reset. Clear the Console?",
+ QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ time.sleep(0.25) # wait 1/4 sec to reset
+ # lest the request for a new prompt
+ # goes to the old kernel
+ self.reset()
def _started_channels(self):
""" Called when the KernelManager channels have started listening or
View
97 IPython/frontend/qt/console/ipythonqt.py
@@ -16,10 +16,10 @@
from IPython.frontend.qt.kernelmanager import QtKernelManager
#-----------------------------------------------------------------------------
-# Constants
+# Network Constants
#-----------------------------------------------------------------------------
-LOCALHOST = '127.0.0.1'
+from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
#-----------------------------------------------------------------------------
# Classes
@@ -31,18 +31,24 @@ class MainWindow(QtGui.QMainWindow):
# 'object' interface
#---------------------------------------------------------------------------
- def __init__(self, app, frontend, existing=False):
+ def __init__(self, app, frontend, existing=False, may_close=True):
""" Create a MainWindow for the specified FrontendWidget.
The app is passed as an argument to allow for different
closing behavior depending on whether we are the Kernel's parent.
- If existing is True, then this Window does not own the Kernel.
+ If existing is True, then this Console does not own the Kernel.
+
+ If may_close is True, then this Console is permitted to close the kernel
"""
super(MainWindow, self).__init__()
self._app = app
self._frontend = frontend
self._existing = existing
+ if existing:
+ self._may_close = may_close
+ else:
+ self._may_close = True
self._frontend.exit_requested.connect(self.close)
self.setCentralWidget(frontend)
@@ -56,21 +62,47 @@ def closeEvent(self, event):
kernel_manager = self._frontend.kernel_manager
if kernel_manager and kernel_manager.channels_running:
title = self.window().windowTitle()
- reply = QtGui.QMessageBox.question(self, title,
- "Close just this console, or shutdown the kernel and close "+
- "all windows attached to it?",
- 'Cancel', 'Close Console', 'Close All')
- if reply == 2: # close All
- kernel_manager.shutdown_kernel()
- #kernel_manager.stop_channels()
- event.accept()
- elif reply == 1: # close Console
- if not self._existing:
- # I have the kernel: don't quit, just close the window
- self._app.setQuitOnLastWindowClosed(False)
- event.accept()
+ cancel = QtGui.QMessageBox.Cancel
+ okay = QtGui.QMessageBox.Ok
+ if self._may_close:
+ msg = "You are closing this Console window."
+ info = "Would you like to quit the Kernel and all attached Consoles as well?"
+ justthis = QtGui.QPushButton("&No, just this Console", self)
+ justthis.setShortcut('N')
+ closeall = QtGui.QPushButton("&Yes, quit everything", self)
+ closeall.setShortcut('Y')
+ box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
+ box.setInformativeText(info)
+ box.addButton(cancel)
+ box.addButton(justthis, QtGui.QMessageBox.NoRole)
+ box.addButton(closeall, QtGui.QMessageBox.YesRole)
+ box.setDefaultButton(closeall)
+ box.setEscapeButton(cancel)
+ reply = box.exec_()
+ if reply == 1: # close All
+ kernel_manager.shutdown_kernel()
+ #kernel_manager.stop_channels()
+ event.accept()
+ elif reply == 0: # close Console
+ if not self._existing:
+ # I have the kernel: don't quit, just close the window
+ self._app.setQuitOnLastWindowClosed(False)
+ self.deleteLater()
+ event.accept()
+ else:
+ event.ignore()
else:
- event.ignore()
+ reply = QtGui.QMessageBox.question(self, title,
+ "Are you sure you want to close this Console?"+
+ "\nThe Kernel and other Consoles will remain active.",
+ okay|cancel,
+ defaultButton=okay
+ )
+ if reply == okay:
+ event.accept()
+ else:
+ event.ignore()
+
#-----------------------------------------------------------------------------
# Main entry point
@@ -85,7 +117,11 @@ def main():
kgroup.add_argument('-e', '--existing', action='store_true',
help='connect to an existing kernel')
kgroup.add_argument('--ip', type=str, default=LOCALHOST,
- help='set the kernel\'s IP address [default localhost]')
+ help=\
+ "set the kernel\'s IP address [default localhost].\
+ If the IP address is something other than localhost, then \
+ Consoles on other machines will be able to connect\
+ to the Kernel, so be careful!")
kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
help='set the XREQ channel port [default random]')
kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
@@ -124,29 +160,34 @@ def main():
sub_address=(args.ip, args.sub),
rep_address=(args.ip, args.rep),
hb_address=(args.ip, args.hb))
- if args.ip == LOCALHOST and not args.existing:
+ if not args.existing:
+ # if not args.ip in LOCAL_IPS+ALL_ALIAS:
+ # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS)
+
+ kwargs = dict(ip=args.ip)
if args.pure:
- kernel_manager.start_kernel(ipython=False)
+ kwargs['ipython']=False
elif args.pylab:
- kernel_manager.start_kernel(pylab=args.pylab)
- else:
- kernel_manager.start_kernel()
+ kwargs['pylab']=args.pylab
+
+ kernel_manager.start_kernel(**kwargs)
kernel_manager.start_channels()
+ local_kernel = (not args.existing) or args.ip in LOCAL_IPS
# Create the widget.
app = QtGui.QApplication([])
if args.pure:
kind = 'rich' if args.rich else 'plain'
- widget = FrontendWidget(kind=kind, paging=args.paging)
+ widget = FrontendWidget(kind=kind, paging=args.paging, local_kernel=local_kernel)
elif args.rich or args.pylab:
- widget = RichIPythonWidget(paging=args.paging)
+ widget = RichIPythonWidget(paging=args.paging, local_kernel=local_kernel)
else:
- widget = IPythonWidget(paging=args.paging)
+ widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
widget.gui_completion = args.gui_completion
widget.kernel_manager = kernel_manager
# Create the main window.
- window = MainWindow(app, widget, args.existing)
+ window = MainWindow(app, widget, args.existing, may_close=local_kernel)
window.setWindowTitle('Python' if args.pure else 'IPython')
window.show()
View
40 IPython/utils/localinterfaces.py
@@ -0,0 +1,40 @@
+"""Simple utility for building a list of local IPs using the socket module.
+This module defines two constants:
+
+LOCALHOST : The loopback interface, or the first interface that points to this
+ machine. It will *almost* always be '127.0.0.1'
+
+LOCAL_IPS : A list of IP addresses, loopback first, that point to this machine.
+"""
+#-----------------------------------------------------------------------------
+# Copyright (C) 2010 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import socket
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+LOCAL_IPS = []
+try:
+ LOCAL_IPS = socket.gethostbyname_ex('localhost')[2]
+except socket.gaierror:
+ pass
+
+try:
+ LOCAL_IPS.extend(socket.gethostbyname_ex(socket.gethostname())[2])
+except socket.gaierror:
+ pass
+
+# include all-interface aliases: 0.0.0.0 and ''
+LOCAL_IPS.extend(['0.0.0.0', ''])
+
+LOCALHOST = LOCAL_IPS[0]
View
3  IPython/zmq/entry_point.py
@@ -16,6 +16,7 @@
from IPython.core.ultratb import FormattedTB
from IPython.external.argparse import ArgumentParser
from IPython.utils import io
+from IPython.utils.localinterfaces import LOCALHOST
from displayhook import DisplayHook
from heartbeat import Heartbeat
from iostream import OutStream
@@ -40,7 +41,7 @@ def make_argument_parser():
kernel entry points.
"""
parser = ArgumentParser()
- parser.add_argument('--ip', type=str, default='127.0.0.1',
+ parser.add_argument('--ip', type=str, default=LOCALHOST,
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]')
View
3  IPython/zmq/frontend.py
@@ -17,6 +17,7 @@
import zmq
import session
import completer
+from IPython.utils.localinterfaces import LOCALHOST
#-----------------------------------------------------------------------------
# Classes and functions
@@ -168,7 +169,7 @@ def interact(self):
def main():
# Defaults
#ip = '192.168.2.109'
- ip = '127.0.0.1'
+ ip = LOCALHOST
#ip = '99.146.222.252'
port_base = 5575
connection = ('tcp://%s' % ip) + ':%i'
View
4 IPython/zmq/heartbeat.py
@@ -17,6 +17,8 @@
import zmq
+from IPython.utils.localinterfaces import LOCALHOST
+
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------
@@ -25,7 +27,7 @@
class Heartbeat(Thread):
"A simple ping-pong style heartbeat that runs in a thread."
- def __init__(self, context, addr=('127.0.0.1', 0)):
+ def __init__(self, context, addr=(LOCALHOST, 0)):
Thread.__init__(self)
self.context = context
self.addr = addr
View
9 IPython/zmq/ipkernel.py
@@ -534,12 +534,15 @@ def start(self):
# Kernel main and launch functions
#-----------------------------------------------------------------------------
-def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
+def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
independent=False, pylab=False):
"""Launches a localhost kernel, binding to the specified ports.
Parameters
----------
+ ip : str, optional
+ The ip address the kernel will bind to.
+
xrep_port : int, optional
The port to use for XREP channel.
@@ -574,6 +577,10 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
extra_arguments.append('--pylab')
if isinstance(pylab, basestring):
extra_arguments.append(pylab)
+ if ip is not None:
+ extra_arguments.append('--ip')
+ if isinstance(ip, basestring):
+ extra_arguments.append(ip)
return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
xrep_port, pub_port, req_port, hb_port,
independent, extra_arguments)
View
25 IPython/zmq/kernelmanager.py
@@ -31,6 +31,7 @@
# Local imports.
from IPython.utils import io
+from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
from session import Session
@@ -38,8 +39,6 @@
# Constants and exceptions
#-----------------------------------------------------------------------------
-LOCALHOST = '127.0.0.1'
-
class InvalidPortNumber(Exception):
pass
@@ -724,24 +723,26 @@ def start_kernel(self, **kw):
"""
xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
self.rep_address, self.hb_address
- if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
- rep[0] != LOCALHOST or hb[0] != LOCALHOST:
- raise RuntimeError("Can only launch a kernel on localhost."
+ if xreq[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
+ rep[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
+ raise RuntimeError("Can only launch a kernel on a local interface. "
"Make sure that the '*_address' attributes are "
- "configured properly.")
-
+ "configured properly. "
+ "Currently valid addresses are: %s"%LOCAL_IPS
+ )
+
self._launch_args = kw.copy()
if kw.pop('ipython', True):
from ipkernel import launch_kernel
else:
from pykernel import launch_kernel
- self.kernel, xrep, pub, req, hb = launch_kernel(
+ self.kernel, xrep, pub, req, _hb = launch_kernel(
xrep_port=xreq[1], pub_port=sub[1],
req_port=rep[1], hb_port=hb[1], **kw)
- self.xreq_address = (LOCALHOST, xrep)
- self.sub_address = (LOCALHOST, pub)
- self.rep_address = (LOCALHOST, req)
- self.hb_address = (LOCALHOST, hb)
+ self.xreq_address = (xreq[0], xrep)
+ self.sub_address = (sub[0], pub)
+ self.rep_address = (rep[0], req)
+ self.hb_address = (hb[0], _hb)
def shutdown_kernel(self, restart=False):
""" Attempts to the stop the kernel process cleanly. If the kernel
View
13 IPython/zmq/pykernel.py
@@ -256,12 +256,15 @@ def _symbol_from_context(self, context):
# Kernel main and launch functions
#-----------------------------------------------------------------------------
-def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
+def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
independent=False):
""" Launches a localhost kernel, binding to the specified ports.
Parameters
----------
+ ip : str, optional
+ The ip address the kernel will bind to.
+
xrep_port : int, optional
The port to use for XREP channel.
@@ -286,9 +289,15 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
(kernel_process, xrep_port, pub_port, req_port)
where kernel_process is a Popen object and the ports are integers.
"""
+ extra_arguments = []
+ if ip is not None:
+ extra_arguments.append('--ip')
+ if isinstance(ip, basestring):
+ extra_arguments.append(ip)
+
return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
xrep_port, pub_port, req_port, hb_port,
- independent)
+ independent, extra_arguments=extra_arguments)
main = make_default_main(Kernel)
Something went wrong with that request. Please try again.