##// 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,18 +372,23 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
383 Modified version of code.py's runsource(), to handle threading issues.
379 Modified version of code.py's runsource(), to handle threading issues.
384 See the original for full docstring details."""
380 See the original for full docstring details."""
385
381
386 global KBINT
382 global KBINT
387
383
388 # If Ctrl-C was typed, we reset the flag and return right away
384 # If Ctrl-C was typed, we reset the flag and return right away
389 if KBINT:
385 if KBINT:
390 KBINT = False
386 KBINT = False
391 return False
387 return False
388
389 if self._kill:
390 # can't queue new code if we are being killed
391 return True
392
392
393 try:
393 try:
394 code = self.compile(source, filename, symbol)
394 code = self.compile(source, filename, symbol)
@@ -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)
@@ -415,24 +408,33 b' class MTInteractiveShell(InteractiveShell):'
415 if self.worker_ident is None or self.worker_ident == thread.get_ident():
408 if self.worker_ident is None or self.worker_ident == thread.get_ident():
416 InteractiveShell.runcode(self,code)
409 InteractiveShell.runcode(self,code)
417 return
410 return
418
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()
424
411
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()
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):
428 """Execute a code object.
430 """Execute a code object.
429
431
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:
486 CODE_RUN = False
478 CODE_RUN = False
487 # allow runsource() return from wait
479
488 completed_ev.set()
480 # We're done with thread-protected variables
489
481 if code_to_run is not None:
490
482 self.thread_ready.notify()
483 self.thread_ready.release()
484
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