##// END OF EJS Templates
merge mtexp to trunk - a more robust threaded shell implementation that works with macros, and does not hang IPython if worker thread dies
Ville M. Vainio -
r1087:5882f870 merge
parent child Browse files
Show More
@@ -8,7 +8,7 b' are at the bottom of the file, the rest is left untouched.'
8 8 Must be loaded with ip.load('ipy_bzr')
9 9
10 10 """
11 a
11
12 12 # Copyright (C) 2004, 2005 Aaron Bentley
13 13 # <aaron@aaronbentley.com>
14 14 #
@@ -358,18 +358,13 b' class MTInteractiveShell(InteractiveShell):'
358 358 InteractiveShell.__init__(self,name,usage,rc,user_ns,
359 359 user_global_ns,banner2)
360 360
361 # Locking control variable.
362 self.thread_ready = threading.Condition(threading.RLock())
363 361
364 # A queue to hold the code to be executed. A scalar variable is NOT
365 # enough, because uses like macros cause reentrancy.
362 # A queue to hold the code to be executed.
366 363 self.code_queue = Queue.Queue()
367 364
368 365 # Stuff to do at closing time
369 self._kill = False
370 on_kill = kw.get('on_kill')
371 if on_kill is None:
372 on_kill = []
366 self._kill = None
367 on_kill = kw.get('on_kill', [])
373 368 # Check that all things to kill are callable:
374 369 for t in on_kill:
375 370 if not callable(t):
@@ -377,6 +372,7 b' class MTInteractiveShell(InteractiveShell):'
377 372 self.on_kill = on_kill
378 373 # thread identity of the "worker thread" (that may execute code directly)
379 374 self.worker_ident = None
375
380 376 def runsource(self, source, filename="<input>", symbol="single"):
381 377 """Compile and run some source in the interpreter.
382 378
@@ -390,6 +386,10 b' class MTInteractiveShell(InteractiveShell):'
390 386 KBINT = False
391 387 return False
392 388
389 if self._kill:
390 # can't queue new code if we are being killed
391 return True
392
393 393 try:
394 394 code = self.compile(source, filename, symbol)
395 395 except (OverflowError, SyntaxError, ValueError):
@@ -401,13 +401,6 b' class MTInteractiveShell(InteractiveShell):'
401 401 # Case 2
402 402 return True
403 403
404 # Case 3
405 # Store code in queue, so the execution thread can handle it.
406
407 # Note that with macros and other applications, we MAY re-enter this
408 # section, so we have to acquire the lock with non-blocking semantics,
409 # else we deadlock.
410
411 404 # shortcut - if we are in worker thread, or the worker thread is not running,
412 405 # execute directly (to allow recursion and prevent deadlock if code is run early
413 406 # in IPython construction)
@@ -416,12 +409,21 b' class MTInteractiveShell(InteractiveShell):'
416 409 InteractiveShell.runcode(self,code)
417 410 return
418 411
419 got_lock = self.thread_ready.acquire(blocking=False)
420 self.code_queue.put(code)
421 if got_lock:
422 self.thread_ready.wait() # Wait until processed in timeout interval
423 self.thread_ready.release()
412 # Case 3
413 # Store code in queue, so the execution thread can handle it.
414
415 completed_ev, received_ev = threading.Event(), threading.Event()
424 416
417 self.code_queue.put((code,completed_ev, received_ev))
418 # first make sure the message was received, with timeout
419 received_ev.wait(5)
420 if not received_ev.isSet():
421 # the mainloop is dead, start executing code directly
422 print "Warning: Timeout for mainloop thread exceeded"
423 print "switching to nonthreaded mode (until mainloop wakes up again)"
424 self.worker_ident = None
425 else:
426 completed_ev.wait()
425 427 return False
426 428
427 429 def runcode(self):
@@ -430,9 +432,9 b' class MTInteractiveShell(InteractiveShell):'
430 432 Multithreaded wrapper around IPython's runcode()."""
431 433
432 434 global CODE_RUN
433 # lock thread-protected stuff
435
436 # we are in worker thread, stash out the id for runsource()
434 437 self.worker_ident = thread.get_ident()
435 got_lock = self.thread_ready.acquire()
436 438
437 439 if self._kill:
438 440 print >>Term.cout, 'Closing threads...',
@@ -440,6 +442,9 b' class MTInteractiveShell(InteractiveShell):'
440 442 for tokill in self.on_kill:
441 443 tokill()
442 444 print >>Term.cout, 'Done.'
445 # allow kill() to return
446 self._kill.set()
447 return True
443 448
444 449 # Install sigint handler. We do it every time to ensure that if user
445 450 # code modifies it, we restore our own handling.
@@ -455,9 +460,11 b' class MTInteractiveShell(InteractiveShell):'
455 460 code_to_run = None
456 461 while 1:
457 462 try:
458 code_to_run = self.code_queue.get_nowait()
463 code_to_run, completed_ev, received_ev = self.code_queue.get_nowait()
459 464 except Queue.Empty:
460 465 break
466 received_ev.set()
467
461 468 # Exceptions need to be raised differently depending on which
462 469 # thread is active. This convoluted try/except is only there to
463 470 # protect against asynchronous exceptions, to ensure that a KBINT
@@ -471,28 +478,23 b' class MTInteractiveShell(InteractiveShell):'
471 478 except KeyboardInterrupt:
472 479 print "Keyboard interrupted in mainloop"
473 480 while not self.code_queue.empty():
474 self.code_queue.get_nowait()
481 code, ev1,ev2 = self.code_queue.get_nowait()
482 ev1.set()
483 ev2.set()
475 484 break
476 485 finally:
477 if got_lock:
478 486 CODE_RUN = False
487 # allow runsource() return from wait
488 completed_ev.set()
479 489
480 # We're done with thread-protected variables
481 if code_to_run is not None:
482 self.thread_ready.notify()
483 self.thread_ready.release()
484 490
485 # We're done...
486 CODE_RUN = False
487 491 # This MUST return true for gtk threading to work
488 492 return True
489 493
490 494 def kill(self):
491 495 """Kill the thread, returning when it has been shut down."""
492 got_lock = self.thread_ready.acquire(False)
493 self._kill = True
494 if got_lock:
495 self.thread_ready.release()
496 self._kill = threading.Event()
497 self._kill.wait()
496 498
497 499 class MatplotlibShellBase:
498 500 """Mixin class to provide the necessary modifications to regular IPython
General Comments 0
You need to be logged in to leave comments. Login now