##// 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 Must be loaded with ip.load('ipy_bzr')
8 Must be loaded with ip.load('ipy_bzr')
9
9
10 """
10 """
11 a
11
12 # Copyright (C) 2004, 2005 Aaron Bentley
12 # Copyright (C) 2004, 2005 Aaron Bentley
13 # <aaron@aaronbentley.com>
13 # <aaron@aaronbentley.com>
14 #
14 #
@@ -358,18 +358,13 b' class MTInteractiveShell(InteractiveShell):'
358 InteractiveShell.__init__(self,name,usage,rc,user_ns,
358 InteractiveShell.__init__(self,name,usage,rc,user_ns,
359 user_global_ns,banner2)
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
362 # A queue to hold the code to be executed.
365 # enough, because uses like macros cause reentrancy.
366 self.code_queue = Queue.Queue()
363 self.code_queue = Queue.Queue()
367
364
368 # Stuff to do at closing time
365 # Stuff to do at closing time
369 self._kill = False
366 self._kill = None
370 on_kill = kw.get('on_kill')
367 on_kill = kw.get('on_kill', [])
371 if on_kill is None:
372 on_kill = []
373 # Check that all things to kill are callable:
368 # Check that all things to kill are callable:
374 for t in on_kill:
369 for t in on_kill:
375 if not callable(t):
370 if not callable(t):
@@ -377,6 +372,7 b' class MTInteractiveShell(InteractiveShell):'
377 self.on_kill = on_kill
372 self.on_kill = on_kill
378 # thread identity of the "worker thread" (that may execute code directly)
373 # thread identity of the "worker thread" (that may execute code directly)
379 self.worker_ident = None
374 self.worker_ident = None
375
380 def runsource(self, source, filename="<input>", symbol="single"):
376 def runsource(self, source, filename="<input>", symbol="single"):
381 """Compile and run some source in the interpreter.
377 """Compile and run some source in the interpreter.
382
378
@@ -390,6 +386,10 b' class MTInteractiveShell(InteractiveShell):'
390 KBINT = False
386 KBINT = False
391 return False
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 try:
393 try:
394 code = self.compile(source, filename, symbol)
394 code = self.compile(source, filename, symbol)
395 except (OverflowError, SyntaxError, ValueError):
395 except (OverflowError, SyntaxError, ValueError):
@@ -401,13 +401,6 b' class MTInteractiveShell(InteractiveShell):'
401 # Case 2
401 # Case 2
402 return True
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 # shortcut - if we are in worker thread, or the worker thread is not running,
404 # shortcut - if we are in worker thread, or the worker thread is not running,
412 # execute directly (to allow recursion and prevent deadlock if code is run early
405 # execute directly (to allow recursion and prevent deadlock if code is run early
413 # in IPython construction)
406 # in IPython construction)
@@ -416,12 +409,21 b' class MTInteractiveShell(InteractiveShell):'
416 InteractiveShell.runcode(self,code)
409 InteractiveShell.runcode(self,code)
417 return
410 return
418
411
419 got_lock = self.thread_ready.acquire(blocking=False)
412 # Case 3
420 self.code_queue.put(code)
413 # Store code in queue, so the execution thread can handle it.
421 if got_lock:
414
422 self.thread_ready.wait() # Wait until processed in timeout interval
415 completed_ev, received_ev = threading.Event(), threading.Event()
423 self.thread_ready.release()
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 return False
427 return False
426
428
427 def runcode(self):
429 def runcode(self):
@@ -430,9 +432,9 b' class MTInteractiveShell(InteractiveShell):'
430 Multithreaded wrapper around IPython's runcode()."""
432 Multithreaded wrapper around IPython's runcode()."""
431
433
432 global CODE_RUN
434 global CODE_RUN
433 # lock thread-protected stuff
435
436 # we are in worker thread, stash out the id for runsource()
434 self.worker_ident = thread.get_ident()
437 self.worker_ident = thread.get_ident()
435 got_lock = self.thread_ready.acquire()
436
438
437 if self._kill:
439 if self._kill:
438 print >>Term.cout, 'Closing threads...',
440 print >>Term.cout, 'Closing threads...',
@@ -440,6 +442,9 b' class MTInteractiveShell(InteractiveShell):'
440 for tokill in self.on_kill:
442 for tokill in self.on_kill:
441 tokill()
443 tokill()
442 print >>Term.cout, 'Done.'
444 print >>Term.cout, 'Done.'
445 # allow kill() to return
446 self._kill.set()
447 return True
443
448
444 # Install sigint handler. We do it every time to ensure that if user
449 # Install sigint handler. We do it every time to ensure that if user
445 # code modifies it, we restore our own handling.
450 # code modifies it, we restore our own handling.
@@ -455,9 +460,11 b' class MTInteractiveShell(InteractiveShell):'
455 code_to_run = None
460 code_to_run = None
456 while 1:
461 while 1:
457 try:
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 except Queue.Empty:
464 except Queue.Empty:
460 break
465 break
466 received_ev.set()
467
461 # Exceptions need to be raised differently depending on which
468 # Exceptions need to be raised differently depending on which
462 # thread is active. This convoluted try/except is only there to
469 # thread is active. This convoluted try/except is only there to
463 # protect against asynchronous exceptions, to ensure that a KBINT
470 # protect against asynchronous exceptions, to ensure that a KBINT
@@ -471,28 +478,23 b' class MTInteractiveShell(InteractiveShell):'
471 except KeyboardInterrupt:
478 except KeyboardInterrupt:
472 print "Keyboard interrupted in mainloop"
479 print "Keyboard interrupted in mainloop"
473 while not self.code_queue.empty():
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 break
484 break
476 finally:
485 finally:
477 if got_lock:
478 CODE_RUN = False
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 # This MUST return true for gtk threading to work
491 # This MUST return true for gtk threading to work
488 return True
492 return True
489
493
490 def kill(self):
494 def kill(self):
491 """Kill the thread, returning when it has been shut down."""
495 """Kill the thread, returning when it has been shut down."""
492 got_lock = self.thread_ready.acquire(False)
496 self._kill = threading.Event()
493 self._kill = True
497 self._kill.wait()
494 if got_lock:
495 self.thread_ready.release()
496
498
497 class MatplotlibShellBase:
499 class MatplotlibShellBase:
498 """Mixin class to provide the necessary modifications to regular IPython
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