##// END OF EJS Templates
Possible fix for GH-169
MinRK -
Show More
@@ -1,178 +1,182 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Imports
5 # Imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # Systemm library imports
8 # Systemm library imports
9 from PyQt4 import QtGui
9 from PyQt4 import QtGui
10
10
11 # Local imports
11 # Local imports
12 from IPython.external.argparse import ArgumentParser
12 from IPython.external.argparse import ArgumentParser
13 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
13 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
14 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
14 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
15 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
15 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
16 from IPython.frontend.qt.kernelmanager import QtKernelManager
16 from IPython.frontend.qt.kernelmanager import QtKernelManager
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Constants
19 # Network Constants
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 LOCALHOST = '127.0.0.1'
22 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Classes
25 # Classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 class MainWindow(QtGui.QMainWindow):
28 class MainWindow(QtGui.QMainWindow):
29
29
30 #---------------------------------------------------------------------------
30 #---------------------------------------------------------------------------
31 # 'object' interface
31 # 'object' interface
32 #---------------------------------------------------------------------------
32 #---------------------------------------------------------------------------
33
33
34 def __init__(self, app, frontend, existing=False, may_close=True):
34 def __init__(self, app, frontend, existing=False, may_close=True):
35 """ Create a MainWindow for the specified FrontendWidget.
35 """ Create a MainWindow for the specified FrontendWidget.
36
36
37 The app is passed as an argument to allow for different
37 The app is passed as an argument to allow for different
38 closing behavior depending on whether we are the Kernel's parent.
38 closing behavior depending on whether we are the Kernel's parent.
39
39
40 If existing is True, then this Console does not own the Kernel.
40 If existing is True, then this Console does not own the Kernel.
41
41
42 If may_close is True, then this Console is permitted to close the kernel
42 If may_close is True, then this Console is permitted to close the kernel
43 """
43 """
44 super(MainWindow, self).__init__()
44 super(MainWindow, self).__init__()
45 self._app = app
45 self._app = app
46 self._frontend = frontend
46 self._frontend = frontend
47 self._existing = existing
47 self._existing = existing
48 if not existing:
48 if existing:
49 self._may_close = may_close
49 self._may_close = may_close
50 else:
50 else:
51 self._may_close = True
51 self._may_close = True
52 self._frontend.exit_requested.connect(self.close)
52 self._frontend.exit_requested.connect(self.close)
53 self.setCentralWidget(frontend)
53 self.setCentralWidget(frontend)
54
54
55 #---------------------------------------------------------------------------
55 #---------------------------------------------------------------------------
56 # QWidget interface
56 # QWidget interface
57 #---------------------------------------------------------------------------
57 #---------------------------------------------------------------------------
58
58
59 def closeEvent(self, event):
59 def closeEvent(self, event):
60 """ Reimplemented to prompt the user and close the kernel cleanly.
60 """ Reimplemented to prompt the user and close the kernel cleanly.
61 """
61 """
62 kernel_manager = self._frontend.kernel_manager
62 kernel_manager = self._frontend.kernel_manager
63 if kernel_manager and kernel_manager.channels_running:
63 if kernel_manager and kernel_manager.channels_running:
64 title = self.window().windowTitle()
64 title = self.window().windowTitle()
65 if self._may_close:
65 if self._may_close:
66 reply = QtGui.QMessageBox.question(self, title,
66 reply = QtGui.QMessageBox.question(self, title,
67 "You are closing this Console window."+
67 "You are closing this Console window."+
68 "\nWould you like to quit the Kernel and all attached Consoles as well?",
68 "\nWould you like to quit the Kernel and all attached Consoles as well?",
69 'Cancel', 'No, just this Console', 'Yes, quit everything')
69 'Cancel', 'No, just this Console', 'Yes, quit everything')
70 if reply == 2: # close All
70 if reply == 2: # close All
71 kernel_manager.shutdown_kernel()
71 kernel_manager.shutdown_kernel()
72 #kernel_manager.stop_channels()
72 #kernel_manager.stop_channels()
73 event.accept()
73 event.accept()
74 elif reply == 1: # close Console
74 elif reply == 1: # close Console
75 if not self._existing:
75 if not self._existing:
76 # I have the kernel: don't quit, just close the window
76 # I have the kernel: don't quit, just close the window
77 self._app.setQuitOnLastWindowClosed(False)
77 self._app.setQuitOnLastWindowClosed(False)
78 self.deleteLater()
78 self.deleteLater()
79 event.accept()
79 event.accept()
80 else:
80 else:
81 event.ignore()
81 event.ignore()
82 else:
82 else:
83 reply = QtGui.QMessageBox.question(self, title,
83 reply = QtGui.QMessageBox.question(self, title,
84 "Are you sure you want to close this Console?\n"+
84 "Are you sure you want to close this Console?\n"+
85 "The Kernel and other Consoles will remain active.",
85 "The Kernel and other Consoles will remain active.",
86 QtGui.QMessageBox.Yes, QtGui.QMessageBox.No
86 QtGui.QMessageBox.Yes, QtGui.QMessageBox.No
87 )
87 )
88 if reply == QtGui.QMessageBox.Yes:
88 if reply == QtGui.QMessageBox.Yes:
89 event.accept()
89 event.accept()
90 else:
90 else:
91 event.ignore()
91 event.ignore()
92
92
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # Main entry point
95 # Main entry point
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97
97
98 def main():
98 def main():
99 """ Entry point for application.
99 """ Entry point for application.
100 """
100 """
101 # Parse command line arguments.
101 # Parse command line arguments.
102 parser = ArgumentParser()
102 parser = ArgumentParser()
103 kgroup = parser.add_argument_group('kernel options')
103 kgroup = parser.add_argument_group('kernel options')
104 kgroup.add_argument('-e', '--existing', action='store_true',
104 kgroup.add_argument('-e', '--existing', action='store_true',
105 help='connect to an existing kernel')
105 help='connect to an existing kernel')
106 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
106 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
107 help='set the kernel\'s IP address [default localhost]')
107 help='set the kernel\'s IP address [default localhost]')
108 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
108 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
109 help='set the XREQ channel port [default random]')
109 help='set the XREQ channel port [default random]')
110 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
110 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
111 help='set the SUB channel port [default random]')
111 help='set the SUB channel port [default random]')
112 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
112 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
113 help='set the REP channel port [default random]')
113 help='set the REP channel port [default random]')
114 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
114 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
115 help='set the heartbeat port [default: random]')
115 help='set the heartbeat port [default: random]')
116
116
117 egroup = kgroup.add_mutually_exclusive_group()
117 egroup = kgroup.add_mutually_exclusive_group()
118 egroup.add_argument('--pure', action='store_true', help = \
118 egroup.add_argument('--pure', action='store_true', help = \
119 'use a pure Python kernel instead of an IPython kernel')
119 'use a pure Python kernel instead of an IPython kernel')
120 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
120 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
121 const='auto', help = \
121 const='auto', help = \
122 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
122 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
123 given, the GUI backend is matplotlib's, otherwise use one of: \
123 given, the GUI backend is matplotlib's, otherwise use one of: \
124 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
124 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
125
125
126 wgroup = parser.add_argument_group('widget options')
126 wgroup = parser.add_argument_group('widget options')
127 wgroup.add_argument('--paging', type=str, default='inside',
127 wgroup.add_argument('--paging', type=str, default='inside',
128 choices = ['inside', 'hsplit', 'vsplit', 'none'],
128 choices = ['inside', 'hsplit', 'vsplit', 'none'],
129 help='set the paging style [default inside]')
129 help='set the paging style [default inside]')
130 wgroup.add_argument('--rich', action='store_true',
130 wgroup.add_argument('--rich', action='store_true',
131 help='enable rich text support')
131 help='enable rich text support')
132 wgroup.add_argument('--gui-completion', action='store_true',
132 wgroup.add_argument('--gui-completion', action='store_true',
133 help='use a GUI widget for tab completion')
133 help='use a GUI widget for tab completion')
134
134
135 args = parser.parse_args()
135 args = parser.parse_args()
136
136
137 # Don't let Qt or ZMQ swallow KeyboardInterupts.
137 # Don't let Qt or ZMQ swallow KeyboardInterupts.
138 import signal
138 import signal
139 signal.signal(signal.SIGINT, signal.SIG_DFL)
139 signal.signal(signal.SIGINT, signal.SIG_DFL)
140
140
141 # Create a KernelManager and start a kernel.
141 # Create a KernelManager and start a kernel.
142 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
142 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
143 sub_address=(args.ip, args.sub),
143 sub_address=(args.ip, args.sub),
144 rep_address=(args.ip, args.rep),
144 rep_address=(args.ip, args.rep),
145 hb_address=(args.ip, args.hb))
145 hb_address=(args.ip, args.hb))
146 if not args.existing:
146 if not args.existing:
147 # if not args.ip in LOCAL_IPS+ALL_ALIAS:
148 # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS)
149
150 kwargs = dict(ip=args.ip)
147 if args.pure:
151 if args.pure:
148 kernel_manager.start_kernel(ipython=False)
152 kwargs['ipython']=False
149 elif args.pylab:
153 elif args.pylab:
150 kernel_manager.start_kernel(pylab=args.pylab)
154 kwargs['pylab']=args.pylab
151 else:
155
152 kernel_manager.start_kernel()
156 kernel_manager.start_kernel(**kwargs)
153 kernel_manager.start_channels()
157 kernel_manager.start_channels()
154
158
155 local_kernel = (not args.existing) or args.ip == LOCALHOST
159 local_kernel = (not args.existing) or args.ip == LOCALHOST
156 # Create the widget.
160 # Create the widget.
157 app = QtGui.QApplication([])
161 app = QtGui.QApplication([])
158 if args.pure:
162 if args.pure:
159 kind = 'rich' if args.rich else 'plain'
163 kind = 'rich' if args.rich else 'plain'
160 widget = FrontendWidget(kind=kind, paging=args.paging, local_kernel=local_kernel)
164 widget = FrontendWidget(kind=kind, paging=args.paging, local_kernel=local_kernel)
161 elif args.rich or args.pylab:
165 elif args.rich or args.pylab:
162 widget = RichIPythonWidget(paging=args.paging, local_kernel=local_kernel)
166 widget = RichIPythonWidget(paging=args.paging, local_kernel=local_kernel)
163 else:
167 else:
164 widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
168 widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
165 widget.gui_completion = args.gui_completion
169 widget.gui_completion = args.gui_completion
166 widget.kernel_manager = kernel_manager
170 widget.kernel_manager = kernel_manager
167
171
168 # Create the main window.
172 # Create the main window.
169 window = MainWindow(app, widget, args.existing, may_close=local_kernel)
173 window = MainWindow(app, widget, args.existing, may_close=local_kernel)
170 window.setWindowTitle('Python' if args.pure else 'IPython')
174 window.setWindowTitle('Python' if args.pure else 'IPython')
171 window.show()
175 window.show()
172
176
173 # Start the application main loop.
177 # Start the application main loop.
174 app.exec_()
178 app.exec_()
175
179
176
180
177 if __name__ == '__main__':
181 if __name__ == '__main__':
178 main()
182 main()
@@ -1,259 +1,260 b''
1 """ Defines helper functions for creating kernel entry points and process
1 """ Defines helper functions for creating kernel entry points and process
2 launchers.
2 launchers.
3 """
3 """
4
4
5 # Standard library imports.
5 # Standard library imports.
6 import atexit
6 import atexit
7 import os
7 import os
8 import socket
8 import socket
9 from subprocess import Popen, PIPE
9 from subprocess import Popen, PIPE
10 import sys
10 import sys
11
11
12 # System library imports.
12 # System library imports.
13 import zmq
13 import zmq
14
14
15 # Local imports.
15 # Local imports.
16 from IPython.core.ultratb import FormattedTB
16 from IPython.core.ultratb import FormattedTB
17 from IPython.external.argparse import ArgumentParser
17 from IPython.external.argparse import ArgumentParser
18 from IPython.utils import io
18 from IPython.utils import io
19 from IPython.utils.localinterfaces import LOCALHOST
19 from displayhook import DisplayHook
20 from displayhook import DisplayHook
20 from heartbeat import Heartbeat
21 from heartbeat import Heartbeat
21 from iostream import OutStream
22 from iostream import OutStream
22 from parentpoller import ParentPollerUnix, ParentPollerWindows
23 from parentpoller import ParentPollerUnix, ParentPollerWindows
23 from session import Session
24 from session import Session
24
25
25 def bind_port(socket, ip, port):
26 def bind_port(socket, ip, port):
26 """ Binds the specified ZMQ socket. If the port is zero, a random port is
27 """ Binds the specified ZMQ socket. If the port is zero, a random port is
27 chosen. Returns the port that was bound.
28 chosen. Returns the port that was bound.
28 """
29 """
29 connection = 'tcp://%s' % ip
30 connection = 'tcp://%s' % ip
30 if port <= 0:
31 if port <= 0:
31 port = socket.bind_to_random_port(connection)
32 port = socket.bind_to_random_port(connection)
32 else:
33 else:
33 connection += ':%i' % port
34 connection += ':%i' % port
34 socket.bind(connection)
35 socket.bind(connection)
35 return port
36 return port
36
37
37
38
38 def make_argument_parser():
39 def make_argument_parser():
39 """ Creates an ArgumentParser for the generic arguments supported by all
40 """ Creates an ArgumentParser for the generic arguments supported by all
40 kernel entry points.
41 kernel entry points.
41 """
42 """
42 parser = ArgumentParser()
43 parser = ArgumentParser()
43 parser.add_argument('--ip', type=str, default='127.0.0.1',
44 parser.add_argument('--ip', type=str, default=LOCALHOST,
44 help='set the kernel\'s IP address [default: local]')
45 help='set the kernel\'s IP address [default: local]')
45 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
46 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
46 help='set the XREP channel port [default: random]')
47 help='set the XREP channel port [default: random]')
47 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
48 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
48 help='set the PUB channel port [default: random]')
49 help='set the PUB channel port [default: random]')
49 parser.add_argument('--req', type=int, metavar='PORT', default=0,
50 parser.add_argument('--req', type=int, metavar='PORT', default=0,
50 help='set the REQ channel port [default: random]')
51 help='set the REQ channel port [default: random]')
51 parser.add_argument('--hb', type=int, metavar='PORT', default=0,
52 parser.add_argument('--hb', type=int, metavar='PORT', default=0,
52 help='set the heartbeat port [default: random]')
53 help='set the heartbeat port [default: random]')
53
54
54 if sys.platform == 'win32':
55 if sys.platform == 'win32':
55 parser.add_argument('--interrupt', type=int, metavar='HANDLE',
56 parser.add_argument('--interrupt', type=int, metavar='HANDLE',
56 default=0, help='interrupt this process when '
57 default=0, help='interrupt this process when '
57 'HANDLE is signaled')
58 'HANDLE is signaled')
58 parser.add_argument('--parent', type=int, metavar='HANDLE',
59 parser.add_argument('--parent', type=int, metavar='HANDLE',
59 default=0, help='kill this process if the process '
60 default=0, help='kill this process if the process '
60 'with HANDLE dies')
61 'with HANDLE dies')
61 else:
62 else:
62 parser.add_argument('--parent', action='store_true',
63 parser.add_argument('--parent', action='store_true',
63 help='kill this process if its parent dies')
64 help='kill this process if its parent dies')
64
65
65 return parser
66 return parser
66
67
67
68
68 def make_kernel(namespace, kernel_factory,
69 def make_kernel(namespace, kernel_factory,
69 out_stream_factory=None, display_hook_factory=None):
70 out_stream_factory=None, display_hook_factory=None):
70 """ Creates a kernel, redirects stdout/stderr, and installs a display hook
71 """ Creates a kernel, redirects stdout/stderr, and installs a display hook
71 and exception handler.
72 and exception handler.
72 """
73 """
73 # If running under pythonw.exe, the interpreter will crash if more than 4KB
74 # If running under pythonw.exe, the interpreter will crash if more than 4KB
74 # of data is written to stdout or stderr. This is a bug that has been with
75 # of data is written to stdout or stderr. This is a bug that has been with
75 # Python for a very long time; see http://bugs.python.org/issue706263.
76 # Python for a very long time; see http://bugs.python.org/issue706263.
76 if sys.executable.endswith('pythonw.exe'):
77 if sys.executable.endswith('pythonw.exe'):
77 blackhole = file(os.devnull, 'w')
78 blackhole = file(os.devnull, 'w')
78 sys.stdout = sys.stderr = blackhole
79 sys.stdout = sys.stderr = blackhole
79 sys.__stdout__ = sys.__stderr__ = blackhole
80 sys.__stdout__ = sys.__stderr__ = blackhole
80
81
81 # Install minimal exception handling
82 # Install minimal exception handling
82 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
83 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
83 ostream=sys.__stdout__)
84 ostream=sys.__stdout__)
84
85
85 # Create a context, a session, and the kernel sockets.
86 # Create a context, a session, and the kernel sockets.
86 io.raw_print("Starting the kernel at pid:", os.getpid())
87 io.raw_print("Starting the kernel at pid:", os.getpid())
87 context = zmq.Context()
88 context = zmq.Context()
88 # Uncomment this to try closing the context.
89 # Uncomment this to try closing the context.
89 # atexit.register(context.close)
90 # atexit.register(context.close)
90 session = Session(username=u'kernel')
91 session = Session(username=u'kernel')
91
92
92 reply_socket = context.socket(zmq.XREP)
93 reply_socket = context.socket(zmq.XREP)
93 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
94 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
94 io.raw_print("XREP Channel on port", xrep_port)
95 io.raw_print("XREP Channel on port", xrep_port)
95
96
96 pub_socket = context.socket(zmq.PUB)
97 pub_socket = context.socket(zmq.PUB)
97 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
98 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
98 io.raw_print("PUB Channel on port", pub_port)
99 io.raw_print("PUB Channel on port", pub_port)
99
100
100 req_socket = context.socket(zmq.XREQ)
101 req_socket = context.socket(zmq.XREQ)
101 req_port = bind_port(req_socket, namespace.ip, namespace.req)
102 req_port = bind_port(req_socket, namespace.ip, namespace.req)
102 io.raw_print("REQ Channel on port", req_port)
103 io.raw_print("REQ Channel on port", req_port)
103
104
104 hb = Heartbeat(context, (namespace.ip, namespace.hb))
105 hb = Heartbeat(context, (namespace.ip, namespace.hb))
105 hb.start()
106 hb.start()
106 hb_port = hb.port
107 hb_port = hb.port
107 io.raw_print("Heartbeat REP Channel on port", hb_port)
108 io.raw_print("Heartbeat REP Channel on port", hb_port)
108
109
109 # Helper to make it easier to connect to an existing kernel, until we have
110 # Helper to make it easier to connect to an existing kernel, until we have
110 # single-port connection negotiation fully implemented.
111 # single-port connection negotiation fully implemented.
111 io.raw_print("To connect another client to this kernel, use:")
112 io.raw_print("To connect another client to this kernel, use:")
112 io.raw_print("-e --xreq {0} --sub {1} --rep {2} --hb {3}".format(
113 io.raw_print("-e --xreq {0} --sub {1} --rep {2} --hb {3}".format(
113 xrep_port, pub_port, req_port, hb_port))
114 xrep_port, pub_port, req_port, hb_port))
114
115
115 # Redirect input streams and set a display hook.
116 # Redirect input streams and set a display hook.
116 if out_stream_factory:
117 if out_stream_factory:
117 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
118 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
118 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
119 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
119 if display_hook_factory:
120 if display_hook_factory:
120 sys.displayhook = display_hook_factory(session, pub_socket)
121 sys.displayhook = display_hook_factory(session, pub_socket)
121
122
122 # Create the kernel.
123 # Create the kernel.
123 kernel = kernel_factory(session=session, reply_socket=reply_socket,
124 kernel = kernel_factory(session=session, reply_socket=reply_socket,
124 pub_socket=pub_socket, req_socket=req_socket)
125 pub_socket=pub_socket, req_socket=req_socket)
125 kernel.record_ports(xrep_port=xrep_port, pub_port=pub_port,
126 kernel.record_ports(xrep_port=xrep_port, pub_port=pub_port,
126 req_port=req_port, hb_port=hb_port)
127 req_port=req_port, hb_port=hb_port)
127 return kernel
128 return kernel
128
129
129
130
130 def start_kernel(namespace, kernel):
131 def start_kernel(namespace, kernel):
131 """ Starts a kernel.
132 """ Starts a kernel.
132 """
133 """
133 # Configure this kernel process to poll the parent process, if necessary.
134 # Configure this kernel process to poll the parent process, if necessary.
134 if sys.platform == 'win32':
135 if sys.platform == 'win32':
135 if namespace.interrupt or namespace.parent:
136 if namespace.interrupt or namespace.parent:
136 poller = ParentPollerWindows(namespace.interrupt, namespace.parent)
137 poller = ParentPollerWindows(namespace.interrupt, namespace.parent)
137 poller.start()
138 poller.start()
138 elif namespace.parent:
139 elif namespace.parent:
139 poller = ParentPollerUnix()
140 poller = ParentPollerUnix()
140 poller.start()
141 poller.start()
141
142
142 # Start the kernel mainloop.
143 # Start the kernel mainloop.
143 kernel.start()
144 kernel.start()
144
145
145
146
146 def make_default_main(kernel_factory):
147 def make_default_main(kernel_factory):
147 """ Creates the simplest possible kernel entry point.
148 """ Creates the simplest possible kernel entry point.
148 """
149 """
149 def main():
150 def main():
150 namespace = make_argument_parser().parse_args()
151 namespace = make_argument_parser().parse_args()
151 kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
152 kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
152 start_kernel(namespace, kernel)
153 start_kernel(namespace, kernel)
153 return main
154 return main
154
155
155
156
156 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
157 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
157 independent=False, extra_arguments=[]):
158 independent=False, extra_arguments=[]):
158 """ Launches a localhost kernel, binding to the specified ports.
159 """ Launches a localhost kernel, binding to the specified ports.
159
160
160 Parameters
161 Parameters
161 ----------
162 ----------
162 code : str,
163 code : str,
163 A string of Python code that imports and executes a kernel entry point.
164 A string of Python code that imports and executes a kernel entry point.
164
165
165 xrep_port : int, optional
166 xrep_port : int, optional
166 The port to use for XREP channel.
167 The port to use for XREP channel.
167
168
168 pub_port : int, optional
169 pub_port : int, optional
169 The port to use for the SUB channel.
170 The port to use for the SUB channel.
170
171
171 req_port : int, optional
172 req_port : int, optional
172 The port to use for the REQ (raw input) channel.
173 The port to use for the REQ (raw input) channel.
173
174
174 hb_port : int, optional
175 hb_port : int, optional
175 The port to use for the hearbeat REP channel.
176 The port to use for the hearbeat REP channel.
176
177
177 independent : bool, optional (default False)
178 independent : bool, optional (default False)
178 If set, the kernel process is guaranteed to survive if this process
179 If set, the kernel process is guaranteed to survive if this process
179 dies. If not set, an effort is made to ensure that the kernel is killed
180 dies. If not set, an effort is made to ensure that the kernel is killed
180 when this process dies. Note that in this case it is still good practice
181 when this process dies. Note that in this case it is still good practice
181 to kill kernels manually before exiting.
182 to kill kernels manually before exiting.
182
183
183 extra_arguments = list, optional
184 extra_arguments = list, optional
184 A list of extra arguments to pass when executing the launch code.
185 A list of extra arguments to pass when executing the launch code.
185
186
186 Returns
187 Returns
187 -------
188 -------
188 A tuple of form:
189 A tuple of form:
189 (kernel_process, xrep_port, pub_port, req_port)
190 (kernel_process, xrep_port, pub_port, req_port)
190 where kernel_process is a Popen object and the ports are integers.
191 where kernel_process is a Popen object and the ports are integers.
191 """
192 """
192 # Find open ports as necessary.
193 # Find open ports as necessary.
193 ports = []
194 ports = []
194 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \
195 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \
195 int(req_port <= 0) + int(hb_port <= 0)
196 int(req_port <= 0) + int(hb_port <= 0)
196 for i in xrange(ports_needed):
197 for i in xrange(ports_needed):
197 sock = socket.socket()
198 sock = socket.socket()
198 sock.bind(('', 0))
199 sock.bind(('', 0))
199 ports.append(sock)
200 ports.append(sock)
200 for i, sock in enumerate(ports):
201 for i, sock in enumerate(ports):
201 port = sock.getsockname()[1]
202 port = sock.getsockname()[1]
202 sock.close()
203 sock.close()
203 ports[i] = port
204 ports[i] = port
204 if xrep_port <= 0:
205 if xrep_port <= 0:
205 xrep_port = ports.pop(0)
206 xrep_port = ports.pop(0)
206 if pub_port <= 0:
207 if pub_port <= 0:
207 pub_port = ports.pop(0)
208 pub_port = ports.pop(0)
208 if req_port <= 0:
209 if req_port <= 0:
209 req_port = ports.pop(0)
210 req_port = ports.pop(0)
210 if hb_port <= 0:
211 if hb_port <= 0:
211 hb_port = ports.pop(0)
212 hb_port = ports.pop(0)
212
213
213 # Build the kernel launch command.
214 # Build the kernel launch command.
214 arguments = [ sys.executable, '-c', code, '--xrep', str(xrep_port),
215 arguments = [ sys.executable, '-c', code, '--xrep', str(xrep_port),
215 '--pub', str(pub_port), '--req', str(req_port),
216 '--pub', str(pub_port), '--req', str(req_port),
216 '--hb', str(hb_port) ]
217 '--hb', str(hb_port) ]
217 arguments.extend(extra_arguments)
218 arguments.extend(extra_arguments)
218
219
219 # Spawn a kernel.
220 # Spawn a kernel.
220 if sys.platform == 'win32':
221 if sys.platform == 'win32':
221 # Create a Win32 event for interrupting the kernel.
222 # Create a Win32 event for interrupting the kernel.
222 interrupt_event = ParentPollerWindows.create_interrupt_event()
223 interrupt_event = ParentPollerWindows.create_interrupt_event()
223 arguments += [ '--interrupt', str(int(interrupt_event)) ]
224 arguments += [ '--interrupt', str(int(interrupt_event)) ]
224
225
225 # If using pythonw, stdin, stdout, and stderr are invalid. Popen will
226 # If using pythonw, stdin, stdout, and stderr are invalid. Popen will
226 # fail unless they are suitably redirected. We don't read from the
227 # fail unless they are suitably redirected. We don't read from the
227 # pipes, but they must exist.
228 # pipes, but they must exist.
228 redirect = PIPE if sys.executable.endswith('pythonw.exe') else None
229 redirect = PIPE if sys.executable.endswith('pythonw.exe') else None
229
230
230 if independent:
231 if independent:
231 proc = Popen(arguments,
232 proc = Popen(arguments,
232 creationflags=512, # CREATE_NEW_PROCESS_GROUP
233 creationflags=512, # CREATE_NEW_PROCESS_GROUP
233 stdout=redirect, stderr=redirect, stdin=redirect)
234 stdout=redirect, stderr=redirect, stdin=redirect)
234 else:
235 else:
235 from _subprocess import DuplicateHandle, GetCurrentProcess, \
236 from _subprocess import DuplicateHandle, GetCurrentProcess, \
236 DUPLICATE_SAME_ACCESS
237 DUPLICATE_SAME_ACCESS
237 pid = GetCurrentProcess()
238 pid = GetCurrentProcess()
238 handle = DuplicateHandle(pid, pid, pid, 0,
239 handle = DuplicateHandle(pid, pid, pid, 0,
239 True, # Inheritable by new processes.
240 True, # Inheritable by new processes.
240 DUPLICATE_SAME_ACCESS)
241 DUPLICATE_SAME_ACCESS)
241 proc = Popen(arguments + ['--parent', str(int(handle))],
242 proc = Popen(arguments + ['--parent', str(int(handle))],
242 stdout=redirect, stderr=redirect, stdin=redirect)
243 stdout=redirect, stderr=redirect, stdin=redirect)
243
244
244 # Attach the interrupt event to the Popen objet so it can be used later.
245 # Attach the interrupt event to the Popen objet so it can be used later.
245 proc.win32_interrupt_event = interrupt_event
246 proc.win32_interrupt_event = interrupt_event
246
247
247 # Clean up pipes created to work around Popen bug.
248 # Clean up pipes created to work around Popen bug.
248 if redirect is not None:
249 if redirect is not None:
249 proc.stdout.close()
250 proc.stdout.close()
250 proc.stderr.close()
251 proc.stderr.close()
251 proc.stdin.close()
252 proc.stdin.close()
252
253
253 else:
254 else:
254 if independent:
255 if independent:
255 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
256 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
256 else:
257 else:
257 proc = Popen(arguments + ['--parent'])
258 proc = Popen(arguments + ['--parent'])
258
259
259 return proc, xrep_port, pub_port, req_port, hb_port
260 return proc, xrep_port, pub_port, req_port, hb_port
@@ -1,194 +1,195 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive frontend that talks to a kernel over 0MQ.
2 """A simple interactive frontend that talks to a kernel over 0MQ.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Imports
6 # Imports
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # stdlib
8 # stdlib
9 import cPickle as pickle
9 import cPickle as pickle
10 import code
10 import code
11 import readline
11 import readline
12 import sys
12 import sys
13 import time
13 import time
14 import uuid
14 import uuid
15
15
16 # our own
16 # our own
17 import zmq
17 import zmq
18 import session
18 import session
19 import completer
19 import completer
20 from IPython.utils.localinterfaces import LOCALHOST
20
21
21 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
22 # Classes and functions
23 # Classes and functions
23 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
24
25
25 class Console(code.InteractiveConsole):
26 class Console(code.InteractiveConsole):
26
27
27 def __init__(self, locals=None, filename="<console>",
28 def __init__(self, locals=None, filename="<console>",
28 session = session,
29 session = session,
29 request_socket=None,
30 request_socket=None,
30 sub_socket=None):
31 sub_socket=None):
31 code.InteractiveConsole.__init__(self, locals, filename)
32 code.InteractiveConsole.__init__(self, locals, filename)
32 self.session = session
33 self.session = session
33 self.request_socket = request_socket
34 self.request_socket = request_socket
34 self.sub_socket = sub_socket
35 self.sub_socket = sub_socket
35 self.backgrounded = 0
36 self.backgrounded = 0
36 self.messages = {}
37 self.messages = {}
37
38
38 # Set tab completion
39 # Set tab completion
39 self.completer = completer.ClientCompleter(self, session, request_socket)
40 self.completer = completer.ClientCompleter(self, session, request_socket)
40 readline.parse_and_bind('tab: complete')
41 readline.parse_and_bind('tab: complete')
41 readline.parse_and_bind('set show-all-if-ambiguous on')
42 readline.parse_and_bind('set show-all-if-ambiguous on')
42 readline.set_completer(self.completer.complete)
43 readline.set_completer(self.completer.complete)
43
44
44 # Set system prompts
45 # Set system prompts
45 sys.ps1 = 'Py>>> '
46 sys.ps1 = 'Py>>> '
46 sys.ps2 = ' ... '
47 sys.ps2 = ' ... '
47 sys.ps3 = 'Out : '
48 sys.ps3 = 'Out : '
48 # Build dict of handlers for message types
49 # Build dict of handlers for message types
49 self.handlers = {}
50 self.handlers = {}
50 for msg_type in ['pyin', 'pyout', 'pyerr', 'stream']:
51 for msg_type in ['pyin', 'pyout', 'pyerr', 'stream']:
51 self.handlers[msg_type] = getattr(self, 'handle_%s' % msg_type)
52 self.handlers[msg_type] = getattr(self, 'handle_%s' % msg_type)
52
53
53 def handle_pyin(self, omsg):
54 def handle_pyin(self, omsg):
54 if omsg.parent_header.session == self.session.session:
55 if omsg.parent_header.session == self.session.session:
55 return
56 return
56 c = omsg.content.code.rstrip()
57 c = omsg.content.code.rstrip()
57 if c:
58 if c:
58 print '[IN from %s]' % omsg.parent_header.username
59 print '[IN from %s]' % omsg.parent_header.username
59 print c
60 print c
60
61
61 def handle_pyout(self, omsg):
62 def handle_pyout(self, omsg):
62 #print omsg # dbg
63 #print omsg # dbg
63 if omsg.parent_header.session == self.session.session:
64 if omsg.parent_header.session == self.session.session:
64 print "%s%s" % (sys.ps3, omsg.content.data)
65 print "%s%s" % (sys.ps3, omsg.content.data)
65 else:
66 else:
66 print '[Out from %s]' % omsg.parent_header.username
67 print '[Out from %s]' % omsg.parent_header.username
67 print omsg.content.data
68 print omsg.content.data
68
69
69 def print_pyerr(self, err):
70 def print_pyerr(self, err):
70 print >> sys.stderr, err.etype,':', err.evalue
71 print >> sys.stderr, err.etype,':', err.evalue
71 print >> sys.stderr, ''.join(err.traceback)
72 print >> sys.stderr, ''.join(err.traceback)
72
73
73 def handle_pyerr(self, omsg):
74 def handle_pyerr(self, omsg):
74 if omsg.parent_header.session == self.session.session:
75 if omsg.parent_header.session == self.session.session:
75 return
76 return
76 print >> sys.stderr, '[ERR from %s]' % omsg.parent_header.username
77 print >> sys.stderr, '[ERR from %s]' % omsg.parent_header.username
77 self.print_pyerr(omsg.content)
78 self.print_pyerr(omsg.content)
78
79
79 def handle_stream(self, omsg):
80 def handle_stream(self, omsg):
80 if omsg.content.name == 'stdout':
81 if omsg.content.name == 'stdout':
81 outstream = sys.stdout
82 outstream = sys.stdout
82 else:
83 else:
83 outstream = sys.stderr
84 outstream = sys.stderr
84 print >> outstream, '*ERR*',
85 print >> outstream, '*ERR*',
85 print >> outstream, omsg.content.data,
86 print >> outstream, omsg.content.data,
86
87
87 def handle_output(self, omsg):
88 def handle_output(self, omsg):
88 handler = self.handlers.get(omsg.msg_type, None)
89 handler = self.handlers.get(omsg.msg_type, None)
89 if handler is not None:
90 if handler is not None:
90 handler(omsg)
91 handler(omsg)
91
92
92 def recv_output(self):
93 def recv_output(self):
93 while True:
94 while True:
94 omsg = self.session.recv(self.sub_socket)
95 omsg = self.session.recv(self.sub_socket)
95 if omsg is None:
96 if omsg is None:
96 break
97 break
97 self.handle_output(omsg)
98 self.handle_output(omsg)
98
99
99 def handle_reply(self, rep):
100 def handle_reply(self, rep):
100 # Handle any side effects on output channels
101 # Handle any side effects on output channels
101 self.recv_output()
102 self.recv_output()
102 # Now, dispatch on the possible reply types we must handle
103 # Now, dispatch on the possible reply types we must handle
103 if rep is None:
104 if rep is None:
104 return
105 return
105 if rep.content.status == 'error':
106 if rep.content.status == 'error':
106 self.print_pyerr(rep.content)
107 self.print_pyerr(rep.content)
107 elif rep.content.status == 'aborted':
108 elif rep.content.status == 'aborted':
108 print >> sys.stderr, "ERROR: ABORTED"
109 print >> sys.stderr, "ERROR: ABORTED"
109 ab = self.messages[rep.parent_header.msg_id].content
110 ab = self.messages[rep.parent_header.msg_id].content
110 if 'code' in ab:
111 if 'code' in ab:
111 print >> sys.stderr, ab.code
112 print >> sys.stderr, ab.code
112 else:
113 else:
113 print >> sys.stderr, ab
114 print >> sys.stderr, ab
114
115
115 def recv_reply(self):
116 def recv_reply(self):
116 rep = self.session.recv(self.request_socket)
117 rep = self.session.recv(self.request_socket)
117 self.handle_reply(rep)
118 self.handle_reply(rep)
118 return rep
119 return rep
119
120
120 def runcode(self, code):
121 def runcode(self, code):
121 # We can't pickle code objects, so fetch the actual source
122 # We can't pickle code objects, so fetch the actual source
122 src = '\n'.join(self.buffer)
123 src = '\n'.join(self.buffer)
123
124
124 # for non-background inputs, if we do have previoiusly backgrounded
125 # for non-background inputs, if we do have previoiusly backgrounded
125 # jobs, check to see if they've produced results
126 # jobs, check to see if they've produced results
126 if not src.endswith(';'):
127 if not src.endswith(';'):
127 while self.backgrounded > 0:
128 while self.backgrounded > 0:
128 #print 'checking background'
129 #print 'checking background'
129 rep = self.recv_reply()
130 rep = self.recv_reply()
130 if rep:
131 if rep:
131 self.backgrounded -= 1
132 self.backgrounded -= 1
132 time.sleep(0.05)
133 time.sleep(0.05)
133
134
134 # Send code execution message to kernel
135 # Send code execution message to kernel
135 omsg = self.session.send(self.request_socket,
136 omsg = self.session.send(self.request_socket,
136 'execute_request', dict(code=src))
137 'execute_request', dict(code=src))
137 self.messages[omsg.header.msg_id] = omsg
138 self.messages[omsg.header.msg_id] = omsg
138
139
139 # Fake asynchronicity by letting the user put ';' at the end of the line
140 # Fake asynchronicity by letting the user put ';' at the end of the line
140 if src.endswith(';'):
141 if src.endswith(';'):
141 self.backgrounded += 1
142 self.backgrounded += 1
142 return
143 return
143
144
144 # For foreground jobs, wait for reply
145 # For foreground jobs, wait for reply
145 while True:
146 while True:
146 rep = self.recv_reply()
147 rep = self.recv_reply()
147 if rep is not None:
148 if rep is not None:
148 break
149 break
149 self.recv_output()
150 self.recv_output()
150 time.sleep(0.05)
151 time.sleep(0.05)
151 else:
152 else:
152 # We exited without hearing back from the kernel!
153 # We exited without hearing back from the kernel!
153 print >> sys.stderr, 'ERROR!!! kernel never got back to us!!!'
154 print >> sys.stderr, 'ERROR!!! kernel never got back to us!!!'
154
155
155
156
156 class InteractiveClient(object):
157 class InteractiveClient(object):
157 def __init__(self, session, request_socket, sub_socket):
158 def __init__(self, session, request_socket, sub_socket):
158 self.session = session
159 self.session = session
159 self.request_socket = request_socket
160 self.request_socket = request_socket
160 self.sub_socket = sub_socket
161 self.sub_socket = sub_socket
161 self.console = Console(None, '<zmq-console>',
162 self.console = Console(None, '<zmq-console>',
162 session, request_socket, sub_socket)
163 session, request_socket, sub_socket)
163
164
164 def interact(self):
165 def interact(self):
165 self.console.interact()
166 self.console.interact()
166
167
167
168
168 def main():
169 def main():
169 # Defaults
170 # Defaults
170 #ip = '192.168.2.109'
171 #ip = '192.168.2.109'
171 ip = '127.0.0.1'
172 ip = LOCALHOST
172 #ip = '99.146.222.252'
173 #ip = '99.146.222.252'
173 port_base = 5575
174 port_base = 5575
174 connection = ('tcp://%s' % ip) + ':%i'
175 connection = ('tcp://%s' % ip) + ':%i'
175 req_conn = connection % port_base
176 req_conn = connection % port_base
176 sub_conn = connection % (port_base+1)
177 sub_conn = connection % (port_base+1)
177
178
178 # Create initial sockets
179 # Create initial sockets
179 c = zmq.Context()
180 c = zmq.Context()
180 request_socket = c.socket(zmq.XREQ)
181 request_socket = c.socket(zmq.XREQ)
181 request_socket.connect(req_conn)
182 request_socket.connect(req_conn)
182
183
183 sub_socket = c.socket(zmq.SUB)
184 sub_socket = c.socket(zmq.SUB)
184 sub_socket.connect(sub_conn)
185 sub_socket.connect(sub_conn)
185 sub_socket.setsockopt(zmq.SUBSCRIBE, '')
186 sub_socket.setsockopt(zmq.SUBSCRIBE, '')
186
187
187 # Make session and user-facing client
188 # Make session and user-facing client
188 sess = session.Session()
189 sess = session.Session()
189 client = InteractiveClient(sess, request_socket, sub_socket)
190 client = InteractiveClient(sess, request_socket, sub_socket)
190 client.interact()
191 client.interact()
191
192
192
193
193 if __name__ == '__main__':
194 if __name__ == '__main__':
194 main()
195 main()
@@ -1,43 +1,45 b''
1 """The client and server for a basic ping-pong style heartbeat.
1 """The client and server for a basic ping-pong style heartbeat.
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2010 The IPython Development Team
5 # Copyright (C) 2008-2010 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import sys
15 import sys
16 from threading import Thread
16 from threading import Thread
17
17
18 import zmq
18 import zmq
19
19
20 from IPython.utils.localinterfaces import LOCALHOST
21
20 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
21 # Code
23 # Code
22 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
23
25
24
26
25 class Heartbeat(Thread):
27 class Heartbeat(Thread):
26 "A simple ping-pong style heartbeat that runs in a thread."
28 "A simple ping-pong style heartbeat that runs in a thread."
27
29
28 def __init__(self, context, addr=('127.0.0.1', 0)):
30 def __init__(self, context, addr=(LOCALHOST, 0)):
29 Thread.__init__(self)
31 Thread.__init__(self)
30 self.context = context
32 self.context = context
31 self.addr = addr
33 self.addr = addr
32 self.ip = addr[0]
34 self.ip = addr[0]
33 self.port = addr[1]
35 self.port = addr[1]
34 self.daemon = True
36 self.daemon = True
35
37
36 def run(self):
38 def run(self):
37 self.socket = self.context.socket(zmq.REP)
39 self.socket = self.context.socket(zmq.REP)
38 if self.port == 0:
40 if self.port == 0:
39 self.port = self.socket.bind_to_random_port('tcp://%s' % self.ip)
41 self.port = self.socket.bind_to_random_port('tcp://%s' % self.ip)
40 else:
42 else:
41 self.socket.bind('tcp://%s:%i' % self.addr)
43 self.socket.bind('tcp://%s:%i' % self.addr)
42 zmq.device(zmq.FORWARDER, self.socket, self.socket)
44 zmq.device(zmq.FORWARDER, self.socket, self.socket)
43
45
@@ -1,623 +1,630 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 import atexit
20 import atexit
21 import sys
21 import sys
22 import time
22 import time
23 import traceback
23 import traceback
24
24
25 # System library imports.
25 # System library imports.
26 import zmq
26 import zmq
27
27
28 # Local imports.
28 # Local imports.
29 from IPython.config.configurable import Configurable
29 from IPython.config.configurable import Configurable
30 from IPython.utils import io
30 from IPython.utils import io
31 from IPython.utils.jsonutil import json_clean
31 from IPython.utils.jsonutil import json_clean
32 from IPython.lib import pylabtools
32 from IPython.lib import pylabtools
33 from IPython.utils.traitlets import Instance, Float
33 from IPython.utils.traitlets import Instance, Float
34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
35 start_kernel)
35 start_kernel)
36 from iostream import OutStream
36 from iostream import OutStream
37 from session import Session, Message
37 from session import Session, Message
38 from zmqshell import ZMQInteractiveShell
38 from zmqshell import ZMQInteractiveShell
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Main kernel class
41 # Main kernel class
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 class Kernel(Configurable):
44 class Kernel(Configurable):
45
45
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47 # Kernel interface
47 # Kernel interface
48 #---------------------------------------------------------------------------
48 #---------------------------------------------------------------------------
49
49
50 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
50 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
51 session = Instance(Session)
51 session = Instance(Session)
52 reply_socket = Instance('zmq.Socket')
52 reply_socket = Instance('zmq.Socket')
53 pub_socket = Instance('zmq.Socket')
53 pub_socket = Instance('zmq.Socket')
54 req_socket = Instance('zmq.Socket')
54 req_socket = Instance('zmq.Socket')
55
55
56 # Private interface
56 # Private interface
57
57
58 # Time to sleep after flushing the stdout/err buffers in each execute
58 # Time to sleep after flushing the stdout/err buffers in each execute
59 # cycle. While this introduces a hard limit on the minimal latency of the
59 # cycle. While this introduces a hard limit on the minimal latency of the
60 # execute cycle, it helps prevent output synchronization problems for
60 # execute cycle, it helps prevent output synchronization problems for
61 # clients.
61 # clients.
62 # Units are in seconds. The minimum zmq latency on local host is probably
62 # Units are in seconds. The minimum zmq latency on local host is probably
63 # ~150 microseconds, set this to 500us for now. We may need to increase it
63 # ~150 microseconds, set this to 500us for now. We may need to increase it
64 # a little if it's not enough after more interactive testing.
64 # a little if it's not enough after more interactive testing.
65 _execute_sleep = Float(0.0005, config=True)
65 _execute_sleep = Float(0.0005, config=True)
66
66
67 # Frequency of the kernel's event loop.
67 # Frequency of the kernel's event loop.
68 # Units are in seconds, kernel subclasses for GUI toolkits may need to
68 # Units are in seconds, kernel subclasses for GUI toolkits may need to
69 # adapt to milliseconds.
69 # adapt to milliseconds.
70 _poll_interval = Float(0.05, config=True)
70 _poll_interval = Float(0.05, config=True)
71
71
72 # If the shutdown was requested over the network, we leave here the
72 # If the shutdown was requested over the network, we leave here the
73 # necessary reply message so it can be sent by our registered atexit
73 # necessary reply message so it can be sent by our registered atexit
74 # handler. This ensures that the reply is only sent to clients truly at
74 # handler. This ensures that the reply is only sent to clients truly at
75 # the end of our shutdown process (which happens after the underlying
75 # the end of our shutdown process (which happens after the underlying
76 # IPython shell's own shutdown).
76 # IPython shell's own shutdown).
77 _shutdown_message = None
77 _shutdown_message = None
78
78
79 # This is a dict of port number that the kernel is listening on. It is set
79 # This is a dict of port number that the kernel is listening on. It is set
80 # by record_ports and used by connect_request.
80 # by record_ports and used by connect_request.
81 _recorded_ports = None
81 _recorded_ports = None
82
82
83 def __init__(self, **kwargs):
83 def __init__(self, **kwargs):
84 super(Kernel, self).__init__(**kwargs)
84 super(Kernel, self).__init__(**kwargs)
85
85
86 # Before we even start up the shell, register *first* our exit handlers
86 # Before we even start up the shell, register *first* our exit handlers
87 # so they come before the shell's
87 # so they come before the shell's
88 atexit.register(self._at_shutdown)
88 atexit.register(self._at_shutdown)
89
89
90 # Initialize the InteractiveShell subclass
90 # Initialize the InteractiveShell subclass
91 self.shell = ZMQInteractiveShell.instance()
91 self.shell = ZMQInteractiveShell.instance()
92 self.shell.displayhook.session = self.session
92 self.shell.displayhook.session = self.session
93 self.shell.displayhook.pub_socket = self.pub_socket
93 self.shell.displayhook.pub_socket = self.pub_socket
94
94
95 # TMP - hack while developing
95 # TMP - hack while developing
96 self.shell._reply_content = None
96 self.shell._reply_content = None
97
97
98 # Build dict of handlers for message types
98 # Build dict of handlers for message types
99 msg_types = [ 'execute_request', 'complete_request',
99 msg_types = [ 'execute_request', 'complete_request',
100 'object_info_request', 'history_request',
100 'object_info_request', 'history_request',
101 'connect_request', 'shutdown_request']
101 'connect_request', 'shutdown_request']
102 self.handlers = {}
102 self.handlers = {}
103 for msg_type in msg_types:
103 for msg_type in msg_types:
104 self.handlers[msg_type] = getattr(self, msg_type)
104 self.handlers[msg_type] = getattr(self, msg_type)
105
105
106 def do_one_iteration(self):
106 def do_one_iteration(self):
107 """Do one iteration of the kernel's evaluation loop.
107 """Do one iteration of the kernel's evaluation loop.
108 """
108 """
109 try:
109 try:
110 ident = self.reply_socket.recv(zmq.NOBLOCK)
110 ident = self.reply_socket.recv(zmq.NOBLOCK)
111 except zmq.ZMQError, e:
111 except zmq.ZMQError, e:
112 if e.errno == zmq.EAGAIN:
112 if e.errno == zmq.EAGAIN:
113 return
113 return
114 else:
114 else:
115 raise
115 raise
116 # This assert will raise in versions of zeromq 2.0.7 and lesser.
116 # This assert will raise in versions of zeromq 2.0.7 and lesser.
117 # We now require 2.0.8 or above, so we can uncomment for safety.
117 # We now require 2.0.8 or above, so we can uncomment for safety.
118 assert self.reply_socket.rcvmore(), "Missing message part."
118 assert self.reply_socket.rcvmore(), "Missing message part."
119 msg = self.reply_socket.recv_json()
119 msg = self.reply_socket.recv_json()
120
120
121 # Print some info about this message and leave a '--->' marker, so it's
121 # Print some info about this message and leave a '--->' marker, so it's
122 # easier to trace visually the message chain when debugging. Each
122 # easier to trace visually the message chain when debugging. Each
123 # handler prints its message at the end.
123 # handler prints its message at the end.
124 # Eventually we'll move these from stdout to a logger.
124 # Eventually we'll move these from stdout to a logger.
125 io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
125 io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
126 io.raw_print(' Content: ', msg['content'],
126 io.raw_print(' Content: ', msg['content'],
127 '\n --->\n ', sep='', end='')
127 '\n --->\n ', sep='', end='')
128
128
129 # Find and call actual handler for message
129 # Find and call actual handler for message
130 handler = self.handlers.get(msg['msg_type'], None)
130 handler = self.handlers.get(msg['msg_type'], None)
131 if handler is None:
131 if handler is None:
132 io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
132 io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
133 else:
133 else:
134 handler(ident, msg)
134 handler(ident, msg)
135
135
136 # Check whether we should exit, in case the incoming message set the
136 # Check whether we should exit, in case the incoming message set the
137 # exit flag on
137 # exit flag on
138 if self.shell.exit_now:
138 if self.shell.exit_now:
139 io.raw_print('\nExiting IPython kernel...')
139 io.raw_print('\nExiting IPython kernel...')
140 # We do a normal, clean exit, which allows any actions registered
140 # We do a normal, clean exit, which allows any actions registered
141 # via atexit (such as history saving) to take place.
141 # via atexit (such as history saving) to take place.
142 sys.exit(0)
142 sys.exit(0)
143
143
144
144
145 def start(self):
145 def start(self):
146 """ Start the kernel main loop.
146 """ Start the kernel main loop.
147 """
147 """
148 while True:
148 while True:
149 time.sleep(self._poll_interval)
149 time.sleep(self._poll_interval)
150 self.do_one_iteration()
150 self.do_one_iteration()
151
151
152 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
152 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
153 """Record the ports that this kernel is using.
153 """Record the ports that this kernel is using.
154
154
155 The creator of the Kernel instance must call this methods if they
155 The creator of the Kernel instance must call this methods if they
156 want the :meth:`connect_request` method to return the port numbers.
156 want the :meth:`connect_request` method to return the port numbers.
157 """
157 """
158 self._recorded_ports = {
158 self._recorded_ports = {
159 'xrep_port' : xrep_port,
159 'xrep_port' : xrep_port,
160 'pub_port' : pub_port,
160 'pub_port' : pub_port,
161 'req_port' : req_port,
161 'req_port' : req_port,
162 'hb_port' : hb_port
162 'hb_port' : hb_port
163 }
163 }
164
164
165 #---------------------------------------------------------------------------
165 #---------------------------------------------------------------------------
166 # Kernel request handlers
166 # Kernel request handlers
167 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
168
168
169 def _publish_pyin(self, code, parent):
169 def _publish_pyin(self, code, parent):
170 """Publish the code request on the pyin stream."""
170 """Publish the code request on the pyin stream."""
171
171
172 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
172 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
173 self.pub_socket.send_json(pyin_msg)
173 self.pub_socket.send_json(pyin_msg)
174
174
175 def execute_request(self, ident, parent):
175 def execute_request(self, ident, parent):
176
176
177 status_msg = self.session.msg(
177 status_msg = self.session.msg(
178 u'status',
178 u'status',
179 {u'execution_state':u'busy'},
179 {u'execution_state':u'busy'},
180 parent=parent
180 parent=parent
181 )
181 )
182 self.pub_socket.send_json(status_msg)
182 self.pub_socket.send_json(status_msg)
183
183
184 try:
184 try:
185 content = parent[u'content']
185 content = parent[u'content']
186 code = content[u'code']
186 code = content[u'code']
187 silent = content[u'silent']
187 silent = content[u'silent']
188 except:
188 except:
189 io.raw_print_err("Got bad msg: ")
189 io.raw_print_err("Got bad msg: ")
190 io.raw_print_err(Message(parent))
190 io.raw_print_err(Message(parent))
191 return
191 return
192
192
193 shell = self.shell # we'll need this a lot here
193 shell = self.shell # we'll need this a lot here
194
194
195 # Replace raw_input. Note that is not sufficient to replace
195 # Replace raw_input. Note that is not sufficient to replace
196 # raw_input in the user namespace.
196 # raw_input in the user namespace.
197 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
197 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
198 __builtin__.raw_input = raw_input
198 __builtin__.raw_input = raw_input
199
199
200 # Set the parent message of the display hook and out streams.
200 # Set the parent message of the display hook and out streams.
201 shell.displayhook.set_parent(parent)
201 shell.displayhook.set_parent(parent)
202 sys.stdout.set_parent(parent)
202 sys.stdout.set_parent(parent)
203 sys.stderr.set_parent(parent)
203 sys.stderr.set_parent(parent)
204
204
205 # Re-broadcast our input for the benefit of listening clients, and
205 # Re-broadcast our input for the benefit of listening clients, and
206 # start computing output
206 # start computing output
207 if not silent:
207 if not silent:
208 self._publish_pyin(code, parent)
208 self._publish_pyin(code, parent)
209
209
210 reply_content = {}
210 reply_content = {}
211 try:
211 try:
212 if silent:
212 if silent:
213 # run_code uses 'exec' mode, so no displayhook will fire, and it
213 # run_code uses 'exec' mode, so no displayhook will fire, and it
214 # doesn't call logging or history manipulations. Print
214 # doesn't call logging or history manipulations. Print
215 # statements in that code will obviously still execute.
215 # statements in that code will obviously still execute.
216 shell.run_code(code)
216 shell.run_code(code)
217 else:
217 else:
218 # FIXME: the shell calls the exception handler itself.
218 # FIXME: the shell calls the exception handler itself.
219 shell._reply_content = None
219 shell._reply_content = None
220 shell.run_cell(code)
220 shell.run_cell(code)
221 except:
221 except:
222 status = u'error'
222 status = u'error'
223 # FIXME: this code right now isn't being used yet by default,
223 # FIXME: this code right now isn't being used yet by default,
224 # because the runlines() call above directly fires off exception
224 # because the runlines() call above directly fires off exception
225 # reporting. This code, therefore, is only active in the scenario
225 # reporting. This code, therefore, is only active in the scenario
226 # where runlines itself has an unhandled exception. We need to
226 # where runlines itself has an unhandled exception. We need to
227 # uniformize this, for all exception construction to come from a
227 # uniformize this, for all exception construction to come from a
228 # single location in the codbase.
228 # single location in the codbase.
229 etype, evalue, tb = sys.exc_info()
229 etype, evalue, tb = sys.exc_info()
230 tb_list = traceback.format_exception(etype, evalue, tb)
230 tb_list = traceback.format_exception(etype, evalue, tb)
231 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
231 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
232 else:
232 else:
233 status = u'ok'
233 status = u'ok'
234
234
235 reply_content[u'status'] = status
235 reply_content[u'status'] = status
236
236
237 # Return the execution counter so clients can display prompts
237 # Return the execution counter so clients can display prompts
238 reply_content['execution_count'] = shell.execution_count -1
238 reply_content['execution_count'] = shell.execution_count -1
239
239
240 # FIXME - fish exception info out of shell, possibly left there by
240 # FIXME - fish exception info out of shell, possibly left there by
241 # runlines. We'll need to clean up this logic later.
241 # runlines. We'll need to clean up this logic later.
242 if shell._reply_content is not None:
242 if shell._reply_content is not None:
243 reply_content.update(shell._reply_content)
243 reply_content.update(shell._reply_content)
244
244
245 # At this point, we can tell whether the main code execution succeeded
245 # At this point, we can tell whether the main code execution succeeded
246 # or not. If it did, we proceed to evaluate user_variables/expressions
246 # or not. If it did, we proceed to evaluate user_variables/expressions
247 if reply_content['status'] == 'ok':
247 if reply_content['status'] == 'ok':
248 reply_content[u'user_variables'] = \
248 reply_content[u'user_variables'] = \
249 shell.user_variables(content[u'user_variables'])
249 shell.user_variables(content[u'user_variables'])
250 reply_content[u'user_expressions'] = \
250 reply_content[u'user_expressions'] = \
251 shell.user_expressions(content[u'user_expressions'])
251 shell.user_expressions(content[u'user_expressions'])
252 else:
252 else:
253 # If there was an error, don't even try to compute variables or
253 # If there was an error, don't even try to compute variables or
254 # expressions
254 # expressions
255 reply_content[u'user_variables'] = {}
255 reply_content[u'user_variables'] = {}
256 reply_content[u'user_expressions'] = {}
256 reply_content[u'user_expressions'] = {}
257
257
258 # Payloads should be retrieved regardless of outcome, so we can both
258 # Payloads should be retrieved regardless of outcome, so we can both
259 # recover partial output (that could have been generated early in a
259 # recover partial output (that could have been generated early in a
260 # block, before an error) and clear the payload system always.
260 # block, before an error) and clear the payload system always.
261 reply_content[u'payload'] = shell.payload_manager.read_payload()
261 reply_content[u'payload'] = shell.payload_manager.read_payload()
262 # Be agressive about clearing the payload because we don't want
262 # Be agressive about clearing the payload because we don't want
263 # it to sit in memory until the next execute_request comes in.
263 # it to sit in memory until the next execute_request comes in.
264 shell.payload_manager.clear_payload()
264 shell.payload_manager.clear_payload()
265
265
266 # Send the reply.
266 # Send the reply.
267 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
267 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
268 io.raw_print(reply_msg)
268 io.raw_print(reply_msg)
269
269
270 # Flush output before sending the reply.
270 # Flush output before sending the reply.
271 sys.stdout.flush()
271 sys.stdout.flush()
272 sys.stderr.flush()
272 sys.stderr.flush()
273 # FIXME: on rare occasions, the flush doesn't seem to make it to the
273 # FIXME: on rare occasions, the flush doesn't seem to make it to the
274 # clients... This seems to mitigate the problem, but we definitely need
274 # clients... This seems to mitigate the problem, but we definitely need
275 # to better understand what's going on.
275 # to better understand what's going on.
276 if self._execute_sleep:
276 if self._execute_sleep:
277 time.sleep(self._execute_sleep)
277 time.sleep(self._execute_sleep)
278
278
279 self.reply_socket.send(ident, zmq.SNDMORE)
279 self.reply_socket.send(ident, zmq.SNDMORE)
280 self.reply_socket.send_json(reply_msg)
280 self.reply_socket.send_json(reply_msg)
281 if reply_msg['content']['status'] == u'error':
281 if reply_msg['content']['status'] == u'error':
282 self._abort_queue()
282 self._abort_queue()
283
283
284 status_msg = self.session.msg(
284 status_msg = self.session.msg(
285 u'status',
285 u'status',
286 {u'execution_state':u'idle'},
286 {u'execution_state':u'idle'},
287 parent=parent
287 parent=parent
288 )
288 )
289 self.pub_socket.send_json(status_msg)
289 self.pub_socket.send_json(status_msg)
290
290
291 def complete_request(self, ident, parent):
291 def complete_request(self, ident, parent):
292 txt, matches = self._complete(parent)
292 txt, matches = self._complete(parent)
293 matches = {'matches' : matches,
293 matches = {'matches' : matches,
294 'matched_text' : txt,
294 'matched_text' : txt,
295 'status' : 'ok'}
295 'status' : 'ok'}
296 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
296 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
297 matches, parent, ident)
297 matches, parent, ident)
298 io.raw_print(completion_msg)
298 io.raw_print(completion_msg)
299
299
300 def object_info_request(self, ident, parent):
300 def object_info_request(self, ident, parent):
301 object_info = self.shell.object_inspect(parent['content']['oname'])
301 object_info = self.shell.object_inspect(parent['content']['oname'])
302 # Before we send this object over, we scrub it for JSON usage
302 # Before we send this object over, we scrub it for JSON usage
303 oinfo = json_clean(object_info)
303 oinfo = json_clean(object_info)
304 msg = self.session.send(self.reply_socket, 'object_info_reply',
304 msg = self.session.send(self.reply_socket, 'object_info_reply',
305 oinfo, parent, ident)
305 oinfo, parent, ident)
306 io.raw_print(msg)
306 io.raw_print(msg)
307
307
308 def history_request(self, ident, parent):
308 def history_request(self, ident, parent):
309 output = parent['content']['output']
309 output = parent['content']['output']
310 index = parent['content']['index']
310 index = parent['content']['index']
311 raw = parent['content']['raw']
311 raw = parent['content']['raw']
312 hist = self.shell.get_history(index=index, raw=raw, output=output)
312 hist = self.shell.get_history(index=index, raw=raw, output=output)
313 content = {'history' : hist}
313 content = {'history' : hist}
314 msg = self.session.send(self.reply_socket, 'history_reply',
314 msg = self.session.send(self.reply_socket, 'history_reply',
315 content, parent, ident)
315 content, parent, ident)
316 io.raw_print(msg)
316 io.raw_print(msg)
317
317
318 def connect_request(self, ident, parent):
318 def connect_request(self, ident, parent):
319 if self._recorded_ports is not None:
319 if self._recorded_ports is not None:
320 content = self._recorded_ports.copy()
320 content = self._recorded_ports.copy()
321 else:
321 else:
322 content = {}
322 content = {}
323 msg = self.session.send(self.reply_socket, 'connect_reply',
323 msg = self.session.send(self.reply_socket, 'connect_reply',
324 content, parent, ident)
324 content, parent, ident)
325 io.raw_print(msg)
325 io.raw_print(msg)
326
326
327 def shutdown_request(self, ident, parent):
327 def shutdown_request(self, ident, parent):
328 self.shell.exit_now = True
328 self.shell.exit_now = True
329 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
329 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
330 sys.exit(0)
330 sys.exit(0)
331
331
332 #---------------------------------------------------------------------------
332 #---------------------------------------------------------------------------
333 # Protected interface
333 # Protected interface
334 #---------------------------------------------------------------------------
334 #---------------------------------------------------------------------------
335
335
336 def _abort_queue(self):
336 def _abort_queue(self):
337 while True:
337 while True:
338 try:
338 try:
339 ident = self.reply_socket.recv(zmq.NOBLOCK)
339 ident = self.reply_socket.recv(zmq.NOBLOCK)
340 except zmq.ZMQError, e:
340 except zmq.ZMQError, e:
341 if e.errno == zmq.EAGAIN:
341 if e.errno == zmq.EAGAIN:
342 break
342 break
343 else:
343 else:
344 assert self.reply_socket.rcvmore(), \
344 assert self.reply_socket.rcvmore(), \
345 "Unexpected missing message part."
345 "Unexpected missing message part."
346 msg = self.reply_socket.recv_json()
346 msg = self.reply_socket.recv_json()
347 io.raw_print("Aborting:\n", Message(msg))
347 io.raw_print("Aborting:\n", Message(msg))
348 msg_type = msg['msg_type']
348 msg_type = msg['msg_type']
349 reply_type = msg_type.split('_')[0] + '_reply'
349 reply_type = msg_type.split('_')[0] + '_reply'
350 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
350 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
351 io.raw_print(reply_msg)
351 io.raw_print(reply_msg)
352 self.reply_socket.send(ident,zmq.SNDMORE)
352 self.reply_socket.send(ident,zmq.SNDMORE)
353 self.reply_socket.send_json(reply_msg)
353 self.reply_socket.send_json(reply_msg)
354 # We need to wait a bit for requests to come in. This can probably
354 # We need to wait a bit for requests to come in. This can probably
355 # be set shorter for true asynchronous clients.
355 # be set shorter for true asynchronous clients.
356 time.sleep(0.1)
356 time.sleep(0.1)
357
357
358 def _raw_input(self, prompt, ident, parent):
358 def _raw_input(self, prompt, ident, parent):
359 # Flush output before making the request.
359 # Flush output before making the request.
360 sys.stderr.flush()
360 sys.stderr.flush()
361 sys.stdout.flush()
361 sys.stdout.flush()
362
362
363 # Send the input request.
363 # Send the input request.
364 content = dict(prompt=prompt)
364 content = dict(prompt=prompt)
365 msg = self.session.msg(u'input_request', content, parent)
365 msg = self.session.msg(u'input_request', content, parent)
366 self.req_socket.send_json(msg)
366 self.req_socket.send_json(msg)
367
367
368 # Await a response.
368 # Await a response.
369 reply = self.req_socket.recv_json()
369 reply = self.req_socket.recv_json()
370 try:
370 try:
371 value = reply['content']['value']
371 value = reply['content']['value']
372 except:
372 except:
373 io.raw_print_err("Got bad raw_input reply: ")
373 io.raw_print_err("Got bad raw_input reply: ")
374 io.raw_print_err(Message(parent))
374 io.raw_print_err(Message(parent))
375 value = ''
375 value = ''
376 return value
376 return value
377
377
378 def _complete(self, msg):
378 def _complete(self, msg):
379 c = msg['content']
379 c = msg['content']
380 try:
380 try:
381 cpos = int(c['cursor_pos'])
381 cpos = int(c['cursor_pos'])
382 except:
382 except:
383 # If we don't get something that we can convert to an integer, at
383 # If we don't get something that we can convert to an integer, at
384 # least attempt the completion guessing the cursor is at the end of
384 # least attempt the completion guessing the cursor is at the end of
385 # the text, if there's any, and otherwise of the line
385 # the text, if there's any, and otherwise of the line
386 cpos = len(c['text'])
386 cpos = len(c['text'])
387 if cpos==0:
387 if cpos==0:
388 cpos = len(c['line'])
388 cpos = len(c['line'])
389 return self.shell.complete(c['text'], c['line'], cpos)
389 return self.shell.complete(c['text'], c['line'], cpos)
390
390
391 def _object_info(self, context):
391 def _object_info(self, context):
392 symbol, leftover = self._symbol_from_context(context)
392 symbol, leftover = self._symbol_from_context(context)
393 if symbol is not None and not leftover:
393 if symbol is not None and not leftover:
394 doc = getattr(symbol, '__doc__', '')
394 doc = getattr(symbol, '__doc__', '')
395 else:
395 else:
396 doc = ''
396 doc = ''
397 object_info = dict(docstring = doc)
397 object_info = dict(docstring = doc)
398 return object_info
398 return object_info
399
399
400 def _symbol_from_context(self, context):
400 def _symbol_from_context(self, context):
401 if not context:
401 if not context:
402 return None, context
402 return None, context
403
403
404 base_symbol_string = context[0]
404 base_symbol_string = context[0]
405 symbol = self.shell.user_ns.get(base_symbol_string, None)
405 symbol = self.shell.user_ns.get(base_symbol_string, None)
406 if symbol is None:
406 if symbol is None:
407 symbol = __builtin__.__dict__.get(base_symbol_string, None)
407 symbol = __builtin__.__dict__.get(base_symbol_string, None)
408 if symbol is None:
408 if symbol is None:
409 return None, context
409 return None, context
410
410
411 context = context[1:]
411 context = context[1:]
412 for i, name in enumerate(context):
412 for i, name in enumerate(context):
413 new_symbol = getattr(symbol, name, None)
413 new_symbol = getattr(symbol, name, None)
414 if new_symbol is None:
414 if new_symbol is None:
415 return symbol, context[i:]
415 return symbol, context[i:]
416 else:
416 else:
417 symbol = new_symbol
417 symbol = new_symbol
418
418
419 return symbol, []
419 return symbol, []
420
420
421 def _at_shutdown(self):
421 def _at_shutdown(self):
422 """Actions taken at shutdown by the kernel, called by python's atexit.
422 """Actions taken at shutdown by the kernel, called by python's atexit.
423 """
423 """
424 # io.rprint("Kernel at_shutdown") # dbg
424 # io.rprint("Kernel at_shutdown") # dbg
425 if self._shutdown_message is not None:
425 if self._shutdown_message is not None:
426 self.reply_socket.send_json(self._shutdown_message)
426 self.reply_socket.send_json(self._shutdown_message)
427 self.pub_socket.send_json(self._shutdown_message)
427 self.pub_socket.send_json(self._shutdown_message)
428 io.raw_print(self._shutdown_message)
428 io.raw_print(self._shutdown_message)
429 # A very short sleep to give zmq time to flush its message buffers
429 # A very short sleep to give zmq time to flush its message buffers
430 # before Python truly shuts down.
430 # before Python truly shuts down.
431 time.sleep(0.01)
431 time.sleep(0.01)
432
432
433
433
434 class QtKernel(Kernel):
434 class QtKernel(Kernel):
435 """A Kernel subclass with Qt support."""
435 """A Kernel subclass with Qt support."""
436
436
437 def start(self):
437 def start(self):
438 """Start a kernel with QtPy4 event loop integration."""
438 """Start a kernel with QtPy4 event loop integration."""
439
439
440 from PyQt4 import QtCore
440 from PyQt4 import QtCore
441 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
441 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
442
442
443 self.app = get_app_qt4([" "])
443 self.app = get_app_qt4([" "])
444 self.app.setQuitOnLastWindowClosed(False)
444 self.app.setQuitOnLastWindowClosed(False)
445 self.timer = QtCore.QTimer()
445 self.timer = QtCore.QTimer()
446 self.timer.timeout.connect(self.do_one_iteration)
446 self.timer.timeout.connect(self.do_one_iteration)
447 # Units for the timer are in milliseconds
447 # Units for the timer are in milliseconds
448 self.timer.start(1000*self._poll_interval)
448 self.timer.start(1000*self._poll_interval)
449 start_event_loop_qt4(self.app)
449 start_event_loop_qt4(self.app)
450
450
451
451
452 class WxKernel(Kernel):
452 class WxKernel(Kernel):
453 """A Kernel subclass with Wx support."""
453 """A Kernel subclass with Wx support."""
454
454
455 def start(self):
455 def start(self):
456 """Start a kernel with wx event loop support."""
456 """Start a kernel with wx event loop support."""
457
457
458 import wx
458 import wx
459 from IPython.lib.guisupport import start_event_loop_wx
459 from IPython.lib.guisupport import start_event_loop_wx
460
460
461 doi = self.do_one_iteration
461 doi = self.do_one_iteration
462 # Wx uses milliseconds
462 # Wx uses milliseconds
463 poll_interval = int(1000*self._poll_interval)
463 poll_interval = int(1000*self._poll_interval)
464
464
465 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
465 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
466 # We make the Frame hidden when we create it in the main app below.
466 # We make the Frame hidden when we create it in the main app below.
467 class TimerFrame(wx.Frame):
467 class TimerFrame(wx.Frame):
468 def __init__(self, func):
468 def __init__(self, func):
469 wx.Frame.__init__(self, None, -1)
469 wx.Frame.__init__(self, None, -1)
470 self.timer = wx.Timer(self)
470 self.timer = wx.Timer(self)
471 # Units for the timer are in milliseconds
471 # Units for the timer are in milliseconds
472 self.timer.Start(poll_interval)
472 self.timer.Start(poll_interval)
473 self.Bind(wx.EVT_TIMER, self.on_timer)
473 self.Bind(wx.EVT_TIMER, self.on_timer)
474 self.func = func
474 self.func = func
475
475
476 def on_timer(self, event):
476 def on_timer(self, event):
477 self.func()
477 self.func()
478
478
479 # We need a custom wx.App to create our Frame subclass that has the
479 # We need a custom wx.App to create our Frame subclass that has the
480 # wx.Timer to drive the ZMQ event loop.
480 # wx.Timer to drive the ZMQ event loop.
481 class IPWxApp(wx.App):
481 class IPWxApp(wx.App):
482 def OnInit(self):
482 def OnInit(self):
483 self.frame = TimerFrame(doi)
483 self.frame = TimerFrame(doi)
484 self.frame.Show(False)
484 self.frame.Show(False)
485 return True
485 return True
486
486
487 # The redirect=False here makes sure that wx doesn't replace
487 # The redirect=False here makes sure that wx doesn't replace
488 # sys.stdout/stderr with its own classes.
488 # sys.stdout/stderr with its own classes.
489 self.app = IPWxApp(redirect=False)
489 self.app = IPWxApp(redirect=False)
490 start_event_loop_wx(self.app)
490 start_event_loop_wx(self.app)
491
491
492
492
493 class TkKernel(Kernel):
493 class TkKernel(Kernel):
494 """A Kernel subclass with Tk support."""
494 """A Kernel subclass with Tk support."""
495
495
496 def start(self):
496 def start(self):
497 """Start a Tk enabled event loop."""
497 """Start a Tk enabled event loop."""
498
498
499 import Tkinter
499 import Tkinter
500 doi = self.do_one_iteration
500 doi = self.do_one_iteration
501 # Tk uses milliseconds
501 # Tk uses milliseconds
502 poll_interval = int(1000*self._poll_interval)
502 poll_interval = int(1000*self._poll_interval)
503 # For Tkinter, we create a Tk object and call its withdraw method.
503 # For Tkinter, we create a Tk object and call its withdraw method.
504 class Timer(object):
504 class Timer(object):
505 def __init__(self, func):
505 def __init__(self, func):
506 self.app = Tkinter.Tk()
506 self.app = Tkinter.Tk()
507 self.app.withdraw()
507 self.app.withdraw()
508 self.func = func
508 self.func = func
509
509
510 def on_timer(self):
510 def on_timer(self):
511 self.func()
511 self.func()
512 self.app.after(poll_interval, self.on_timer)
512 self.app.after(poll_interval, self.on_timer)
513
513
514 def start(self):
514 def start(self):
515 self.on_timer() # Call it once to get things going.
515 self.on_timer() # Call it once to get things going.
516 self.app.mainloop()
516 self.app.mainloop()
517
517
518 self.timer = Timer(doi)
518 self.timer = Timer(doi)
519 self.timer.start()
519 self.timer.start()
520
520
521
521
522 class GTKKernel(Kernel):
522 class GTKKernel(Kernel):
523 """A Kernel subclass with GTK support."""
523 """A Kernel subclass with GTK support."""
524
524
525 def start(self):
525 def start(self):
526 """Start the kernel, coordinating with the GTK event loop"""
526 """Start the kernel, coordinating with the GTK event loop"""
527 from .gui.gtkembed import GTKEmbed
527 from .gui.gtkembed import GTKEmbed
528
528
529 gtk_kernel = GTKEmbed(self)
529 gtk_kernel = GTKEmbed(self)
530 gtk_kernel.start()
530 gtk_kernel.start()
531
531
532
532
533 #-----------------------------------------------------------------------------
533 #-----------------------------------------------------------------------------
534 # Kernel main and launch functions
534 # Kernel main and launch functions
535 #-----------------------------------------------------------------------------
535 #-----------------------------------------------------------------------------
536
536
537 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
537 def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
538 independent=False, pylab=False):
538 independent=False, pylab=False):
539 """Launches a localhost kernel, binding to the specified ports.
539 """Launches a localhost kernel, binding to the specified ports.
540
540
541 Parameters
541 Parameters
542 ----------
542 ----------
543 ip : str, optional
544 The ip address the kernel will bind to.
545
543 xrep_port : int, optional
546 xrep_port : int, optional
544 The port to use for XREP channel.
547 The port to use for XREP channel.
545
548
546 pub_port : int, optional
549 pub_port : int, optional
547 The port to use for the SUB channel.
550 The port to use for the SUB channel.
548
551
549 req_port : int, optional
552 req_port : int, optional
550 The port to use for the REQ (raw input) channel.
553 The port to use for the REQ (raw input) channel.
551
554
552 hb_port : int, optional
555 hb_port : int, optional
553 The port to use for the hearbeat REP channel.
556 The port to use for the hearbeat REP channel.
554
557
555 independent : bool, optional (default False)
558 independent : bool, optional (default False)
556 If set, the kernel process is guaranteed to survive if this process
559 If set, the kernel process is guaranteed to survive if this process
557 dies. If not set, an effort is made to ensure that the kernel is killed
560 dies. If not set, an effort is made to ensure that the kernel is killed
558 when this process dies. Note that in this case it is still good practice
561 when this process dies. Note that in this case it is still good practice
559 to kill kernels manually before exiting.
562 to kill kernels manually before exiting.
560
563
561 pylab : bool or string, optional (default False)
564 pylab : bool or string, optional (default False)
562 If not False, the kernel will be launched with pylab enabled. If a
565 If not False, the kernel will be launched with pylab enabled. If a
563 string is passed, matplotlib will use the specified backend. Otherwise,
566 string is passed, matplotlib will use the specified backend. Otherwise,
564 matplotlib's default backend will be used.
567 matplotlib's default backend will be used.
565
568
566 Returns
569 Returns
567 -------
570 -------
568 A tuple of form:
571 A tuple of form:
569 (kernel_process, xrep_port, pub_port, req_port)
572 (kernel_process, xrep_port, pub_port, req_port)
570 where kernel_process is a Popen object and the ports are integers.
573 where kernel_process is a Popen object and the ports are integers.
571 """
574 """
572 extra_arguments = []
575 extra_arguments = []
573 if pylab:
576 if pylab:
574 extra_arguments.append('--pylab')
577 extra_arguments.append('--pylab')
575 if isinstance(pylab, basestring):
578 if isinstance(pylab, basestring):
576 extra_arguments.append(pylab)
579 extra_arguments.append(pylab)
580 if ip is not None:
581 extra_arguments.append('--ip')
582 if isinstance(ip, basestring):
583 extra_arguments.append(ip)
577 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
584 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
578 xrep_port, pub_port, req_port, hb_port,
585 xrep_port, pub_port, req_port, hb_port,
579 independent, extra_arguments)
586 independent, extra_arguments)
580
587
581
588
582 def main():
589 def main():
583 """ The IPython kernel main entry point.
590 """ The IPython kernel main entry point.
584 """
591 """
585 parser = make_argument_parser()
592 parser = make_argument_parser()
586 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
593 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
587 const='auto', help = \
594 const='auto', help = \
588 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
595 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
589 given, the GUI backend is matplotlib's, otherwise use one of: \
596 given, the GUI backend is matplotlib's, otherwise use one of: \
590 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
597 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
591 namespace = parser.parse_args()
598 namespace = parser.parse_args()
592
599
593 kernel_class = Kernel
600 kernel_class = Kernel
594
601
595 kernel_classes = {
602 kernel_classes = {
596 'qt' : QtKernel,
603 'qt' : QtKernel,
597 'qt4': QtKernel,
604 'qt4': QtKernel,
598 'inline': Kernel,
605 'inline': Kernel,
599 'wx' : WxKernel,
606 'wx' : WxKernel,
600 'tk' : TkKernel,
607 'tk' : TkKernel,
601 'gtk': GTKKernel,
608 'gtk': GTKKernel,
602 }
609 }
603 if namespace.pylab:
610 if namespace.pylab:
604 if namespace.pylab == 'auto':
611 if namespace.pylab == 'auto':
605 gui, backend = pylabtools.find_gui_and_backend()
612 gui, backend = pylabtools.find_gui_and_backend()
606 else:
613 else:
607 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
614 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
608 kernel_class = kernel_classes.get(gui)
615 kernel_class = kernel_classes.get(gui)
609 if kernel_class is None:
616 if kernel_class is None:
610 raise ValueError('GUI is not supported: %r' % gui)
617 raise ValueError('GUI is not supported: %r' % gui)
611 pylabtools.activate_matplotlib(backend)
618 pylabtools.activate_matplotlib(backend)
612
619
613 kernel = make_kernel(namespace, kernel_class, OutStream)
620 kernel = make_kernel(namespace, kernel_class, OutStream)
614
621
615 if namespace.pylab:
622 if namespace.pylab:
616 pylabtools.import_pylab(kernel.shell.user_ns, backend,
623 pylabtools.import_pylab(kernel.shell.user_ns, backend,
617 shell=kernel.shell)
624 shell=kernel.shell)
618
625
619 start_kernel(namespace, kernel)
626 start_kernel(namespace, kernel)
620
627
621
628
622 if __name__ == '__main__':
629 if __name__ == '__main__':
623 main()
630 main()
@@ -1,905 +1,906 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2010 The IPython Development Team
8 # Copyright (C) 2008-2010 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
19 import atexit
20 from Queue import Queue, Empty
20 from Queue import Queue, Empty
21 from subprocess import Popen
21 from subprocess import Popen
22 import signal
22 import signal
23 import sys
23 import sys
24 from threading import Thread
24 from threading import Thread
25 import time
25 import time
26
26
27 # System library imports.
27 # System library imports.
28 import zmq
28 import zmq
29 from zmq import POLLIN, POLLOUT, POLLERR
29 from zmq import POLLIN, POLLOUT, POLLERR
30 from zmq.eventloop import ioloop
30 from zmq.eventloop import ioloop
31
31
32 # Local imports.
32 # Local imports.
33 from IPython.utils import io
33 from IPython.utils import io
34 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
34 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
35 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
35 from session import Session
36 from session import Session
36
37
37 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
38 # Constants and exceptions
39 # Constants and exceptions
39 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
40
41
41 LOCALHOST = '127.0.0.1'
42
43 class InvalidPortNumber(Exception):
42 class InvalidPortNumber(Exception):
44 pass
43 pass
45
44
46 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
47 # Utility functions
46 # Utility functions
48 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
49
48
50 # some utilities to validate message structure, these might get moved elsewhere
49 # some utilities to validate message structure, these might get moved elsewhere
51 # if they prove to have more generic utility
50 # if they prove to have more generic utility
52
51
53 def validate_string_list(lst):
52 def validate_string_list(lst):
54 """Validate that the input is a list of strings.
53 """Validate that the input is a list of strings.
55
54
56 Raises ValueError if not."""
55 Raises ValueError if not."""
57 if not isinstance(lst, list):
56 if not isinstance(lst, list):
58 raise ValueError('input %r must be a list' % lst)
57 raise ValueError('input %r must be a list' % lst)
59 for x in lst:
58 for x in lst:
60 if not isinstance(x, basestring):
59 if not isinstance(x, basestring):
61 raise ValueError('element %r in list must be a string' % x)
60 raise ValueError('element %r in list must be a string' % x)
62
61
63
62
64 def validate_string_dict(dct):
63 def validate_string_dict(dct):
65 """Validate that the input is a dict with string keys and values.
64 """Validate that the input is a dict with string keys and values.
66
65
67 Raises ValueError if not."""
66 Raises ValueError if not."""
68 for k,v in dct.iteritems():
67 for k,v in dct.iteritems():
69 if not isinstance(k, basestring):
68 if not isinstance(k, basestring):
70 raise ValueError('key %r in dict must be a string' % k)
69 raise ValueError('key %r in dict must be a string' % k)
71 if not isinstance(v, basestring):
70 if not isinstance(v, basestring):
72 raise ValueError('value %r in dict must be a string' % v)
71 raise ValueError('value %r in dict must be a string' % v)
73
72
74
73
75 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
76 # ZMQ Socket Channel classes
75 # ZMQ Socket Channel classes
77 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
78
77
79 class ZmqSocketChannel(Thread):
78 class ZmqSocketChannel(Thread):
80 """The base class for the channels that use ZMQ sockets.
79 """The base class for the channels that use ZMQ sockets.
81 """
80 """
82 context = None
81 context = None
83 session = None
82 session = None
84 socket = None
83 socket = None
85 ioloop = None
84 ioloop = None
86 iostate = None
85 iostate = None
87 _address = None
86 _address = None
88
87
89 def __init__(self, context, session, address):
88 def __init__(self, context, session, address):
90 """Create a channel
89 """Create a channel
91
90
92 Parameters
91 Parameters
93 ----------
92 ----------
94 context : :class:`zmq.Context`
93 context : :class:`zmq.Context`
95 The ZMQ context to use.
94 The ZMQ context to use.
96 session : :class:`session.Session`
95 session : :class:`session.Session`
97 The session to use.
96 The session to use.
98 address : tuple
97 address : tuple
99 Standard (ip, port) tuple that the kernel is listening on.
98 Standard (ip, port) tuple that the kernel is listening on.
100 """
99 """
101 super(ZmqSocketChannel, self).__init__()
100 super(ZmqSocketChannel, self).__init__()
102 self.daemon = True
101 self.daemon = True
103
102
104 self.context = context
103 self.context = context
105 self.session = session
104 self.session = session
106 if address[1] == 0:
105 if address[1] == 0:
107 message = 'The port number for a channel cannot be 0.'
106 message = 'The port number for a channel cannot be 0.'
108 raise InvalidPortNumber(message)
107 raise InvalidPortNumber(message)
109 self._address = address
108 self._address = address
110
109
111 def stop(self):
110 def stop(self):
112 """Stop the channel's activity.
111 """Stop the channel's activity.
113
112
114 This calls :method:`Thread.join` and returns when the thread
113 This calls :method:`Thread.join` and returns when the thread
115 terminates. :class:`RuntimeError` will be raised if
114 terminates. :class:`RuntimeError` will be raised if
116 :method:`self.start` is called again.
115 :method:`self.start` is called again.
117 """
116 """
118 self.join()
117 self.join()
119
118
120 @property
119 @property
121 def address(self):
120 def address(self):
122 """Get the channel's address as an (ip, port) tuple.
121 """Get the channel's address as an (ip, port) tuple.
123
122
124 By the default, the address is (localhost, 0), where 0 means a random
123 By the default, the address is (localhost, 0), where 0 means a random
125 port.
124 port.
126 """
125 """
127 return self._address
126 return self._address
128
127
129 def add_io_state(self, state):
128 def add_io_state(self, state):
130 """Add IO state to the eventloop.
129 """Add IO state to the eventloop.
131
130
132 Parameters
131 Parameters
133 ----------
132 ----------
134 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
133 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
135 The IO state flag to set.
134 The IO state flag to set.
136
135
137 This is thread safe as it uses the thread safe IOLoop.add_callback.
136 This is thread safe as it uses the thread safe IOLoop.add_callback.
138 """
137 """
139 def add_io_state_callback():
138 def add_io_state_callback():
140 if not self.iostate & state:
139 if not self.iostate & state:
141 self.iostate = self.iostate | state
140 self.iostate = self.iostate | state
142 self.ioloop.update_handler(self.socket, self.iostate)
141 self.ioloop.update_handler(self.socket, self.iostate)
143 self.ioloop.add_callback(add_io_state_callback)
142 self.ioloop.add_callback(add_io_state_callback)
144
143
145 def drop_io_state(self, state):
144 def drop_io_state(self, state):
146 """Drop IO state from the eventloop.
145 """Drop IO state from the eventloop.
147
146
148 Parameters
147 Parameters
149 ----------
148 ----------
150 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
149 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
151 The IO state flag to set.
150 The IO state flag to set.
152
151
153 This is thread safe as it uses the thread safe IOLoop.add_callback.
152 This is thread safe as it uses the thread safe IOLoop.add_callback.
154 """
153 """
155 def drop_io_state_callback():
154 def drop_io_state_callback():
156 if self.iostate & state:
155 if self.iostate & state:
157 self.iostate = self.iostate & (~state)
156 self.iostate = self.iostate & (~state)
158 self.ioloop.update_handler(self.socket, self.iostate)
157 self.ioloop.update_handler(self.socket, self.iostate)
159 self.ioloop.add_callback(drop_io_state_callback)
158 self.ioloop.add_callback(drop_io_state_callback)
160
159
161
160
162 class XReqSocketChannel(ZmqSocketChannel):
161 class XReqSocketChannel(ZmqSocketChannel):
163 """The XREQ channel for issues request/replies to the kernel.
162 """The XREQ channel for issues request/replies to the kernel.
164 """
163 """
165
164
166 command_queue = None
165 command_queue = None
167
166
168 def __init__(self, context, session, address):
167 def __init__(self, context, session, address):
169 super(XReqSocketChannel, self).__init__(context, session, address)
168 super(XReqSocketChannel, self).__init__(context, session, address)
170 self.command_queue = Queue()
169 self.command_queue = Queue()
171 self.ioloop = ioloop.IOLoop()
170 self.ioloop = ioloop.IOLoop()
172
171
173 def run(self):
172 def run(self):
174 """The thread's main activity. Call start() instead."""
173 """The thread's main activity. Call start() instead."""
175 self.socket = self.context.socket(zmq.XREQ)
174 self.socket = self.context.socket(zmq.XREQ)
176 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
175 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
177 self.socket.connect('tcp://%s:%i' % self.address)
176 self.socket.connect('tcp://%s:%i' % self.address)
178 self.iostate = POLLERR|POLLIN
177 self.iostate = POLLERR|POLLIN
179 self.ioloop.add_handler(self.socket, self._handle_events,
178 self.ioloop.add_handler(self.socket, self._handle_events,
180 self.iostate)
179 self.iostate)
181 self.ioloop.start()
180 self.ioloop.start()
182
181
183 def stop(self):
182 def stop(self):
184 self.ioloop.stop()
183 self.ioloop.stop()
185 super(XReqSocketChannel, self).stop()
184 super(XReqSocketChannel, self).stop()
186
185
187 def call_handlers(self, msg):
186 def call_handlers(self, msg):
188 """This method is called in the ioloop thread when a message arrives.
187 """This method is called in the ioloop thread when a message arrives.
189
188
190 Subclasses should override this method to handle incoming messages.
189 Subclasses should override this method to handle incoming messages.
191 It is important to remember that this method is called in the thread
190 It is important to remember that this method is called in the thread
192 so that some logic must be done to ensure that the application leve
191 so that some logic must be done to ensure that the application leve
193 handlers are called in the application thread.
192 handlers are called in the application thread.
194 """
193 """
195 raise NotImplementedError('call_handlers must be defined in a subclass.')
194 raise NotImplementedError('call_handlers must be defined in a subclass.')
196
195
197 def execute(self, code, silent=False,
196 def execute(self, code, silent=False,
198 user_variables=None, user_expressions=None):
197 user_variables=None, user_expressions=None):
199 """Execute code in the kernel.
198 """Execute code in the kernel.
200
199
201 Parameters
200 Parameters
202 ----------
201 ----------
203 code : str
202 code : str
204 A string of Python code.
203 A string of Python code.
205
204
206 silent : bool, optional (default False)
205 silent : bool, optional (default False)
207 If set, the kernel will execute the code as quietly possible.
206 If set, the kernel will execute the code as quietly possible.
208
207
209 user_variables : list, optional
208 user_variables : list, optional
210 A list of variable names to pull from the user's namespace. They
209 A list of variable names to pull from the user's namespace. They
211 will come back as a dict with these names as keys and their
210 will come back as a dict with these names as keys and their
212 :func:`repr` as values.
211 :func:`repr` as values.
213
212
214 user_expressions : dict, optional
213 user_expressions : dict, optional
215 A dict with string keys and to pull from the user's
214 A dict with string keys and to pull from the user's
216 namespace. They will come back as a dict with these names as keys
215 namespace. They will come back as a dict with these names as keys
217 and their :func:`repr` as values.
216 and their :func:`repr` as values.
218
217
219 Returns
218 Returns
220 -------
219 -------
221 The msg_id of the message sent.
220 The msg_id of the message sent.
222 """
221 """
223 if user_variables is None:
222 if user_variables is None:
224 user_variables = []
223 user_variables = []
225 if user_expressions is None:
224 if user_expressions is None:
226 user_expressions = {}
225 user_expressions = {}
227
226
228 # Don't waste network traffic if inputs are invalid
227 # Don't waste network traffic if inputs are invalid
229 if not isinstance(code, basestring):
228 if not isinstance(code, basestring):
230 raise ValueError('code %r must be a string' % code)
229 raise ValueError('code %r must be a string' % code)
231 validate_string_list(user_variables)
230 validate_string_list(user_variables)
232 validate_string_dict(user_expressions)
231 validate_string_dict(user_expressions)
233
232
234 # Create class for content/msg creation. Related to, but possibly
233 # Create class for content/msg creation. Related to, but possibly
235 # not in Session.
234 # not in Session.
236 content = dict(code=code, silent=silent,
235 content = dict(code=code, silent=silent,
237 user_variables=user_variables,
236 user_variables=user_variables,
238 user_expressions=user_expressions)
237 user_expressions=user_expressions)
239 msg = self.session.msg('execute_request', content)
238 msg = self.session.msg('execute_request', content)
240 self._queue_request(msg)
239 self._queue_request(msg)
241 return msg['header']['msg_id']
240 return msg['header']['msg_id']
242
241
243 def complete(self, text, line, cursor_pos, block=None):
242 def complete(self, text, line, cursor_pos, block=None):
244 """Tab complete text in the kernel's namespace.
243 """Tab complete text in the kernel's namespace.
245
244
246 Parameters
245 Parameters
247 ----------
246 ----------
248 text : str
247 text : str
249 The text to complete.
248 The text to complete.
250 line : str
249 line : str
251 The full line of text that is the surrounding context for the
250 The full line of text that is the surrounding context for the
252 text to complete.
251 text to complete.
253 cursor_pos : int
252 cursor_pos : int
254 The position of the cursor in the line where the completion was
253 The position of the cursor in the line where the completion was
255 requested.
254 requested.
256 block : str, optional
255 block : str, optional
257 The full block of code in which the completion is being requested.
256 The full block of code in which the completion is being requested.
258
257
259 Returns
258 Returns
260 -------
259 -------
261 The msg_id of the message sent.
260 The msg_id of the message sent.
262 """
261 """
263 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
262 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
264 msg = self.session.msg('complete_request', content)
263 msg = self.session.msg('complete_request', content)
265 self._queue_request(msg)
264 self._queue_request(msg)
266 return msg['header']['msg_id']
265 return msg['header']['msg_id']
267
266
268 def object_info(self, oname):
267 def object_info(self, oname):
269 """Get metadata information about an object.
268 """Get metadata information about an object.
270
269
271 Parameters
270 Parameters
272 ----------
271 ----------
273 oname : str
272 oname : str
274 A string specifying the object name.
273 A string specifying the object name.
275
274
276 Returns
275 Returns
277 -------
276 -------
278 The msg_id of the message sent.
277 The msg_id of the message sent.
279 """
278 """
280 content = dict(oname=oname)
279 content = dict(oname=oname)
281 msg = self.session.msg('object_info_request', content)
280 msg = self.session.msg('object_info_request', content)
282 self._queue_request(msg)
281 self._queue_request(msg)
283 return msg['header']['msg_id']
282 return msg['header']['msg_id']
284
283
285 def history(self, index=None, raw=False, output=True):
284 def history(self, index=None, raw=False, output=True):
286 """Get the history list.
285 """Get the history list.
287
286
288 Parameters
287 Parameters
289 ----------
288 ----------
290 index : n or (n1, n2) or None
289 index : n or (n1, n2) or None
291 If n, then the last entries. If a tuple, then all in
290 If n, then the last entries. If a tuple, then all in
292 range(n1, n2). If None, then all entries. Raises IndexError if
291 range(n1, n2). If None, then all entries. Raises IndexError if
293 the format of index is incorrect.
292 the format of index is incorrect.
294 raw : bool
293 raw : bool
295 If True, return the raw input.
294 If True, return the raw input.
296 output : bool
295 output : bool
297 If True, then return the output as well.
296 If True, then return the output as well.
298
297
299 Returns
298 Returns
300 -------
299 -------
301 The msg_id of the message sent.
300 The msg_id of the message sent.
302 """
301 """
303 content = dict(index=index, raw=raw, output=output)
302 content = dict(index=index, raw=raw, output=output)
304 msg = self.session.msg('history_request', content)
303 msg = self.session.msg('history_request', content)
305 self._queue_request(msg)
304 self._queue_request(msg)
306 return msg['header']['msg_id']
305 return msg['header']['msg_id']
307
306
308 def shutdown(self, restart=False):
307 def shutdown(self, restart=False):
309 """Request an immediate kernel shutdown.
308 """Request an immediate kernel shutdown.
310
309
311 Upon receipt of the (empty) reply, client code can safely assume that
310 Upon receipt of the (empty) reply, client code can safely assume that
312 the kernel has shut down and it's safe to forcefully terminate it if
311 the kernel has shut down and it's safe to forcefully terminate it if
313 it's still alive.
312 it's still alive.
314
313
315 The kernel will send the reply via a function registered with Python's
314 The kernel will send the reply via a function registered with Python's
316 atexit module, ensuring it's truly done as the kernel is done with all
315 atexit module, ensuring it's truly done as the kernel is done with all
317 normal operation.
316 normal operation.
318 """
317 """
319 # Send quit message to kernel. Once we implement kernel-side setattr,
318 # Send quit message to kernel. Once we implement kernel-side setattr,
320 # this should probably be done that way, but for now this will do.
319 # this should probably be done that way, but for now this will do.
321 msg = self.session.msg('shutdown_request', {'restart':restart})
320 msg = self.session.msg('shutdown_request', {'restart':restart})
322 self._queue_request(msg)
321 self._queue_request(msg)
323 return msg['header']['msg_id']
322 return msg['header']['msg_id']
324
323
325 def _handle_events(self, socket, events):
324 def _handle_events(self, socket, events):
326 if events & POLLERR:
325 if events & POLLERR:
327 self._handle_err()
326 self._handle_err()
328 if events & POLLOUT:
327 if events & POLLOUT:
329 self._handle_send()
328 self._handle_send()
330 if events & POLLIN:
329 if events & POLLIN:
331 self._handle_recv()
330 self._handle_recv()
332
331
333 def _handle_recv(self):
332 def _handle_recv(self):
334 msg = self.socket.recv_json()
333 msg = self.socket.recv_json()
335 self.call_handlers(msg)
334 self.call_handlers(msg)
336
335
337 def _handle_send(self):
336 def _handle_send(self):
338 try:
337 try:
339 msg = self.command_queue.get(False)
338 msg = self.command_queue.get(False)
340 except Empty:
339 except Empty:
341 pass
340 pass
342 else:
341 else:
343 self.socket.send_json(msg)
342 self.socket.send_json(msg)
344 if self.command_queue.empty():
343 if self.command_queue.empty():
345 self.drop_io_state(POLLOUT)
344 self.drop_io_state(POLLOUT)
346
345
347 def _handle_err(self):
346 def _handle_err(self):
348 # We don't want to let this go silently, so eventually we should log.
347 # We don't want to let this go silently, so eventually we should log.
349 raise zmq.ZMQError()
348 raise zmq.ZMQError()
350
349
351 def _queue_request(self, msg):
350 def _queue_request(self, msg):
352 self.command_queue.put(msg)
351 self.command_queue.put(msg)
353 self.add_io_state(POLLOUT)
352 self.add_io_state(POLLOUT)
354
353
355
354
356 class SubSocketChannel(ZmqSocketChannel):
355 class SubSocketChannel(ZmqSocketChannel):
357 """The SUB channel which listens for messages that the kernel publishes.
356 """The SUB channel which listens for messages that the kernel publishes.
358 """
357 """
359
358
360 def __init__(self, context, session, address):
359 def __init__(self, context, session, address):
361 super(SubSocketChannel, self).__init__(context, session, address)
360 super(SubSocketChannel, self).__init__(context, session, address)
362 self.ioloop = ioloop.IOLoop()
361 self.ioloop = ioloop.IOLoop()
363
362
364 def run(self):
363 def run(self):
365 """The thread's main activity. Call start() instead."""
364 """The thread's main activity. Call start() instead."""
366 self.socket = self.context.socket(zmq.SUB)
365 self.socket = self.context.socket(zmq.SUB)
367 self.socket.setsockopt(zmq.SUBSCRIBE,'')
366 self.socket.setsockopt(zmq.SUBSCRIBE,'')
368 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
367 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
369 self.socket.connect('tcp://%s:%i' % self.address)
368 self.socket.connect('tcp://%s:%i' % self.address)
370 self.iostate = POLLIN|POLLERR
369 self.iostate = POLLIN|POLLERR
371 self.ioloop.add_handler(self.socket, self._handle_events,
370 self.ioloop.add_handler(self.socket, self._handle_events,
372 self.iostate)
371 self.iostate)
373 self.ioloop.start()
372 self.ioloop.start()
374
373
375 def stop(self):
374 def stop(self):
376 self.ioloop.stop()
375 self.ioloop.stop()
377 super(SubSocketChannel, self).stop()
376 super(SubSocketChannel, self).stop()
378
377
379 def call_handlers(self, msg):
378 def call_handlers(self, msg):
380 """This method is called in the ioloop thread when a message arrives.
379 """This method is called in the ioloop thread when a message arrives.
381
380
382 Subclasses should override this method to handle incoming messages.
381 Subclasses should override this method to handle incoming messages.
383 It is important to remember that this method is called in the thread
382 It is important to remember that this method is called in the thread
384 so that some logic must be done to ensure that the application leve
383 so that some logic must be done to ensure that the application leve
385 handlers are called in the application thread.
384 handlers are called in the application thread.
386 """
385 """
387 raise NotImplementedError('call_handlers must be defined in a subclass.')
386 raise NotImplementedError('call_handlers must be defined in a subclass.')
388
387
389 def flush(self, timeout=1.0):
388 def flush(self, timeout=1.0):
390 """Immediately processes all pending messages on the SUB channel.
389 """Immediately processes all pending messages on the SUB channel.
391
390
392 Callers should use this method to ensure that :method:`call_handlers`
391 Callers should use this method to ensure that :method:`call_handlers`
393 has been called for all messages that have been received on the
392 has been called for all messages that have been received on the
394 0MQ SUB socket of this channel.
393 0MQ SUB socket of this channel.
395
394
396 This method is thread safe.
395 This method is thread safe.
397
396
398 Parameters
397 Parameters
399 ----------
398 ----------
400 timeout : float, optional
399 timeout : float, optional
401 The maximum amount of time to spend flushing, in seconds. The
400 The maximum amount of time to spend flushing, in seconds. The
402 default is one second.
401 default is one second.
403 """
402 """
404 # We do the IOLoop callback process twice to ensure that the IOLoop
403 # We do the IOLoop callback process twice to ensure that the IOLoop
405 # gets to perform at least one full poll.
404 # gets to perform at least one full poll.
406 stop_time = time.time() + timeout
405 stop_time = time.time() + timeout
407 for i in xrange(2):
406 for i in xrange(2):
408 self._flushed = False
407 self._flushed = False
409 self.ioloop.add_callback(self._flush)
408 self.ioloop.add_callback(self._flush)
410 while not self._flushed and time.time() < stop_time:
409 while not self._flushed and time.time() < stop_time:
411 time.sleep(0.01)
410 time.sleep(0.01)
412
411
413 def _handle_events(self, socket, events):
412 def _handle_events(self, socket, events):
414 # Turn on and off POLLOUT depending on if we have made a request
413 # Turn on and off POLLOUT depending on if we have made a request
415 if events & POLLERR:
414 if events & POLLERR:
416 self._handle_err()
415 self._handle_err()
417 if events & POLLIN:
416 if events & POLLIN:
418 self._handle_recv()
417 self._handle_recv()
419
418
420 def _handle_err(self):
419 def _handle_err(self):
421 # We don't want to let this go silently, so eventually we should log.
420 # We don't want to let this go silently, so eventually we should log.
422 raise zmq.ZMQError()
421 raise zmq.ZMQError()
423
422
424 def _handle_recv(self):
423 def _handle_recv(self):
425 # Get all of the messages we can
424 # Get all of the messages we can
426 while True:
425 while True:
427 try:
426 try:
428 msg = self.socket.recv_json(zmq.NOBLOCK)
427 msg = self.socket.recv_json(zmq.NOBLOCK)
429 except zmq.ZMQError:
428 except zmq.ZMQError:
430 # Check the errno?
429 # Check the errno?
431 # Will this trigger POLLERR?
430 # Will this trigger POLLERR?
432 break
431 break
433 else:
432 else:
434 self.call_handlers(msg)
433 self.call_handlers(msg)
435
434
436 def _flush(self):
435 def _flush(self):
437 """Callback for :method:`self.flush`."""
436 """Callback for :method:`self.flush`."""
438 self._flushed = True
437 self._flushed = True
439
438
440
439
441 class RepSocketChannel(ZmqSocketChannel):
440 class RepSocketChannel(ZmqSocketChannel):
442 """A reply channel to handle raw_input requests that the kernel makes."""
441 """A reply channel to handle raw_input requests that the kernel makes."""
443
442
444 msg_queue = None
443 msg_queue = None
445
444
446 def __init__(self, context, session, address):
445 def __init__(self, context, session, address):
447 super(RepSocketChannel, self).__init__(context, session, address)
446 super(RepSocketChannel, self).__init__(context, session, address)
448 self.ioloop = ioloop.IOLoop()
447 self.ioloop = ioloop.IOLoop()
449 self.msg_queue = Queue()
448 self.msg_queue = Queue()
450
449
451 def run(self):
450 def run(self):
452 """The thread's main activity. Call start() instead."""
451 """The thread's main activity. Call start() instead."""
453 self.socket = self.context.socket(zmq.XREQ)
452 self.socket = self.context.socket(zmq.XREQ)
454 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
453 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
455 self.socket.connect('tcp://%s:%i' % self.address)
454 self.socket.connect('tcp://%s:%i' % self.address)
456 self.iostate = POLLERR|POLLIN
455 self.iostate = POLLERR|POLLIN
457 self.ioloop.add_handler(self.socket, self._handle_events,
456 self.ioloop.add_handler(self.socket, self._handle_events,
458 self.iostate)
457 self.iostate)
459 self.ioloop.start()
458 self.ioloop.start()
460
459
461 def stop(self):
460 def stop(self):
462 self.ioloop.stop()
461 self.ioloop.stop()
463 super(RepSocketChannel, self).stop()
462 super(RepSocketChannel, self).stop()
464
463
465 def call_handlers(self, msg):
464 def call_handlers(self, msg):
466 """This method is called in the ioloop thread when a message arrives.
465 """This method is called in the ioloop thread when a message arrives.
467
466
468 Subclasses should override this method to handle incoming messages.
467 Subclasses should override this method to handle incoming messages.
469 It is important to remember that this method is called in the thread
468 It is important to remember that this method is called in the thread
470 so that some logic must be done to ensure that the application leve
469 so that some logic must be done to ensure that the application leve
471 handlers are called in the application thread.
470 handlers are called in the application thread.
472 """
471 """
473 raise NotImplementedError('call_handlers must be defined in a subclass.')
472 raise NotImplementedError('call_handlers must be defined in a subclass.')
474
473
475 def input(self, string):
474 def input(self, string):
476 """Send a string of raw input to the kernel."""
475 """Send a string of raw input to the kernel."""
477 content = dict(value=string)
476 content = dict(value=string)
478 msg = self.session.msg('input_reply', content)
477 msg = self.session.msg('input_reply', content)
479 self._queue_reply(msg)
478 self._queue_reply(msg)
480
479
481 def _handle_events(self, socket, events):
480 def _handle_events(self, socket, events):
482 if events & POLLERR:
481 if events & POLLERR:
483 self._handle_err()
482 self._handle_err()
484 if events & POLLOUT:
483 if events & POLLOUT:
485 self._handle_send()
484 self._handle_send()
486 if events & POLLIN:
485 if events & POLLIN:
487 self._handle_recv()
486 self._handle_recv()
488
487
489 def _handle_recv(self):
488 def _handle_recv(self):
490 msg = self.socket.recv_json()
489 msg = self.socket.recv_json()
491 self.call_handlers(msg)
490 self.call_handlers(msg)
492
491
493 def _handle_send(self):
492 def _handle_send(self):
494 try:
493 try:
495 msg = self.msg_queue.get(False)
494 msg = self.msg_queue.get(False)
496 except Empty:
495 except Empty:
497 pass
496 pass
498 else:
497 else:
499 self.socket.send_json(msg)
498 self.socket.send_json(msg)
500 if self.msg_queue.empty():
499 if self.msg_queue.empty():
501 self.drop_io_state(POLLOUT)
500 self.drop_io_state(POLLOUT)
502
501
503 def _handle_err(self):
502 def _handle_err(self):
504 # We don't want to let this go silently, so eventually we should log.
503 # We don't want to let this go silently, so eventually we should log.
505 raise zmq.ZMQError()
504 raise zmq.ZMQError()
506
505
507 def _queue_reply(self, msg):
506 def _queue_reply(self, msg):
508 self.msg_queue.put(msg)
507 self.msg_queue.put(msg)
509 self.add_io_state(POLLOUT)
508 self.add_io_state(POLLOUT)
510
509
511
510
512 class HBSocketChannel(ZmqSocketChannel):
511 class HBSocketChannel(ZmqSocketChannel):
513 """The heartbeat channel which monitors the kernel heartbeat.
512 """The heartbeat channel which monitors the kernel heartbeat.
514
513
515 Note that the heartbeat channel is paused by default. As long as you start
514 Note that the heartbeat channel is paused by default. As long as you start
516 this channel, the kernel manager will ensure that it is paused and un-paused
515 this channel, the kernel manager will ensure that it is paused and un-paused
517 as appropriate.
516 as appropriate.
518 """
517 """
519
518
520 time_to_dead = 3.0
519 time_to_dead = 3.0
521 socket = None
520 socket = None
522 poller = None
521 poller = None
523 _running = None
522 _running = None
524 _pause = None
523 _pause = None
525
524
526 def __init__(self, context, session, address):
525 def __init__(self, context, session, address):
527 super(HBSocketChannel, self).__init__(context, session, address)
526 super(HBSocketChannel, self).__init__(context, session, address)
528 self._running = False
527 self._running = False
529 self._pause = True
528 self._pause = True
530
529
531 def _create_socket(self):
530 def _create_socket(self):
532 self.socket = self.context.socket(zmq.REQ)
531 self.socket = self.context.socket(zmq.REQ)
533 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
532 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
534 self.socket.connect('tcp://%s:%i' % self.address)
533 self.socket.connect('tcp://%s:%i' % self.address)
535 self.poller = zmq.Poller()
534 self.poller = zmq.Poller()
536 self.poller.register(self.socket, zmq.POLLIN)
535 self.poller.register(self.socket, zmq.POLLIN)
537
536
538 def run(self):
537 def run(self):
539 """The thread's main activity. Call start() instead."""
538 """The thread's main activity. Call start() instead."""
540 self._create_socket()
539 self._create_socket()
541 self._running = True
540 self._running = True
542 while self._running:
541 while self._running:
543 if self._pause:
542 if self._pause:
544 time.sleep(self.time_to_dead)
543 time.sleep(self.time_to_dead)
545 else:
544 else:
546 since_last_heartbeat = 0.0
545 since_last_heartbeat = 0.0
547 request_time = time.time()
546 request_time = time.time()
548 try:
547 try:
549 #io.rprint('Ping from HB channel') # dbg
548 #io.rprint('Ping from HB channel') # dbg
550 self.socket.send_json('ping')
549 self.socket.send_json('ping')
551 except zmq.ZMQError, e:
550 except zmq.ZMQError, e:
552 #io.rprint('*** HB Error:', e) # dbg
551 #io.rprint('*** HB Error:', e) # dbg
553 if e.errno == zmq.EFSM:
552 if e.errno == zmq.EFSM:
554 #io.rprint('sleep...', self.time_to_dead) # dbg
553 #io.rprint('sleep...', self.time_to_dead) # dbg
555 time.sleep(self.time_to_dead)
554 time.sleep(self.time_to_dead)
556 self._create_socket()
555 self._create_socket()
557 else:
556 else:
558 raise
557 raise
559 else:
558 else:
560 while True:
559 while True:
561 try:
560 try:
562 self.socket.recv_json(zmq.NOBLOCK)
561 self.socket.recv_json(zmq.NOBLOCK)
563 except zmq.ZMQError, e:
562 except zmq.ZMQError, e:
564 #io.rprint('*** HB Error 2:', e) # dbg
563 #io.rprint('*** HB Error 2:', e) # dbg
565 if e.errno == zmq.EAGAIN:
564 if e.errno == zmq.EAGAIN:
566 before_poll = time.time()
565 before_poll = time.time()
567 until_dead = self.time_to_dead - (before_poll -
566 until_dead = self.time_to_dead - (before_poll -
568 request_time)
567 request_time)
569
568
570 # When the return value of poll() is an empty
569 # When the return value of poll() is an empty
571 # list, that is when things have gone wrong
570 # list, that is when things have gone wrong
572 # (zeromq bug). As long as it is not an empty
571 # (zeromq bug). As long as it is not an empty
573 # list, poll is working correctly even if it
572 # list, poll is working correctly even if it
574 # returns quickly. Note: poll timeout is in
573 # returns quickly. Note: poll timeout is in
575 # milliseconds.
574 # milliseconds.
576 self.poller.poll(1000*until_dead)
575 self.poller.poll(1000*until_dead)
577
576
578 since_last_heartbeat = time.time()-request_time
577 since_last_heartbeat = time.time()-request_time
579 if since_last_heartbeat > self.time_to_dead:
578 if since_last_heartbeat > self.time_to_dead:
580 self.call_handlers(since_last_heartbeat)
579 self.call_handlers(since_last_heartbeat)
581 break
580 break
582 else:
581 else:
583 # FIXME: We should probably log this instead.
582 # FIXME: We should probably log this instead.
584 raise
583 raise
585 else:
584 else:
586 until_dead = self.time_to_dead - (time.time() -
585 until_dead = self.time_to_dead - (time.time() -
587 request_time)
586 request_time)
588 if until_dead > 0.0:
587 if until_dead > 0.0:
589 #io.rprint('sleep...', self.time_to_dead) # dbg
588 #io.rprint('sleep...', self.time_to_dead) # dbg
590 time.sleep(until_dead)
589 time.sleep(until_dead)
591 break
590 break
592
591
593 def pause(self):
592 def pause(self):
594 """Pause the heartbeat."""
593 """Pause the heartbeat."""
595 self._pause = True
594 self._pause = True
596
595
597 def unpause(self):
596 def unpause(self):
598 """Unpause the heartbeat."""
597 """Unpause the heartbeat."""
599 self._pause = False
598 self._pause = False
600
599
601 def is_beating(self):
600 def is_beating(self):
602 """Is the heartbeat running and not paused."""
601 """Is the heartbeat running and not paused."""
603 if self.is_alive() and not self._pause:
602 if self.is_alive() and not self._pause:
604 return True
603 return True
605 else:
604 else:
606 return False
605 return False
607
606
608 def stop(self):
607 def stop(self):
609 self._running = False
608 self._running = False
610 super(HBSocketChannel, self).stop()
609 super(HBSocketChannel, self).stop()
611
610
612 def call_handlers(self, since_last_heartbeat):
611 def call_handlers(self, since_last_heartbeat):
613 """This method is called in the ioloop thread when a message arrives.
612 """This method is called in the ioloop thread when a message arrives.
614
613
615 Subclasses should override this method to handle incoming messages.
614 Subclasses should override this method to handle incoming messages.
616 It is important to remember that this method is called in the thread
615 It is important to remember that this method is called in the thread
617 so that some logic must be done to ensure that the application leve
616 so that some logic must be done to ensure that the application leve
618 handlers are called in the application thread.
617 handlers are called in the application thread.
619 """
618 """
620 raise NotImplementedError('call_handlers must be defined in a subclass.')
619 raise NotImplementedError('call_handlers must be defined in a subclass.')
621
620
622
621
623 #-----------------------------------------------------------------------------
622 #-----------------------------------------------------------------------------
624 # Main kernel manager class
623 # Main kernel manager class
625 #-----------------------------------------------------------------------------
624 #-----------------------------------------------------------------------------
626
625
627 class KernelManager(HasTraits):
626 class KernelManager(HasTraits):
628 """ Manages a kernel for a frontend.
627 """ Manages a kernel for a frontend.
629
628
630 The SUB channel is for the frontend to receive messages published by the
629 The SUB channel is for the frontend to receive messages published by the
631 kernel.
630 kernel.
632
631
633 The REQ channel is for the frontend to make requests of the kernel.
632 The REQ channel is for the frontend to make requests of the kernel.
634
633
635 The REP channel is for the kernel to request stdin (raw_input) from the
634 The REP channel is for the kernel to request stdin (raw_input) from the
636 frontend.
635 frontend.
637 """
636 """
638 # The PyZMQ Context to use for communication with the kernel.
637 # The PyZMQ Context to use for communication with the kernel.
639 context = Instance(zmq.Context,(),{})
638 context = Instance(zmq.Context,(),{})
640
639
641 # The Session to use for communication with the kernel.
640 # The Session to use for communication with the kernel.
642 session = Instance(Session,(),{})
641 session = Instance(Session,(),{})
643
642
644 # The kernel process with which the KernelManager is communicating.
643 # The kernel process with which the KernelManager is communicating.
645 kernel = Instance(Popen)
644 kernel = Instance(Popen)
646
645
647 # The addresses for the communication channels.
646 # The addresses for the communication channels.
648 xreq_address = TCPAddress((LOCALHOST, 0))
647 xreq_address = TCPAddress((LOCALHOST, 0))
649 sub_address = TCPAddress((LOCALHOST, 0))
648 sub_address = TCPAddress((LOCALHOST, 0))
650 rep_address = TCPAddress((LOCALHOST, 0))
649 rep_address = TCPAddress((LOCALHOST, 0))
651 hb_address = TCPAddress((LOCALHOST, 0))
650 hb_address = TCPAddress((LOCALHOST, 0))
652
651
653 # The classes to use for the various channels.
652 # The classes to use for the various channels.
654 xreq_channel_class = Type(XReqSocketChannel)
653 xreq_channel_class = Type(XReqSocketChannel)
655 sub_channel_class = Type(SubSocketChannel)
654 sub_channel_class = Type(SubSocketChannel)
656 rep_channel_class = Type(RepSocketChannel)
655 rep_channel_class = Type(RepSocketChannel)
657 hb_channel_class = Type(HBSocketChannel)
656 hb_channel_class = Type(HBSocketChannel)
658
657
659 # Protected traits.
658 # Protected traits.
660 _launch_args = Any
659 _launch_args = Any
661 _xreq_channel = Any
660 _xreq_channel = Any
662 _sub_channel = Any
661 _sub_channel = Any
663 _rep_channel = Any
662 _rep_channel = Any
664 _hb_channel = Any
663 _hb_channel = Any
665
664
666 def __init__(self, **kwargs):
665 def __init__(self, **kwargs):
667 super(KernelManager, self).__init__(**kwargs)
666 super(KernelManager, self).__init__(**kwargs)
668 # Uncomment this to try closing the context.
667 # Uncomment this to try closing the context.
669 # atexit.register(self.context.close)
668 # atexit.register(self.context.close)
670
669
671 #--------------------------------------------------------------------------
670 #--------------------------------------------------------------------------
672 # Channel management methods:
671 # Channel management methods:
673 #--------------------------------------------------------------------------
672 #--------------------------------------------------------------------------
674
673
675 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
674 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
676 """Starts the channels for this kernel.
675 """Starts the channels for this kernel.
677
676
678 This will create the channels if they do not exist and then start
677 This will create the channels if they do not exist and then start
679 them. If port numbers of 0 are being used (random ports) then you
678 them. If port numbers of 0 are being used (random ports) then you
680 must first call :method:`start_kernel`. If the channels have been
679 must first call :method:`start_kernel`. If the channels have been
681 stopped and you call this, :class:`RuntimeError` will be raised.
680 stopped and you call this, :class:`RuntimeError` will be raised.
682 """
681 """
683 if xreq:
682 if xreq:
684 self.xreq_channel.start()
683 self.xreq_channel.start()
685 if sub:
684 if sub:
686 self.sub_channel.start()
685 self.sub_channel.start()
687 if rep:
686 if rep:
688 self.rep_channel.start()
687 self.rep_channel.start()
689 if hb:
688 if hb:
690 self.hb_channel.start()
689 self.hb_channel.start()
691
690
692 def stop_channels(self):
691 def stop_channels(self):
693 """Stops all the running channels for this kernel.
692 """Stops all the running channels for this kernel.
694 """
693 """
695 if self.xreq_channel.is_alive():
694 if self.xreq_channel.is_alive():
696 self.xreq_channel.stop()
695 self.xreq_channel.stop()
697 if self.sub_channel.is_alive():
696 if self.sub_channel.is_alive():
698 self.sub_channel.stop()
697 self.sub_channel.stop()
699 if self.rep_channel.is_alive():
698 if self.rep_channel.is_alive():
700 self.rep_channel.stop()
699 self.rep_channel.stop()
701 if self.hb_channel.is_alive():
700 if self.hb_channel.is_alive():
702 self.hb_channel.stop()
701 self.hb_channel.stop()
703
702
704 @property
703 @property
705 def channels_running(self):
704 def channels_running(self):
706 """Are any of the channels created and running?"""
705 """Are any of the channels created and running?"""
707 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
706 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
708 self.rep_channel.is_alive() or self.hb_channel.is_alive())
707 self.rep_channel.is_alive() or self.hb_channel.is_alive())
709
708
710 #--------------------------------------------------------------------------
709 #--------------------------------------------------------------------------
711 # Kernel process management methods:
710 # Kernel process management methods:
712 #--------------------------------------------------------------------------
711 #--------------------------------------------------------------------------
713
712
714 def start_kernel(self, **kw):
713 def start_kernel(self, **kw):
715 """Starts a kernel process and configures the manager to use it.
714 """Starts a kernel process and configures the manager to use it.
716
715
717 If random ports (port=0) are being used, this method must be called
716 If random ports (port=0) are being used, this method must be called
718 before the channels are created.
717 before the channels are created.
719
718
720 Parameters:
719 Parameters:
721 -----------
720 -----------
722 ipython : bool, optional (default True)
721 ipython : bool, optional (default True)
723 Whether to use an IPython kernel instead of a plain Python kernel.
722 Whether to use an IPython kernel instead of a plain Python kernel.
724 """
723 """
725 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
724 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
726 self.rep_address, self.hb_address
725 self.rep_address, self.hb_address
727 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
726 if xreq[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
728 rep[0] != LOCALHOST or hb[0] != LOCALHOST:
727 rep[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
729 raise RuntimeError("Can only launch a kernel on localhost."
728 raise RuntimeError("Can only launch a kernel on a local interface. "
730 "Make sure that the '*_address' attributes are "
729 "Make sure that the '*_address' attributes are "
731 "configured properly.")
730 "configured properly. "
732
731 "Currently valid addresses are: %s"%LOCAL_IPS
732 )
733
733 self._launch_args = kw.copy()
734 self._launch_args = kw.copy()
734 if kw.pop('ipython', True):
735 if kw.pop('ipython', True):
735 from ipkernel import launch_kernel
736 from ipkernel import launch_kernel
736 else:
737 else:
737 from pykernel import launch_kernel
738 from pykernel import launch_kernel
738 self.kernel, xrep, pub, req, hb = launch_kernel(
739 self.kernel, xrep, pub, req, _hb = launch_kernel(
739 xrep_port=xreq[1], pub_port=sub[1],
740 xrep_port=xreq[1], pub_port=sub[1],
740 req_port=rep[1], hb_port=hb[1], **kw)
741 req_port=rep[1], hb_port=hb[1], **kw)
741 self.xreq_address = (LOCALHOST, xrep)
742 self.xreq_address = (xreq[0], xrep)
742 self.sub_address = (LOCALHOST, pub)
743 self.sub_address = (sub[0], pub)
743 self.rep_address = (LOCALHOST, req)
744 self.rep_address = (rep[0], req)
744 self.hb_address = (LOCALHOST, hb)
745 self.hb_address = (hb[0], _hb)
745
746
746 def shutdown_kernel(self, restart=False):
747 def shutdown_kernel(self, restart=False):
747 """ Attempts to the stop the kernel process cleanly. If the kernel
748 """ Attempts to the stop the kernel process cleanly. If the kernel
748 cannot be stopped, it is killed, if possible.
749 cannot be stopped, it is killed, if possible.
749 """
750 """
750 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
751 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
751 if sys.platform == 'win32':
752 if sys.platform == 'win32':
752 self.kill_kernel()
753 self.kill_kernel()
753 return
754 return
754
755
755 # Pause the heart beat channel if it exists.
756 # Pause the heart beat channel if it exists.
756 if self._hb_channel is not None:
757 if self._hb_channel is not None:
757 self._hb_channel.pause()
758 self._hb_channel.pause()
758
759
759 # Don't send any additional kernel kill messages immediately, to give
760 # Don't send any additional kernel kill messages immediately, to give
760 # the kernel a chance to properly execute shutdown actions. Wait for at
761 # the kernel a chance to properly execute shutdown actions. Wait for at
761 # most 1s, checking every 0.1s.
762 # most 1s, checking every 0.1s.
762 self.xreq_channel.shutdown(restart=restart)
763 self.xreq_channel.shutdown(restart=restart)
763 for i in range(10):
764 for i in range(10):
764 if self.is_alive:
765 if self.is_alive:
765 time.sleep(0.1)
766 time.sleep(0.1)
766 else:
767 else:
767 break
768 break
768 else:
769 else:
769 # OK, we've waited long enough.
770 # OK, we've waited long enough.
770 if self.has_kernel:
771 if self.has_kernel:
771 self.kill_kernel()
772 self.kill_kernel()
772
773
773 def restart_kernel(self, now=False):
774 def restart_kernel(self, now=False):
774 """Restarts a kernel with the same arguments that were used to launch
775 """Restarts a kernel with the same arguments that were used to launch
775 it. If the old kernel was launched with random ports, the same ports
776 it. If the old kernel was launched with random ports, the same ports
776 will be used for the new kernel.
777 will be used for the new kernel.
777
778
778 Parameters
779 Parameters
779 ----------
780 ----------
780 now : bool, optional
781 now : bool, optional
781 If True, the kernel is forcefully restarted *immediately*, without
782 If True, the kernel is forcefully restarted *immediately*, without
782 having a chance to do any cleanup action. Otherwise the kernel is
783 having a chance to do any cleanup action. Otherwise the kernel is
783 given 1s to clean up before a forceful restart is issued.
784 given 1s to clean up before a forceful restart is issued.
784
785
785 In all cases the kernel is restarted, the only difference is whether
786 In all cases the kernel is restarted, the only difference is whether
786 it is given a chance to perform a clean shutdown or not.
787 it is given a chance to perform a clean shutdown or not.
787 """
788 """
788 if self._launch_args is None:
789 if self._launch_args is None:
789 raise RuntimeError("Cannot restart the kernel. "
790 raise RuntimeError("Cannot restart the kernel. "
790 "No previous call to 'start_kernel'.")
791 "No previous call to 'start_kernel'.")
791 else:
792 else:
792 if self.has_kernel:
793 if self.has_kernel:
793 if now:
794 if now:
794 self.kill_kernel()
795 self.kill_kernel()
795 else:
796 else:
796 self.shutdown_kernel(restart=True)
797 self.shutdown_kernel(restart=True)
797 self.start_kernel(**self._launch_args)
798 self.start_kernel(**self._launch_args)
798
799
799 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
800 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
800 # unless there is some delay here.
801 # unless there is some delay here.
801 if sys.platform == 'win32':
802 if sys.platform == 'win32':
802 time.sleep(0.2)
803 time.sleep(0.2)
803
804
804 @property
805 @property
805 def has_kernel(self):
806 def has_kernel(self):
806 """Returns whether a kernel process has been specified for the kernel
807 """Returns whether a kernel process has been specified for the kernel
807 manager.
808 manager.
808 """
809 """
809 return self.kernel is not None
810 return self.kernel is not None
810
811
811 def kill_kernel(self):
812 def kill_kernel(self):
812 """ Kill the running kernel. """
813 """ Kill the running kernel. """
813 if self.has_kernel:
814 if self.has_kernel:
814 # Pause the heart beat channel if it exists.
815 # Pause the heart beat channel if it exists.
815 if self._hb_channel is not None:
816 if self._hb_channel is not None:
816 self._hb_channel.pause()
817 self._hb_channel.pause()
817
818
818 # Attempt to kill the kernel.
819 # Attempt to kill the kernel.
819 try:
820 try:
820 self.kernel.kill()
821 self.kernel.kill()
821 except OSError, e:
822 except OSError, e:
822 # In Windows, we will get an Access Denied error if the process
823 # In Windows, we will get an Access Denied error if the process
823 # has already terminated. Ignore it.
824 # has already terminated. Ignore it.
824 if not (sys.platform == 'win32' and e.winerror == 5):
825 if not (sys.platform == 'win32' and e.winerror == 5):
825 raise
826 raise
826 self.kernel = None
827 self.kernel = None
827 else:
828 else:
828 raise RuntimeError("Cannot kill kernel. No kernel is running!")
829 raise RuntimeError("Cannot kill kernel. No kernel is running!")
829
830
830 def interrupt_kernel(self):
831 def interrupt_kernel(self):
831 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
832 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
832 well supported on all platforms.
833 well supported on all platforms.
833 """
834 """
834 if self.has_kernel:
835 if self.has_kernel:
835 if sys.platform == 'win32':
836 if sys.platform == 'win32':
836 from parentpoller import ParentPollerWindows as Poller
837 from parentpoller import ParentPollerWindows as Poller
837 Poller.send_interrupt(self.kernel.win32_interrupt_event)
838 Poller.send_interrupt(self.kernel.win32_interrupt_event)
838 else:
839 else:
839 self.kernel.send_signal(signal.SIGINT)
840 self.kernel.send_signal(signal.SIGINT)
840 else:
841 else:
841 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
842 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
842
843
843 def signal_kernel(self, signum):
844 def signal_kernel(self, signum):
844 """ Sends a signal to the kernel. Note that since only SIGTERM is
845 """ Sends a signal to the kernel. Note that since only SIGTERM is
845 supported on Windows, this function is only useful on Unix systems.
846 supported on Windows, this function is only useful on Unix systems.
846 """
847 """
847 if self.has_kernel:
848 if self.has_kernel:
848 self.kernel.send_signal(signum)
849 self.kernel.send_signal(signum)
849 else:
850 else:
850 raise RuntimeError("Cannot signal kernel. No kernel is running!")
851 raise RuntimeError("Cannot signal kernel. No kernel is running!")
851
852
852 @property
853 @property
853 def is_alive(self):
854 def is_alive(self):
854 """Is the kernel process still running?"""
855 """Is the kernel process still running?"""
855 # FIXME: not using a heartbeat means this method is broken for any
856 # FIXME: not using a heartbeat means this method is broken for any
856 # remote kernel, it's only capable of handling local kernels.
857 # remote kernel, it's only capable of handling local kernels.
857 if self.has_kernel:
858 if self.has_kernel:
858 if self.kernel.poll() is None:
859 if self.kernel.poll() is None:
859 return True
860 return True
860 else:
861 else:
861 return False
862 return False
862 else:
863 else:
863 # We didn't start the kernel with this KernelManager so we don't
864 # We didn't start the kernel with this KernelManager so we don't
864 # know if it is running. We should use a heartbeat for this case.
865 # know if it is running. We should use a heartbeat for this case.
865 return True
866 return True
866
867
867 #--------------------------------------------------------------------------
868 #--------------------------------------------------------------------------
868 # Channels used for communication with the kernel:
869 # Channels used for communication with the kernel:
869 #--------------------------------------------------------------------------
870 #--------------------------------------------------------------------------
870
871
871 @property
872 @property
872 def xreq_channel(self):
873 def xreq_channel(self):
873 """Get the REQ socket channel object to make requests of the kernel."""
874 """Get the REQ socket channel object to make requests of the kernel."""
874 if self._xreq_channel is None:
875 if self._xreq_channel is None:
875 self._xreq_channel = self.xreq_channel_class(self.context,
876 self._xreq_channel = self.xreq_channel_class(self.context,
876 self.session,
877 self.session,
877 self.xreq_address)
878 self.xreq_address)
878 return self._xreq_channel
879 return self._xreq_channel
879
880
880 @property
881 @property
881 def sub_channel(self):
882 def sub_channel(self):
882 """Get the SUB socket channel object."""
883 """Get the SUB socket channel object."""
883 if self._sub_channel is None:
884 if self._sub_channel is None:
884 self._sub_channel = self.sub_channel_class(self.context,
885 self._sub_channel = self.sub_channel_class(self.context,
885 self.session,
886 self.session,
886 self.sub_address)
887 self.sub_address)
887 return self._sub_channel
888 return self._sub_channel
888
889
889 @property
890 @property
890 def rep_channel(self):
891 def rep_channel(self):
891 """Get the REP socket channel object to handle stdin (raw_input)."""
892 """Get the REP socket channel object to handle stdin (raw_input)."""
892 if self._rep_channel is None:
893 if self._rep_channel is None:
893 self._rep_channel = self.rep_channel_class(self.context,
894 self._rep_channel = self.rep_channel_class(self.context,
894 self.session,
895 self.session,
895 self.rep_address)
896 self.rep_address)
896 return self._rep_channel
897 return self._rep_channel
897
898
898 @property
899 @property
899 def hb_channel(self):
900 def hb_channel(self):
900 """Get the REP socket channel object to handle stdin (raw_input)."""
901 """Get the REP socket channel object to handle stdin (raw_input)."""
901 if self._hb_channel is None:
902 if self._hb_channel is None:
902 self._hb_channel = self.hb_channel_class(self.context,
903 self._hb_channel = self.hb_channel_class(self.context,
903 self.session,
904 self.session,
904 self.hb_address)
905 self.hb_address)
905 return self._hb_channel
906 return self._hb_channel
@@ -1,296 +1,305 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 # Standard library imports.
17 # Standard library imports.
18 import __builtin__
18 import __builtin__
19 from code import CommandCompiler
19 from code import CommandCompiler
20 import sys
20 import sys
21 import time
21 import time
22 import traceback
22 import traceback
23
23
24 # System library imports.
24 # System library imports.
25 import zmq
25 import zmq
26
26
27 # Local imports.
27 # Local imports.
28 from IPython.utils.traitlets import HasTraits, Instance
28 from IPython.utils.traitlets import HasTraits, Instance
29 from completer import KernelCompleter
29 from completer import KernelCompleter
30 from entry_point import base_launch_kernel, make_default_main
30 from entry_point import base_launch_kernel, make_default_main
31 from session import Session, Message
31 from session import Session, Message
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Main kernel class
34 # Main kernel class
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class Kernel(HasTraits):
37 class Kernel(HasTraits):
38
38
39 # Private interface
39 # Private interface
40
40
41 # This is a dict of port number that the kernel is listening on. It is set
41 # This is a dict of port number that the kernel is listening on. It is set
42 # by record_ports and used by connect_request.
42 # by record_ports and used by connect_request.
43 _recorded_ports = None
43 _recorded_ports = None
44
44
45 #---------------------------------------------------------------------------
45 #---------------------------------------------------------------------------
46 # Kernel interface
46 # Kernel interface
47 #---------------------------------------------------------------------------
47 #---------------------------------------------------------------------------
48
48
49 session = Instance(Session)
49 session = Instance(Session)
50 reply_socket = Instance('zmq.Socket')
50 reply_socket = Instance('zmq.Socket')
51 pub_socket = Instance('zmq.Socket')
51 pub_socket = Instance('zmq.Socket')
52 req_socket = Instance('zmq.Socket')
52 req_socket = Instance('zmq.Socket')
53
53
54 def __init__(self, **kwargs):
54 def __init__(self, **kwargs):
55 super(Kernel, self).__init__(**kwargs)
55 super(Kernel, self).__init__(**kwargs)
56 self.user_ns = {}
56 self.user_ns = {}
57 self.history = []
57 self.history = []
58 self.compiler = CommandCompiler()
58 self.compiler = CommandCompiler()
59 self.completer = KernelCompleter(self.user_ns)
59 self.completer = KernelCompleter(self.user_ns)
60
60
61 # Build dict of handlers for message types
61 # Build dict of handlers for message types
62 msg_types = [ 'execute_request', 'complete_request',
62 msg_types = [ 'execute_request', 'complete_request',
63 'object_info_request', 'shutdown_request' ]
63 'object_info_request', 'shutdown_request' ]
64 self.handlers = {}
64 self.handlers = {}
65 for msg_type in msg_types:
65 for msg_type in msg_types:
66 self.handlers[msg_type] = getattr(self, msg_type)
66 self.handlers[msg_type] = getattr(self, msg_type)
67
67
68 def start(self):
68 def start(self):
69 """ Start the kernel main loop.
69 """ Start the kernel main loop.
70 """
70 """
71 while True:
71 while True:
72 ident = self.reply_socket.recv()
72 ident = self.reply_socket.recv()
73 assert self.reply_socket.rcvmore(), "Missing message part."
73 assert self.reply_socket.rcvmore(), "Missing message part."
74 msg = self.reply_socket.recv_json()
74 msg = self.reply_socket.recv_json()
75 omsg = Message(msg)
75 omsg = Message(msg)
76 print>>sys.__stdout__
76 print>>sys.__stdout__
77 print>>sys.__stdout__, omsg
77 print>>sys.__stdout__, omsg
78 handler = self.handlers.get(omsg.msg_type, None)
78 handler = self.handlers.get(omsg.msg_type, None)
79 if handler is None:
79 if handler is None:
80 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
80 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
81 else:
81 else:
82 handler(ident, omsg)
82 handler(ident, omsg)
83
83
84 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
84 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
85 """Record the ports that this kernel is using.
85 """Record the ports that this kernel is using.
86
86
87 The creator of the Kernel instance must call this methods if they
87 The creator of the Kernel instance must call this methods if they
88 want the :meth:`connect_request` method to return the port numbers.
88 want the :meth:`connect_request` method to return the port numbers.
89 """
89 """
90 self._recorded_ports = {
90 self._recorded_ports = {
91 'xrep_port' : xrep_port,
91 'xrep_port' : xrep_port,
92 'pub_port' : pub_port,
92 'pub_port' : pub_port,
93 'req_port' : req_port,
93 'req_port' : req_port,
94 'hb_port' : hb_port
94 'hb_port' : hb_port
95 }
95 }
96
96
97 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
98 # Kernel request handlers
98 # Kernel request handlers
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100
100
101 def execute_request(self, ident, parent):
101 def execute_request(self, ident, parent):
102 try:
102 try:
103 code = parent[u'content'][u'code']
103 code = parent[u'content'][u'code']
104 except:
104 except:
105 print>>sys.__stderr__, "Got bad msg: "
105 print>>sys.__stderr__, "Got bad msg: "
106 print>>sys.__stderr__, Message(parent)
106 print>>sys.__stderr__, Message(parent)
107 return
107 return
108 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
108 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
109 self.pub_socket.send_json(pyin_msg)
109 self.pub_socket.send_json(pyin_msg)
110
110
111 try:
111 try:
112 comp_code = self.compiler(code, '<zmq-kernel>')
112 comp_code = self.compiler(code, '<zmq-kernel>')
113
113
114 # Replace raw_input. Note that is not sufficient to replace
114 # Replace raw_input. Note that is not sufficient to replace
115 # raw_input in the user namespace.
115 # raw_input in the user namespace.
116 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
116 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
117 __builtin__.raw_input = raw_input
117 __builtin__.raw_input = raw_input
118
118
119 # Set the parent message of the display hook and out streams.
119 # Set the parent message of the display hook and out streams.
120 sys.displayhook.set_parent(parent)
120 sys.displayhook.set_parent(parent)
121 sys.stdout.set_parent(parent)
121 sys.stdout.set_parent(parent)
122 sys.stderr.set_parent(parent)
122 sys.stderr.set_parent(parent)
123
123
124 exec comp_code in self.user_ns, self.user_ns
124 exec comp_code in self.user_ns, self.user_ns
125 except:
125 except:
126 etype, evalue, tb = sys.exc_info()
126 etype, evalue, tb = sys.exc_info()
127 tb = traceback.format_exception(etype, evalue, tb)
127 tb = traceback.format_exception(etype, evalue, tb)
128 exc_content = {
128 exc_content = {
129 u'status' : u'error',
129 u'status' : u'error',
130 u'traceback' : tb,
130 u'traceback' : tb,
131 u'ename' : unicode(etype.__name__),
131 u'ename' : unicode(etype.__name__),
132 u'evalue' : unicode(evalue)
132 u'evalue' : unicode(evalue)
133 }
133 }
134 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
134 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
135 self.pub_socket.send_json(exc_msg)
135 self.pub_socket.send_json(exc_msg)
136 reply_content = exc_content
136 reply_content = exc_content
137 else:
137 else:
138 reply_content = { 'status' : 'ok', 'payload' : {} }
138 reply_content = { 'status' : 'ok', 'payload' : {} }
139
139
140 # Flush output before sending the reply.
140 # Flush output before sending the reply.
141 sys.stderr.flush()
141 sys.stderr.flush()
142 sys.stdout.flush()
142 sys.stdout.flush()
143
143
144 # Send the reply.
144 # Send the reply.
145 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
145 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
146 print>>sys.__stdout__, Message(reply_msg)
146 print>>sys.__stdout__, Message(reply_msg)
147 self.reply_socket.send(ident, zmq.SNDMORE)
147 self.reply_socket.send(ident, zmq.SNDMORE)
148 self.reply_socket.send_json(reply_msg)
148 self.reply_socket.send_json(reply_msg)
149 if reply_msg['content']['status'] == u'error':
149 if reply_msg['content']['status'] == u'error':
150 self._abort_queue()
150 self._abort_queue()
151
151
152 def complete_request(self, ident, parent):
152 def complete_request(self, ident, parent):
153 matches = {'matches' : self._complete(parent),
153 matches = {'matches' : self._complete(parent),
154 'status' : 'ok'}
154 'status' : 'ok'}
155 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
155 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
156 matches, parent, ident)
156 matches, parent, ident)
157 print >> sys.__stdout__, completion_msg
157 print >> sys.__stdout__, completion_msg
158
158
159 def object_info_request(self, ident, parent):
159 def object_info_request(self, ident, parent):
160 context = parent['content']['oname'].split('.')
160 context = parent['content']['oname'].split('.')
161 object_info = self._object_info(context)
161 object_info = self._object_info(context)
162 msg = self.session.send(self.reply_socket, 'object_info_reply',
162 msg = self.session.send(self.reply_socket, 'object_info_reply',
163 object_info, parent, ident)
163 object_info, parent, ident)
164 print >> sys.__stdout__, msg
164 print >> sys.__stdout__, msg
165
165
166 def shutdown_request(self, ident, parent):
166 def shutdown_request(self, ident, parent):
167 content = dict(parent['content'])
167 content = dict(parent['content'])
168 msg = self.session.send(self.reply_socket, 'shutdown_reply',
168 msg = self.session.send(self.reply_socket, 'shutdown_reply',
169 content, parent, ident)
169 content, parent, ident)
170 msg = self.session.send(self.pub_socket, 'shutdown_reply',
170 msg = self.session.send(self.pub_socket, 'shutdown_reply',
171 content, parent, ident)
171 content, parent, ident)
172 print >> sys.__stdout__, msg
172 print >> sys.__stdout__, msg
173 time.sleep(0.1)
173 time.sleep(0.1)
174 sys.exit(0)
174 sys.exit(0)
175
175
176 #---------------------------------------------------------------------------
176 #---------------------------------------------------------------------------
177 # Protected interface
177 # Protected interface
178 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
179
179
180 def _abort_queue(self):
180 def _abort_queue(self):
181 while True:
181 while True:
182 try:
182 try:
183 ident = self.reply_socket.recv(zmq.NOBLOCK)
183 ident = self.reply_socket.recv(zmq.NOBLOCK)
184 except zmq.ZMQError, e:
184 except zmq.ZMQError, e:
185 if e.errno == zmq.EAGAIN:
185 if e.errno == zmq.EAGAIN:
186 break
186 break
187 else:
187 else:
188 assert self.reply_socket.rcvmore(), "Missing message part."
188 assert self.reply_socket.rcvmore(), "Missing message part."
189 msg = self.reply_socket.recv_json()
189 msg = self.reply_socket.recv_json()
190 print>>sys.__stdout__, "Aborting:"
190 print>>sys.__stdout__, "Aborting:"
191 print>>sys.__stdout__, Message(msg)
191 print>>sys.__stdout__, Message(msg)
192 msg_type = msg['msg_type']
192 msg_type = msg['msg_type']
193 reply_type = msg_type.split('_')[0] + '_reply'
193 reply_type = msg_type.split('_')[0] + '_reply'
194 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
194 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
195 print>>sys.__stdout__, Message(reply_msg)
195 print>>sys.__stdout__, Message(reply_msg)
196 self.reply_socket.send(ident,zmq.SNDMORE)
196 self.reply_socket.send(ident,zmq.SNDMORE)
197 self.reply_socket.send_json(reply_msg)
197 self.reply_socket.send_json(reply_msg)
198 # We need to wait a bit for requests to come in. This can probably
198 # We need to wait a bit for requests to come in. This can probably
199 # be set shorter for true asynchronous clients.
199 # be set shorter for true asynchronous clients.
200 time.sleep(0.1)
200 time.sleep(0.1)
201
201
202 def _raw_input(self, prompt, ident, parent):
202 def _raw_input(self, prompt, ident, parent):
203 # Flush output before making the request.
203 # Flush output before making the request.
204 sys.stderr.flush()
204 sys.stderr.flush()
205 sys.stdout.flush()
205 sys.stdout.flush()
206
206
207 # Send the input request.
207 # Send the input request.
208 content = dict(prompt=prompt)
208 content = dict(prompt=prompt)
209 msg = self.session.msg(u'input_request', content, parent)
209 msg = self.session.msg(u'input_request', content, parent)
210 self.req_socket.send_json(msg)
210 self.req_socket.send_json(msg)
211
211
212 # Await a response.
212 # Await a response.
213 reply = self.req_socket.recv_json()
213 reply = self.req_socket.recv_json()
214 try:
214 try:
215 value = reply['content']['value']
215 value = reply['content']['value']
216 except:
216 except:
217 print>>sys.__stderr__, "Got bad raw_input reply: "
217 print>>sys.__stderr__, "Got bad raw_input reply: "
218 print>>sys.__stderr__, Message(parent)
218 print>>sys.__stderr__, Message(parent)
219 value = ''
219 value = ''
220 return value
220 return value
221
221
222 def _complete(self, msg):
222 def _complete(self, msg):
223 return self.completer.complete(msg.content.line, msg.content.text)
223 return self.completer.complete(msg.content.line, msg.content.text)
224
224
225 def _object_info(self, context):
225 def _object_info(self, context):
226 symbol, leftover = self._symbol_from_context(context)
226 symbol, leftover = self._symbol_from_context(context)
227 if symbol is not None and not leftover:
227 if symbol is not None and not leftover:
228 doc = getattr(symbol, '__doc__', '')
228 doc = getattr(symbol, '__doc__', '')
229 else:
229 else:
230 doc = ''
230 doc = ''
231 object_info = dict(docstring = doc)
231 object_info = dict(docstring = doc)
232 return object_info
232 return object_info
233
233
234 def _symbol_from_context(self, context):
234 def _symbol_from_context(self, context):
235 if not context:
235 if not context:
236 return None, context
236 return None, context
237
237
238 base_symbol_string = context[0]
238 base_symbol_string = context[0]
239 symbol = self.user_ns.get(base_symbol_string, None)
239 symbol = self.user_ns.get(base_symbol_string, None)
240 if symbol is None:
240 if symbol is None:
241 symbol = __builtin__.__dict__.get(base_symbol_string, None)
241 symbol = __builtin__.__dict__.get(base_symbol_string, None)
242 if symbol is None:
242 if symbol is None:
243 return None, context
243 return None, context
244
244
245 context = context[1:]
245 context = context[1:]
246 for i, name in enumerate(context):
246 for i, name in enumerate(context):
247 new_symbol = getattr(symbol, name, None)
247 new_symbol = getattr(symbol, name, None)
248 if new_symbol is None:
248 if new_symbol is None:
249 return symbol, context[i:]
249 return symbol, context[i:]
250 else:
250 else:
251 symbol = new_symbol
251 symbol = new_symbol
252
252
253 return symbol, []
253 return symbol, []
254
254
255 #-----------------------------------------------------------------------------
255 #-----------------------------------------------------------------------------
256 # Kernel main and launch functions
256 # Kernel main and launch functions
257 #-----------------------------------------------------------------------------
257 #-----------------------------------------------------------------------------
258
258
259 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
259 def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
260 independent=False):
260 independent=False):
261 """ Launches a localhost kernel, binding to the specified ports.
261 """ Launches a localhost kernel, binding to the specified ports.
262
262
263 Parameters
263 Parameters
264 ----------
264 ----------
265 ip : str, optional
266 The ip address the kernel will bind to.
267
265 xrep_port : int, optional
268 xrep_port : int, optional
266 The port to use for XREP channel.
269 The port to use for XREP channel.
267
270
268 pub_port : int, optional
271 pub_port : int, optional
269 The port to use for the SUB channel.
272 The port to use for the SUB channel.
270
273
271 req_port : int, optional
274 req_port : int, optional
272 The port to use for the REQ (raw input) channel.
275 The port to use for the REQ (raw input) channel.
273
276
274 hb_port : int, optional
277 hb_port : int, optional
275 The port to use for the hearbeat REP channel.
278 The port to use for the hearbeat REP channel.
276
279
277 independent : bool, optional (default False)
280 independent : bool, optional (default False)
278 If set, the kernel process is guaranteed to survive if this process
281 If set, the kernel process is guaranteed to survive if this process
279 dies. If not set, an effort is made to ensure that the kernel is killed
282 dies. If not set, an effort is made to ensure that the kernel is killed
280 when this process dies. Note that in this case it is still good practice
283 when this process dies. Note that in this case it is still good practice
281 to kill kernels manually before exiting.
284 to kill kernels manually before exiting.
282
285
283 Returns
286 Returns
284 -------
287 -------
285 A tuple of form:
288 A tuple of form:
286 (kernel_process, xrep_port, pub_port, req_port)
289 (kernel_process, xrep_port, pub_port, req_port)
287 where kernel_process is a Popen object and the ports are integers.
290 where kernel_process is a Popen object and the ports are integers.
288 """
291 """
292 extra_arguments = []
293 if ip is not None:
294 extra_arguments.append('--ip')
295 if isinstance(ip, basestring):
296 extra_arguments.append(ip)
297
289 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
298 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
290 xrep_port, pub_port, req_port, hb_port,
299 xrep_port, pub_port, req_port, hb_port,
291 independent)
300 independent, extra_arguments=extra_arguments)
292
301
293 main = make_default_main(Kernel)
302 main = make_default_main(Kernel)
294
303
295 if __name__ == '__main__':
304 if __name__ == '__main__':
296 main()
305 main()
General Comments 0
You need to be logged in to leave comments. Login now