/// <inheritdoc/> public void Dispose() { if (_cancellationTokenSource != null) { _cancellationTokenSource.Cancel(false); _cancellationTokenSource.Dispose(); _cancellationTokenSource = null; } if (_cas == 0) { // Never locked return; } var key = LockDocument.GetKey(Name); _collection.RemoveAsync(key, new RemoveOptions().Cas(_cas)) .ContinueWith(t => { if (t.Exception !.InnerExceptions.OfType <DocumentNotFoundException>().Any()) { _logger.LogDebug("Did not release lock '{name}' for holder '{holder}' because it was already released.", Name, Holder); }
/// <inheritdoc/> public void Dispose() { if (_cancellationTokenSource != null) { _cancellationTokenSource.Cancel(false); _cancellationTokenSource.Dispose(); _cancellationTokenSource = null; } if (_cas == 0) { // Never locked return; } var key = LockDocument.GetKey(Name); _bucket.RemoveAsync(key, _cas) .ContinueWith(t => { if (t.IsFaulted) { _log.Warn($"Error releasing lock '{Name}' for holder '{Holder}'", t.Exception); } else if (t.Result != null) { var result = t.Result; if (result.Status == ResponseStatus.KeyNotFound) { _log.Debug("Did not release lock '{0}' for holder '{1}' because it was already released.", Name, Holder); } else if (result.Status == ResponseStatus.DocumentMutationDetected) { _log.Debug( "Did not release lock '{0}' for holder '{1}' because it was already held by another.", Name, Holder); } else if (!result.Success) { _log.Warn("Error releasing lock '{0}' for holder '{1}': {2}", Name, Holder, result.Exception?.Message ?? result.Message); } } }); }
/// <inheritdoc/> public async Task Renew(TimeSpan expiration, CancellationToken cancellationToken = default) { if (expiration <= TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(expiration), "Value must be positive."); } var key = LockDocument.GetKey(Name); var document = new LockDocument { Holder = Holder, RequestedDateTime = DateTime.UtcNow }; bool lockAcquired = false; IMutationResult?result = null; try { if (_cas == 0) { // We're creating a new lock _logger.LogDebug("Requesting lock '{name}' for holder '{holder}' for {expiration}", Name, Holder, expiration); result = await _collection.InsertAsync(key, document, new InsertOptions().Expiry(expiration).CancellationToken(cancellationToken)) .ConfigureAwait(false); } else { _logger.LogDebug("Renewing lock '{name}' for holder '{holder}' for {expiration}", Name, Holder, expiration); result = await _collection.ReplaceAsync(key, document, new ReplaceOptions().Expiry(expiration).CancellationToken(cancellationToken).Cas(_cas)) .ConfigureAwait(false); } lockAcquired = true; } catch (CasMismatchException) { // This is a valid case, trap the exception _logger.LogDebug("CAS mismatch updating lock '{name}'", Name); } catch (DocumentExistsException) { // This is a valid case, trap the exception _logger.LogDebug("Lock document already exists for lock '{name}'", Name); } catch (DocumentNotFoundException) { // This is a valid, but rare, case where the document being renewed expired before // the renewal. In this case, we'll let the logic move on, which will recreate the document. _logger.LogDebug("Lock document missing for lock '{name}'", Name); } if (lockAcquired) { _logger.LogDebug("Lock '{name}' issued to holder '{holder}'", Name, Holder); _expirationInterval = expiration; _cas = result !.Cas; return; } if (!lockAcquired) { _logger.LogDebug("Lock '{name}' unavailable, getting lock info", Name); IGetResult getResult; try { getResult = await _collection.GetAsync(key).ConfigureAwait(false); } catch (DocumentNotFoundException) { try { // Couldn't find the lock, must have expired between Insert and Get, try one more time result = await _collection.InsertAsync(key, document, new InsertOptions().Expiry(expiration).CancellationToken(cancellationToken)) .ConfigureAwait(false); } catch (DocumentExistsException) { throw new CouchbaseLockUnavailableException(Name); } _logger.LogDebug("Lock '{name}' issued to holder '{holder}'", Name, Holder); _expirationInterval = expiration; _cas = result.Cas; return; } _logger.LogDebug("Unable to acquire lock '{name}' for holder '{holder}'", Name, Holder); throw new CouchbaseLockUnavailableException(Name) { Holder = getResult.ContentAs <LockDocument>().Holder }; } }
/// <inheritdoc/> public async Task Renew(TimeSpan expiration) { if (expiration <= TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(expiration), "Value must be positive."); } var key = LockDocument.GetKey(Name); var document = new LockDocument { Holder = Holder, RequestedDateTime = DateTime.UtcNow }; IOperationResult <LockDocument> result; if (_cas == 0) { // We're creating a new lock _log.Debug("Requesting lock '{0}' for holder '{1}' for {2}", Name, Holder, expiration); result = await _bucket.InsertAsync(key, document, expiration).ConfigureAwait(false); } else { _log.Debug("Renewing lock '{0}' for holder '{1}' for {2}", Name, Holder, expiration); result = await _bucket.UpsertAsync(key, document, _cas, expiration).ConfigureAwait(false); } if (result.Status == ResponseStatus.DocumentMutationDetected || result.Status == ResponseStatus.KeyExists) { _log.Debug("Lock '{0}' unavailable, getting lock info", Name); var getResult = await _bucket.GetDocumentAsync <LockDocument>(key).ConfigureAwait(false); if (getResult.Status == ResponseStatus.KeyNotFound) { // Couldn't find the lock, must have expired between Insert and Get, try one more time result = await _bucket.InsertAsync(key, document, expiration).ConfigureAwait(false); if (result.Status == ResponseStatus.KeyExists) { throw new CouchbaseLockUnavailableException(Name); } _log.Debug("Lock '{0}' issued to holder '{1}'", Name, Holder); result.EnsureSuccess(); _expirationInterval = expiration; _cas = result.Cas; return; } getResult.EnsureSuccess(); _log.Debug("Unable to acquire lock '{0}' for holder '{1}'", Name, Holder); throw new CouchbaseLockUnavailableException(Name) { Holder = getResult.Content.Holder }; } _log.Debug("Lock '{0}' issued to holder '{1}'", Name, Holder); result.EnsureSuccess(); _expirationInterval = expiration; _cas = result.Cas; }