public async Task ReplaceDocumentAsync(ReplaceOneModel <BsonDocument> model, CancellationToken token) { var sw = new Stopwatch(); sw.Start(); try { var result = await WithRetry.ExecuteAsync(3, TimeSpan.FromSeconds(5), () => _collection.ReplaceOneAsync(model.Filter, model.Replacement, new ReplaceOptions { BypassDocumentValidation = true }, token), e => e is MongoWriteException mwe && mwe.WriteError.Code == MongoErrorCodes.LockTimeout, token, e => _logger.Warning(e, "Failed replace with retryable exception")); sw.Stop(); _logger.Debug( result.ModifiedCount == 1 ? "Successfully retried replacement of document (ID: {Id}) in {Elapsed:N1} ms" : "Failed replacement of document (ID: {Id}) in {Elapsed:N1} ms. Document is missing", model.Replacement["_id"], sw.ElapsedMilliseconds); } catch (OperationCanceledException) { throw; } catch (Exception e) { throw new ReplaceOneException($"Failed to replace document: {e.Message}", e); } }
public async Task ExecuteAsync_ShouldThrowArgumentException_AttemptsIsLessOrEqualToZero(int attempts) { // Arrange var func = () => WithRetry.ExecuteAsync(attempts, TimeSpan.Zero, () => Task.FromResult(0), _ => true, default); // Act & Assert await func.Awaiting(f => f.Invoke()).Should().ThrowExactlyAsync <ArgumentException>(); }
public void ExecuteAsync_ShouldThrowExceptionFromFunction_WrappedFunctionThrowsException(int attempts) { // Arrange var exception = new Exception($"Attempt failed"); var func = () => WithRetry.ExecuteAsync(attempts, TimeSpan.Zero, () => Task.FromException <int>(exception), _ => true, default); // Act & Assert func.Awaiting(f => f.Invoke()).Should().Throw <Exception>().Which.Should().Be(exception); }
public async Task ExecuteAsync_ShouldInvokeCallbackOnRetry_WrappedFunctionFails() { // Arrange var onRetryMock = new Mock <Action <Exception> >(); var wrappedFunc = new Mock <Func <Task <int> > >(); wrappedFunc.SetupSequence(wf => wf()) .ThrowsAsync(new Exception()) .ReturnsAsync(42); // Act await WithRetry.ExecuteAsync(2, TimeSpan.Zero, wrappedFunc.Object, _ => true, default, onRetryMock.Object);
public void ExecuteAsync_ShouldRetryOnlyForSpecifiedException_ShouldRetryPredicateChecksException() { // Arrange var retryableException = new Exception("Retryable"); var notRetryableException = new Exception("Not Retryable"); var wrappedFunc = new Mock <Func <Task <int> > >(); wrappedFunc.SetupSequence(wf => wf()) .ThrowsAsync(retryableException) .ThrowsAsync(notRetryableException) .ReturnsAsync(42); var func = () => WithRetry.ExecuteAsync(4, TimeSpan.Zero, wrappedFunc.Object, e => e == retryableException, default); // Act & Assert func.Awaiting(f => f.Invoke()).Should().Throw <Exception>().Which.Should().Be(notRetryableException); }
public async Task ExecuteAsync_ShouldReturnValue_WrappedFunctionSucceededOnLastAttempt(int attempts) { // Arrange var wrappedFunc = new Mock <Func <Task <int> > >(); var seq = wrappedFunc.SetupSequence(wf => wf()); for (var i = 1; i < attempts; i++) { seq.Throws <Exception>(); } seq.ReturnsAsync(42); // Act var actual = await WithRetry.ExecuteAsync(attempts, TimeSpan.Zero, wrappedFunc.Object, _ => true, default); // Assert actual.Should().Be(42); }
public async Task ExecuteAsync_ShouldRunFunctionExactNumberOfTimes_WrappedFunctionThrowsException(int attempts) { // Arrange var count = 0; // Act try { await WithRetry.ExecuteAsync(attempts, TimeSpan.Zero, () => Task.FromException <int>(new Exception($"Attempt {count++} failed")), _ => true, default); } catch (Exception) { // Discard thrown exception } // Assert count.Should().Be(attempts); }
public async Task ExecuteAsync_ShouldWaitBeforeEachAttempt_WrappedFunctionSucceededOnLastAttempt() { // Arrange var cooldown = TimeSpan.FromSeconds(2); var waitBetweenAttempts = new Stopwatch(); var wrappedFunc = () => { if (waitBetweenAttempts.IsRunning) { waitBetweenAttempts.Stop(); return(Task.FromResult(42)); } waitBetweenAttempts.Start(); return(Task.FromException <int>(new Exception("Attempt failed"))); }; // Act await WithRetry.ExecuteAsync(2, cooldown, wrappedFunc, _ => true, default); // Assert waitBetweenAttempts.Elapsed.Should().BeCloseTo(cooldown); }