public void ReleaseLock(LockTicket ticket) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } // Get the lock client for the granted request, release the lock, and dispose the client. if (_lockClients.TryRemove(ticket.RequestId, out LockServiceClient lockClient)) { LogDebug($"Releasing lock for {ticket.ResourceId}."); try { lockClient.ReleaseLock(); } catch (Exception ex) { LogError($"Unable to send release request to lock service.", ex); } finally { // Must ensure the client is disposed even if the release request fails. // Otherwise the client will keep responding to the ping requests from the lock service // and the lock might never get cleaned up. lockClient.Dispose(); } } }
/// <summary> /// Selects a <see cref="LockToken" /> from the provided list, based on current /// availability of the associated resources, then obtains an exlusive lock /// and executes the specified <see cref="Action{LockToken}" /> before releasing the lock. /// </summary> /// <param name="tokens">The collection of tokens to choose from.</param> /// <param name="action">The action to execute; takes the acquired token as a parameter.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="tokens" /> is null. /// <para>or</para> /// <paramref name="action" /> is null. /// </exception> /// <exception cref="ArgumentException"><paramref name="tokens" /> contains no elements.</exception> /// <exception cref="AcquireLockTimeoutException">The lock could not be obtained within the acquisition timeout specified by the selected token.</exception> /// <exception cref="HoldLockTimeoutException"><paramref name="action" /> did not complete within the hold timeout specified by the selected token.</exception> public void Run(IEnumerable <LockToken> tokens, Action <LockToken> action) { if (tokens == null) { throw new ArgumentNullException(nameof(tokens)); } if (action == null) { throw new ArgumentNullException(nameof(action)); } if (!tokens.Any()) { throw new ArgumentException("No lock tokens were specified.", nameof(tokens)); } // Send the lock tokens in a random order to prevent picking the same one every time if they are all available Random random = new Random(); IEnumerable <string> tokenIds = tokens.Select(n => n.Key).OrderBy(n => random.Next()); LockTicket lockTicket = _lockManager.AcquireLock(tokenIds, tokens.First().AcquireTimeout, 1, _requestName); try { LockToken acquiredToken = tokens.First(n => n.Key == lockTicket.ResourceId); RunAction(acquiredToken, () => action(acquiredToken)); } finally { _lockManager.ReleaseLock(lockTicket); } }
/// <summary> /// Releases the lock specified by the <see cref="LockTicket" />. /// </summary> /// <param name="ticket">The <see cref="LockTicket" /> identifying the lock to release.</param> /// <exception cref="ArgumentNullException"><paramref name="ticket" /> is null.</exception> public void ReleaseLock(LockTicket ticket) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } ReleaseLock(ticket.RequestId); }
/// <summary> /// Signals the client that the lock request has been granted. /// </summary> /// <param name="resourceId">The identifier for the locked resource.</param> public void GrantLock(string resourceId) { LogTrace($"Received lock grant signal for {resourceId}."); try { LockTicket = new LockTicket(_requestId, resourceId); _waitEvent.Set(); } catch (ObjectDisposedException) { // Race condition - ignore. } }
private LockTicket AcquireLock(Action <LockServiceClient> requestAction, TimeSpan waitTime, string requestName) { // Create a new LockServiceClient, then request the lock and wait for a response. LockServiceClient lockClient = new LockServiceClient(_lockServiceAddress, requestName); try { requestAction(lockClient); } catch (Exception ex) { // It is crucial to dispose the lock client here to avoid any lingering callback hosts LogError($"Unable to send acquire request to lock service.", ex); lockClient.Dispose(); throw; } LogDebug("Lock requested, waiting for grant."); LockTicket ticket = lockClient.WaitForLockGranted(waitTime); if (ticket != null) { // Request was granted - persist the lock client for this request so that the service // can ping it periodically to make sure it is still alive. LogDebug($"Acquired lock for {ticket.ResourceId}."); _lockClients.TryAdd(ticket.RequestId, lockClient); return(ticket); } else { // Did not get the lock in time. Cancel the request on the server and dispose the client. LogDebug($"Unable to acquire lock within {waitTime}. Canceling request."); try { lockClient.CancelRequest(); } catch (Exception ex) { LogError($"Unable to send cancel request to lock service.", ex); } finally { // Must ensure the client is disposed even if the cancel request fails. lockClient.Dispose(); } throw new AcquireLockTimeoutException($"Unable to acquire lock within {waitTime}."); } }
/// <summary> /// Obtains an exclusive lock according to the specified <see cref="LockToken" />, /// then executes the specified <see cref="Action" /> before releasing the lock. /// </summary> /// <param name="token">The token dictating the scope and behavior of execution.</param> /// <param name="action">The action to execute.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="token" /> is null. /// <para>or</para> /// <paramref name="action" /> is null. /// </exception> /// <exception cref="AcquireLockTimeoutException">The lock could not be obtained within the acquisition timeout specified by <paramref name="token" />.</exception> /// <exception cref="HoldLockTimeoutException"><paramref name="action" /> did not complete within the hold timeout specified by <paramref name="token" />.</exception> public void Run(LockToken token, Action action) { if (token == null) { throw new ArgumentNullException(nameof(token)); } if (action == null) { throw new ArgumentNullException(nameof(action)); } LockTicket lockTicket = _lockManager.AcquireLock(token.Key, token.AcquireTimeout, 1, _requestName); try { RunAction(token, action); } finally { _lockManager.ReleaseLock(lockTicket); } }