21
21
import functools
22
22
import logging as std_logging
23
23
import os
24
+ import time
24
25
import warnings
25
26
26
27
import eventlet
@@ -431,6 +432,13 @@ def _wrap_target_cell(self, context, cell_mapping):
431
432
# yield to do the actual work. We can do schedulable things
432
433
# here and not exclude other threads from making progress.
433
434
# 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.
434
442
# 4. If we changed state in #2, we need to change it back. So we grab
435
443
# a writer lock again and do that.
436
444
# 5. Finally, if an exception was raised in #3 while state was
@@ -449,29 +457,47 @@ def _wrap_target_cell(self, context, cell_mapping):
449
457
450
458
raised_exc = None
451
459
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
457
466
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 ()
469
469
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 ):
470
473
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 ()
475
501
476
502
with self ._cell_lock .write_lock ():
477
503
# Once we have returned from the context, we need
0 commit comments