#NEWLINE# @ classmethod #NEWLINE# def _TryAcquire( cls , client , resource_type , resource_id , callback , resource_data = None , detect_abandonment = False , owner_id = None , attempts = 0 , test_hook = None ) : #NEWLINE# #INDENT# 'Helper method that makes "MAX_UPDATE_ATTEMPTS" to acquire the\n    lock. If multiple agents are trying to acquire the lock, then one\n    might be updating the lock in order to acquire it while another\n    is querying the lock in order to see if it can be acquired. These\n    race conditions are resolved by detecting changes and retrying.\n\n    The "test_hook" function is called just before an attempt is made\n    to update the Lock row in the database. This makes testing various\n    race conditions much easier.\n    ' #NEWLINE# def _OnUpdate( lock , status ) : #NEWLINE# #INDENT# 'If lock was acquired and abandonment needs to be detected,\n      then starts renewal timer.\n      ' #NEWLINE# if ( ( status != Lock . FAILED_TO_ACQUIRE_LOCK ) and detect_abandonment ) : #NEWLINE# #INDENT# lock . _Renew ( client ) #NEWLINE# #UNINDENT# callback ( lock , status ) #NEWLINE# #UNINDENT# def _OnException( type , value , tb ) : #NEWLINE# #INDENT# 'Starts over unless too many acquire attempts have been made.' #NEWLINE# logging . warning ( ( 'race condition caused update of "%s:%s" lock to fail (will retry): %s' % ( resource_type , resource_id , value ) ) ) #NEWLINE# if ( attempts >= Lock . MAX_UPDATE_ATTEMPTS ) : #NEWLINE# #INDENT# logging . error ( 'too many failures attempting to update lock; aborting' , exc_info = ( type , value , tb ) ) #NEWLINE# callback ( None , Lock . FAILED_TO_ACQUIRE_LOCK ) #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# Lock . _TryAcquire ( client , resource_type , resource_id , callback , resource_data = resource_data , detect_abandonment = detect_abandonment , attempts = ( attempts + 1 ) , test_hook = test_hook ) #NEWLINE# #UNINDENT# #UNINDENT# def _DoUpdate( update_func ) : #NEWLINE# #INDENT# 'Calls the test hook just before a row is updated in the database.\n      Tests can simulate updates made by another agent at this critical\n      juncture.\n      ' #NEWLINE# if ( test_hook is not None ) : #NEWLINE# #INDENT# test_hook ( update_func ) #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# update_func ( ) #NEWLINE# #UNINDENT# #UNINDENT# def _OnQuery( lock ) : #NEWLINE# #INDENT# 'Creates a new lock, takes control of an abandoned lock, or reports\n      failure to acquire the lock. The choice depends upon the current\n      state of the lock in the database. Other agents may be trying to\n      acquire the lock at the same time, so takes care to handle those race\n      conditions.\n      ' #NEWLINE# if ( lock is None ) : #NEWLINE# #INDENT# lock = Lock ( lock_id , owner_id ) #NEWLINE# if ( resource_data is not None ) : #NEWLINE# #INDENT# lock . resource_data = resource_data #NEWLINE# #UNINDENT# if detect_abandonment : #NEWLINE# #INDENT# lock . expiration = ( time . time ( ) + Lock . ABANDONMENT_SECS ) #NEWLINE# #UNINDENT# with util . Barrier ( partial ( _OnUpdate , lock , Lock . ACQUIRED_LOCK ) , on_exception = _OnException ) as b : #NEWLINE# #INDENT# _DoUpdate ( partial ( lock . Update , client , expected = { #NEWLINE# #INDENT# 'lock_id' : False , #NEWLINE# #UNINDENT# } , callback = b . Callback ( ) ) ) #NEWLINE# #UNINDENT# #UNINDENT# else : #NEWLINE# #INDENT# if lock . _IsOwnedBy ( owner_id ) : #NEWLINE# #INDENT# assert ( not detect_abandonment ) , ( resource_type , resource_data ) #NEWLINE# lock . _SyncUniqueIdToOwnerId ( ) #NEWLINE# _OnUpdate ( lock , Lock . ACQUIRED_LOCK ) #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# if lock . IsAbandoned ( ) : #NEWLINE# #INDENT# logging . warning ( ( 'lock was abandoned; trying to take control of it: %s' % lock ) ) #NEWLINE# with util . Barrier ( partial ( _OnUpdate , lock , Lock . ACQUIRED_ABANDONED_LOCK ) , on_exception = _OnException ) as b : #NEWLINE# #INDENT# _DoUpdate ( partial ( lock . _TryTakeControl , client , detect_abandonment , b . Callback ( ) ) ) #NEWLINE# #UNINDENT# #UNINDENT# else : #NEWLINE# #INDENT# logging . warning ( ( 'acquire of lock failed; already held by another agent: %s' % lock ) ) #NEWLINE# with util . Barrier ( partial ( _OnUpdate , None , Lock . FAILED_TO_ACQUIRE_LOCK ) , on_exception = _OnException ) as b : #NEWLINE# #INDENT# _DoUpdate ( partial ( lock . _TryReportAcquireFailure , client , b . Callback ( ) ) ) #NEWLINE# #UNINDENT# #UNINDENT# #UNINDENT# #UNINDENT# #UNINDENT# lock_id = Lock . ConstructLockId ( resource_type , resource_id ) #NEWLINE# Lock . Query ( lock_id , lock_id , None , _OnQuery , must_exist = False )