I have a service which writes files over some network using FileStream,
without a write timeout. I added a Polly WaitAndRetry
policy to handle occasional write failures, and that works great. I noticed that sometimes a write just hangs though, so I'm trying to wrap a Timeout policy inside the retry policy in order to limit each write attempt.
public class Retrier
{
private readonly CancellationToken _cancellationToken;
public Retrier(CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
}
public void Start(IService service)
{
var retryPolicy = Policy
.Handle<IOException>()
.WaitAndRetry(
retryCount: 3,
sleepDurationProvider: (retries) => TimeSpan.FromSeconds(retries * 10));
var timeoutPolicy = Policy.Timeout(seconds: 60);
var combinedPolicy = retryPolicy.Wrap(timeoutPolicy);
var result = combinedPolicy.ExecuteAndCapture(
(ct) =>
{
service.Write(ct);
},
_cancellationToken);
// Do some other stuff
}
}
However, with the wrapped policy, the Write action does not get called at all, e.g. ExecuteAndCapture
is entered and action called, but execution just continues to "do other other stuff" below. A simple test method verifies that Write
is called zero times.
[TestMethod]
public void Retrier_Start_When_IOExceptionOnce_CallsExecuteTwice()
{
var attempt = 0;
var mock = new Mock<IService>();
mock
.Setup(svc => svc.Write(_cancellationTokenSource.Token))
.Returns(() =>
{
attempt++;
if (attempt == 1)
{
throw new IOException("Failed");
}
return 1;
})
.Verifiable();
_testee.Start(mock.Object);
mock.Verify(svc => svc.Write(_cancellationTokenSource.Token), Times.Exactly(2));
}
Write is a simple service, no magic:
public int Write(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var tempFullPath = ...
using (var fw = new FileStream(tempFullPath, FileMode.Create, FileAccess.Write))
{
fw.Write(_buffer, 0, _buffer.Length);
}
File.Move(tempFullPath, _finalFullPath);
return ++Count;
}
I went over similar questions, but couldn't find a solution. What am I doing wrong?