|
45 | 45 | BaseException = Exception |
46 | 46 |
|
47 | 47 | try: |
48 | | - from Queue import Empty # Python 2 |
| 48 | + from Queue import Empty, Queue # Python 2 |
49 | 49 | except ImportError: |
50 | | - from queue import Empty # Python 3 |
| 50 | + from queue import Empty, Queue # Python 3 |
51 | 51 |
|
52 | 52 | DEBUG = os.environ.get('DEBUG_DJAYAMANNE_IPYTHON') is not None |
53 | 53 | TEST = os.environ.get('PYTHON_DONJAYAMANNE_TEST') is not None |
@@ -128,12 +128,18 @@ def release(self): |
128 | 128 | - Not the best, but that's how it will have to be done |
129 | 129 | - http://www.xavierdupre.fr/app/pyquickhelper/helpsphinx/_modules/pyquickhelper/ipythonhelper/notebook_runner.html |
130 | 130 | """ |
| 131 | + |
| 132 | + |
131 | 133 | class iPythonKernelResponseMonitor(object): |
132 | | - def __init__(self, kernelUUID, socketConnection): |
| 134 | + |
| 135 | + def __init__(self, kernelUUID, socketConnection, send_lock, shell_channel, iopub_channel): |
133 | 136 | import threading |
134 | 137 | self.kernel = multiKernelManager.get_kernel(kernelUUID) |
135 | 138 | self.conn = socketConnection |
136 | 139 | self.is_stop_requested = False |
| 140 | + self.shell_channel = shell_channel |
| 141 | + self.iopub_channel = iopub_channel |
| 142 | + self.send_lock = send_lock |
137 | 143 | thread.start_new_thread(self.start_processing, ()) |
138 | 144 |
|
139 | 145 | def stop(self): |
@@ -142,6 +148,14 @@ def stop(self): |
142 | 148 | def check_for_exit_socket_loop(self): |
143 | 149 | return self.is_stop_requested |
144 | 150 |
|
| 151 | + def _populateErrorContents(self, sourceContents, targetContents): |
| 152 | + try: |
| 153 | + targetContents.ename = sourceContents.ename |
| 154 | + targetContents.evalue = sourceContents.evalue |
| 155 | + targetContents.traceback = sourceContents.traceback |
| 156 | + except AttributeError: |
| 157 | + pass |
| 158 | + |
145 | 159 | def start_processing(self): |
146 | 160 | """loop to read the io ports/messages""" |
147 | 161 |
|
@@ -151,6 +165,31 @@ def start_processing(self): |
151 | 165 | if self.check_for_exit_socket_loop(): |
152 | 166 | break |
153 | 167 |
|
| 168 | + try: |
| 169 | + # We can ignore msgtype=execute_request |
| 170 | + |
| 171 | + exe_result = self.shell_channel.get_shell_msg(timeout=1) |
| 172 | + # message can be JSON, but not always |
| 173 | + # (http://jupyter-client.readthedocs.io/en/latest/messaging.html) |
| 174 | + # assume for now that dates are the only crappy (non JSONable stuff sent) |
| 175 | + json_to_send = json.dumps(exe_result, default=str) |
| 176 | + with self.send_lock: |
| 177 | + _debug_write('shell_result') |
| 178 | + write_bytes(self.conn, iPythonSocketServer._SHEL) |
| 179 | + write_string(self.conn, json_to_send) |
| 180 | + except Empty: |
| 181 | + pass |
| 182 | + |
| 183 | + try: |
| 184 | + msg = self.iopub_channel.get_iopub_msg(timeout=10) |
| 185 | + json_to_send = json.dumps(msg, default=str) |
| 186 | + with self.send_lock: |
| 187 | + _debug_write('iopub_msg') |
| 188 | + write_bytes(self.conn, iPythonSocketServer._IOPB) |
| 189 | + write_string(self.conn, json_to_send) |
| 190 | + except Empty: |
| 191 | + pass |
| 192 | + |
154 | 193 | except IPythonExitException: |
155 | 194 | _debug_write('IPythonExitException') |
156 | 195 | _debug_write(traceback.format_exc()) |
@@ -185,6 +224,9 @@ class iPythonSocketServer(object): |
185 | 224 | _STPK = to_bytes('STPK') |
186 | 225 | _RSTK = to_bytes('RSTK') |
187 | 226 | _ITPK = to_bytes('ITPK') |
| 227 | + _RUN = to_bytes('RUN ') |
| 228 | + _SHEL = to_bytes('SHEL') |
| 229 | + _IOPB = to_bytes('IOPB') |
188 | 230 |
|
189 | 231 | def __init__(self): |
190 | 232 | import threading |
@@ -199,6 +241,8 @@ def __init__(self): |
199 | 241 | self.execute_item_lock = threading.Lock() |
200 | 242 | # lock starts acquired (we use it like manual reset event) |
201 | 243 | self.execute_item_lock.acquire() |
| 244 | + self.kernelMonitor = None |
| 245 | + self.shell_channel = None |
202 | 246 |
|
203 | 247 | def connect(self, port): |
204 | 248 | # start a new thread for communicating w/ the remote process |
@@ -332,14 +376,15 @@ def _cmd_strk(self, id): |
332 | 376 | except socket.timeout: |
333 | 377 | pass |
334 | 378 | kernelUUID = multiKernelManager.start_kernel(kernel_name=kernelName) |
| 379 | + self._postStartKernel(kernelUUID) |
| 380 | + |
335 | 381 | # get the config and the connection FileExistsError |
336 | | - kernel = multiKernelManager.get_kernel(kernelUUID) |
337 | 382 | try: |
338 | | - config = kernel.config |
| 383 | + config = kernel_manager.config |
339 | 384 | except: |
340 | 385 | config = {} |
341 | 386 | try: |
342 | | - connection_file = kernel.connection_file |
| 387 | + connection_file = kernel_manager.connection_file |
343 | 388 | except: |
344 | 389 | connection_file = "" |
345 | 390 |
|
@@ -351,6 +396,28 @@ def _cmd_strk(self, id): |
351 | 396 | write_string(self.conn, json.dumps(config)) |
352 | 397 | write_string(self.conn, connection_file) |
353 | 398 |
|
| 399 | + def _postStartKernel(self, kernelUUID): |
| 400 | + kernel_manager = multiKernelManager.get_kernel(kernelUUID) |
| 401 | + kernel_client = kernel_manager.client() |
| 402 | + kernel_client.start_channels() |
| 403 | + |
| 404 | + try: |
| 405 | + # IPython 3.x |
| 406 | + kernel_client.wait_for_ready() |
| 407 | + iopub = kernel_client |
| 408 | + shell = kernel_client |
| 409 | + except AttributeError: |
| 410 | + # Ipython 2.x |
| 411 | + # Based on https://github.com/paulgb/runipy/pull/49/files |
| 412 | + iopub = kernel_client.iopub_channel |
| 413 | + shell = kernel_client.shell_channel |
| 414 | + shell.get_shell_msg = shell.get_msg |
| 415 | + iopub.get_iopub_msg = iopub.get_msg |
| 416 | + |
| 417 | + self.shell_channel = shell |
| 418 | + self.kernelMonitor = iPythonKernelResponseMonitor( |
| 419 | + kernelUUID, self.conn, self.send_lock, shell, iopub) |
| 420 | + |
354 | 421 | def _cmd_stpk(self, id): |
355 | 422 | """Shutdown a kernel by UUID""" |
356 | 423 | while True: |
@@ -359,12 +426,28 @@ def _cmd_stpk(self, id): |
359 | 426 | break |
360 | 427 | except socket.timeout: |
361 | 428 | pass |
| 429 | + |
362 | 430 | try: |
363 | | - kernel = multiKernelManager.get_kernel(kernelUUID) |
364 | | - kernel.shutdown_kernel() |
| 431 | + if self.kernelMonitor is not None: |
| 432 | + self.kernelMonitor.stop() |
| 433 | + finally: |
| 434 | + pass |
| 435 | + |
| 436 | + try: |
| 437 | + kernel_manager = multiKernelManager.get_kernel(kernelUUID) |
| 438 | + kernel_client = kernel_manager.client() |
| 439 | + kernel_client.stop_channels() |
| 440 | + finally: |
| 441 | + pass |
| 442 | + |
| 443 | + try: |
| 444 | + kernel_manager = multiKernelManager.get_kernel(kernelUUID) |
| 445 | + kernel_manager.shutdown_kernel() |
365 | 446 | except: |
366 | 447 | pass |
367 | 448 | finally: |
| 449 | + self.shell_channel = None |
| 450 | + self.kernelMonitor = None |
368 | 451 | with self.send_lock: |
369 | 452 | write_bytes(self.conn, iPythonSocketServer._STPK) |
370 | 453 | write_string(self.conn, id) |
@@ -377,8 +460,18 @@ def _cmd_rstk(self, id): |
377 | 460 | break |
378 | 461 | except socket.timeout: |
379 | 462 | pass |
380 | | - kernel = multiKernelManager.get_kernel(kernelUUID) |
381 | | - kernel.restart_kernel(now=True) |
| 463 | + |
| 464 | + try: |
| 465 | + if self.kernelMonitor is not None: |
| 466 | + self.kernelMonitor.stop() |
| 467 | + finally: |
| 468 | + self.kernelMonitor = None |
| 469 | + |
| 470 | + kernel_manager = multiKernelManager.get_kernel(kernelUUID) |
| 471 | + kernel_manager.restart_kernel(now=True) |
| 472 | + |
| 473 | + self._postStartKernel(kernelUUID) |
| 474 | + |
382 | 475 | with self.send_lock: |
383 | 476 | write_bytes(self.conn, iPythonSocketServer._RSTK) |
384 | 477 | write_string(self.conn, id) |
@@ -391,16 +484,25 @@ def _cmd_itpk(self, id): |
391 | 484 | break |
392 | 485 | except socket.timeout: |
393 | 486 | pass |
394 | | - kernel = multiKernelManager.get_kernel(kernelUUID) |
395 | | - kernel.interrupt_kernel() |
| 487 | + kernel_manager = multiKernelManager.get_kernel(kernelUUID) |
| 488 | + kernel_manager.interrupt_kernel() |
396 | 489 | with self.send_lock: |
397 | 490 | write_bytes(self.conn, iPythonSocketServer._ITPK) |
398 | 491 | write_string(self.conn, id) |
399 | 492 |
|
400 | | - def _cmd_run(self): |
401 | | - """runs the received snippet of code""" |
402 | | - # self.run_command(read_string(self.conn)) |
403 | | - pass |
| 493 | + def _cmd_run(self, id): |
| 494 | + """runs the received snippet of code (kernel is expected to have been started)""" |
| 495 | + while True: |
| 496 | + try: |
| 497 | + code = read_string(self.conn) |
| 498 | + break |
| 499 | + except socket.timeout: |
| 500 | + pass |
| 501 | + msg_id = self.shell_channel.execute(code) |
| 502 | + with self.send_lock: |
| 503 | + write_bytes(self.conn, iPythonSocketServer._RUN) |
| 504 | + write_string(self.conn, id) |
| 505 | + write_string(self.conn, msg_id) |
404 | 506 |
|
405 | 507 | def _cmd_abrt(self): |
406 | 508 | """aborts the current running command""" |
@@ -501,6 +603,7 @@ def flush(self): |
501 | 603 | to_bytes('stpk'): True, |
502 | 604 | to_bytes('rstk'): True, |
503 | 605 | to_bytes('itpk'): True, |
| 606 | + to_bytes('run '): True, |
504 | 607 | } |
505 | 608 |
|
506 | 609 |
|
|