[Ipython-tickets] [IPython] #210: Race condition in MTInteractiveShell
IPython
ipython-tickets@scipy....
Wed Jan 30 09:12:11 CST 2008
#210: Race condition in MTInteractiveShell
---------------------+------------------------------------------------------
Reporter: marc | Owner: fperez
Type: defect | Status: new
Priority: high | Milestone:
Component: ipython | Version:
Severity: major | Resolution:
Keywords: |
---------------------+------------------------------------------------------
Comment (by marc):
OK, sorry for the big mess, here it is again with some better formatting
I am using ipython (0.8.2) in threaded mode (for pylab support) with a GTK
backend in a (sort-of) embedded shell (by calling make_session). However,
occasionally ipython locks up. I traced the problem after a while back to
the threading synchronization in MTInteractiveShell:
{{{
Runsource:
411 got_lock = self.thread_ready.acquire(False)
412 self.code_queue.put(code)
413 if got_lock:
414 self.thread_ready.wait() # Wait until processed in
timeout interval
415 self.thread_ready.release()
Runcode:
424 global CODE_RUN
425
426 # Exceptions need to be raised differently depending on
which thread is
427 # active
428 CODE_RUN = True
429
430 # lock thread-protected stuff
431 got_lock = self.thread_ready.acquire(False)
432
...
449 # Flush queue of pending code by calling the run methood
of the parent
450 # class with all items which may be in the queue.
451 while 1:
452 try:
453 code_to_run = self.code_queue.get_nowait()
454 except Queue.Empty:
455 break
456 if got_lock:
457 self.thread_ready.notify()
458 InteractiveShell.runcode(self,code_to_run)
459 else:
460 break
461
462 # We're done with thread-protected variables
463 if got_lock:
464 self.thread_ready.release()
465
466 # We're done...
467 CODE_RUN = False
}}}
The problem is that both functions acquire the lock only if available (the
got_lock parameter). The race condition that occurs (every 40 commands or
so) is that: [[BR]]
A. runsource acquires lock, puts code in queue (411-412) [[BR]]
B. runcode trys to acquire lock, fails as runsource has the lock
(431)[[BR]]
C. runsource starts waiting (as it has the lock) (414)[[BR]]
D. runcode obtains code, but breaks as it doesn not have the lock. It does
not notify the waiting Runsource! (451-460)[[BR]]
(C and D) could also be in different order
{{{
Possible solution:
Make the Lock an Rlock (to enable the thread calling runcode to call
runsource)
364 self.thread_ready = threading.Condition(threading.RLock())
Runsource
- Make lock acquire blocking
411 got_lock = self.thread_ready.acquire()
- Only perform wait if this is not an reentrant lock (got_lock is True on
outer lock, and 1 on inner locks)
413 if(got_lock is True):
414 self.thread_ready.wait() # Wait until processed in timeout
interval
- always release (not based on if(got_lock))
415 self.thread_ready.release()
Runcode
- make locking required
431 self.thread_ready.acquire()
- always run code if available (not dependent on if(got_lock)) (in the
current implementation code just disappears)
- move notify out of while loop, only call it if code has been obtained
and executed (not essential)
- always release lock (not dependent on if(got_lock))
450 code_to_run=None
451 while 1:
452 try:
453 code_to_run = self.code_queue.get_nowait()
454 except Queue.Empty:
455 break
458 InteractiveShell.runcode(self,code_to_run)
...
# We're done with thread-protected variables
461 if(not code_to_run is None):
462 self.thread_ready.notify()
463 self.thread_ready.release()
}}}
This seems to solve the deadlocking problem I encountered. Furthermore,
using this the code is still reentrant (e.g. you can run
ip.IP.runsource('a=1') from the console, or even something like
'ip.IP.runsource('a=1'); ip.IP.runcode()' without deadlocking), so i guess
macros are ok too. I did not test it with the other backends (QT,etc.)
however.
--
Ticket URL: <http://ipython.scipy.org/ipython/ipython/ticket/210#comment:1>
IPython <http://ipython.scipy.org>
The IPython interactive Python system
More information about the Ipython-tickets
mailing list