2121import functools
2222import logging as std_logging
2323import os
24+ import time
2425import warnings
2526
2627import eventlet
@@ -431,6 +432,13 @@ def _wrap_target_cell(self, context, cell_mapping):
431432 # yield to do the actual work. We can do schedulable things
432433 # here and not exclude other threads from making progress.
433434 # If an exception is raised, we capture that and save it.
435+ # Note that it is possible that another thread has changed the
436+ # global state (step #2) after we released the writer lock but
437+ # before we acquired the reader lock. If this happens, we will
438+ # detect the global state change and retry step #2 a limited number
439+ # of times. If we happen to race repeatedly with another thread and
440+ # exceed our retry limit, we will give up and raise a RuntimeError,
441+ # which will fail the test.
434442 # 4. If we changed state in #2, we need to change it back. So we grab
435443 # a writer lock again and do that.
436444 # 5. Finally, if an exception was raised in #3 while state was
@@ -449,29 +457,47 @@ def _wrap_target_cell(self, context, cell_mapping):
449457
450458 raised_exc = None
451459
452- with self ._cell_lock .write_lock ():
453- if cell_mapping is not None :
454- # This assumes the next local DB access is the same cell that
455- # was targeted last time.
456- self ._last_ctxt_mgr = desired
460+ def set_last_ctxt_mgr ():
461+ with self ._cell_lock .write_lock ():
462+ if cell_mapping is not None :
463+ # This assumes the next local DB access is the same cell
464+ # that was targeted last time.
465+ self ._last_ctxt_mgr = desired
457466
458- with self ._cell_lock .read_lock ():
459- if self ._last_ctxt_mgr != desired :
460- # NOTE(danms): This is unlikely to happen, but it's possible
461- # another waiting writer changed the state between us letting
462- # it go and re-acquiring as a reader. If lockutils supported
463- # upgrading and downgrading locks, this wouldn't be a problem.
464- # Regardless, assert that it is still as we left it here
465- # so we don't hit the wrong cell. If this becomes a problem,
466- # we just need to retry the write section above until we land
467- # here with the cell we want.
468- raise RuntimeError ('Global DB state changed underneath us' )
467+ # Set last context manager to the desired cell's context manager.
468+ set_last_ctxt_mgr ()
469469
470+ # Retry setting the last context manager if we detect that a writer
471+ # changed global DB state before we take the read lock.
472+ for retry_time in range (0 , 3 ):
470473 try :
471- with self ._real_target_cell (context , cell_mapping ) as ccontext :
472- yield ccontext
473- except Exception as exc :
474- raised_exc = exc
474+ with self ._cell_lock .read_lock ():
475+ if self ._last_ctxt_mgr != desired :
476+ # NOTE(danms): This is unlikely to happen, but it's
477+ # possible another waiting writer changed the state
478+ # between us letting it go and re-acquiring as a
479+ # reader. If lockutils supported upgrading and
480+ # downgrading locks, this wouldn't be a problem.
481+ # Regardless, assert that it is still as we left it
482+ # here so we don't hit the wrong cell. If this becomes
483+ # a problem, we just need to retry the write section
484+ # above until we land here with the cell we want.
485+ raise RuntimeError (
486+ 'Global DB state changed underneath us' )
487+ try :
488+ with self ._real_target_cell (
489+ context , cell_mapping
490+ ) as ccontext :
491+ yield ccontext
492+ except Exception as exc :
493+ raised_exc = exc
494+ # Leave the retry loop after calling target_cell
495+ break
496+ except RuntimeError :
497+ # Give other threads a chance to make progress, increasing the
498+ # wait time between attempts.
499+ time .sleep (retry_time )
500+ set_last_ctxt_mgr ()
475501
476502 with self ._cell_lock .write_lock ():
477503 # Once we have returned from the context, we need
0 commit comments