Commit 516183b5 authored by Tim Peters's avatar Tim Peters

Foward port fix for race in DB.open():

Under exceedingly rare conditions, a timing hole made it
possible for a second open() call on a database to block for an
arbitrarily long time.  This accounts for the intermittent
failure of a thread to make any progress after 5 minutes in
checkConcurrentUpdates1Storage.

We intend to get rid of most of this delicate lock
business, but before then the test failures are still
hurting me.
parent 1ccd62fd
...@@ -487,9 +487,9 @@ class DB(object): ...@@ -487,9 +487,9 @@ class DB(object):
# set whenever the pool becomes empty so that threads are # set whenever the pool becomes empty so that threads are
# forced to wait until the pool gets a connection in it. # forced to wait until the pool gets a connection in it.
# The lock is acquired when the (empty) pool is # The lock is acquired when the (empty) pool is
# created. The The lock is acquired just prior to removing # created. The lock is acquired just prior to removing
# the last connection from the pool and just after adding # the last connection from the pool and released just after
# a connection to an empty pool. # adding a connection to an empty pool.
if pools.has_key(version): if pools.has_key(version):
...@@ -528,22 +528,36 @@ class DB(object): ...@@ -528,22 +528,36 @@ class DB(object):
pool_lock.release() pool_lock.release()
else: return else: return
elif len(pool) == 1: elif len(pool)==1:
# Taking last one, lock the pool # Taking last one, lock the pool.
# Note that another thread might grab the lock # Note that another thread might grab the lock
# before us, so we might actually block, however, # before us, so we might actually block, however,
# when we get the lock back, there *will* be a # when we get the lock back, there *will* be a
# connection in the pool. # connection in the pool. OTOH, there's no limit on
# how long we may need to wait: if the other thread
# grabbed the lock in this section too, we'll wait
# here until another connection is closed.
# checkConcurrentUpdates1Storage provoked this frequently
# on a hyperthreaded machine, with its second thread
# timing out after waiting 5 minutes for DB.open() to
# return. So, if we can't get the pool lock immediately,
# now we make a recursive call. This allows the current
# thread to allocate a new connection instead of waiting
# arbitrarily long for the single connection in the pool
# right now.
self._r() self._r()
pool_lock.acquire() if not pool_lock.acquire(0):
result = DB.open(self, version, transaction, temporary,
force, waitflag)
self._a()
return result
self._a() self._a()
if len(pool) > 1: if len(pool) > 1:
# Note that the pool size will normally be 1 here, # Note that the pool size will normally be 1 here,
# but it could be higher due to a race condition. # but it could be higher due to a race condition.
pool_lock.release() pool_lock.release()
c = pool[-1] c = pool.pop()
del pool[-1]
c._setDB(self, mvcc=mvcc, txn_mgr=txn_mgr, synch=synch) c._setDB(self, mvcc=mvcc, txn_mgr=txn_mgr, synch=synch)
for pool, allocated in pooll: for pool, allocated in pooll:
for cc in pool: for cc in pool:
...@@ -553,7 +567,8 @@ class DB(object): ...@@ -553,7 +567,8 @@ class DB(object):
transaction[version] = c transaction[version] = c
return c return c
finally: self._r() finally:
self._r()
def removeVersionPool(self, version): def removeVersionPool(self, version):
pools, pooll = self._pools pools, pooll = self._pools
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment