##// 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,18 +372,23 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
383 379 Modified version of code.py's runsource(), to handle threading issues.
384 380 See the original for full docstring details."""
385
381
386 382 global KBINT
387 383
388 384 # If Ctrl-C was typed, we reset the flag and return right away
389 385 if KBINT:
390 386 KBINT = False
391 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 393 try:
394 394 code = self.compile(source, filename, symbol)
@@ -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)
@@ -415,24 +408,33 b' class MTInteractiveShell(InteractiveShell):'
415 408 if self.worker_ident is None or self.worker_ident == thread.get_ident():
416 409 InteractiveShell.runcode(self,code)
417 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 427 return False
426 428
427 429 def runcode(self):
428 430 """Execute a code object.
429 431
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 CODE_RUN = False
479
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
485 # We're done...
486 CODE_RUN = False
486 CODE_RUN = False
487 # allow runsource() return from wait
488 completed_ev.set()
489
490
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