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