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);
            }
        }
Example #2
0
    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>();
    }
Example #3
0
    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);
    }
Example #4
0
    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);
Example #5
0
    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);
    }
Example #6
0
    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);
    }
Example #7
0
    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);
    }
Example #8
0
    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);
    }