MediatR
MediatR copied to clipboard
Timeout?
I'd like to be able to specify (or implement) a timeout for all my requests and responses.
I tried doing so with a behavior, but without any luck.
I initially thought I would implement it by altering the cancellation token that gets passed on to the next behaviors, but that's not possible apparently.
Is there any way to implement a timeout in Mediatr? I am okay with it being on the cancellation-token level, and only working when cancellation tokens are used.
As long as you are respecting the cancellation token in your handlers you should be able to use that.
public class TimeoutBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var source = new CancellationTokenSource(TimeSpan.FromSeconds(30));
cancellationToken = source.Token;
return next();
}
}
SO For reference https://stackoverflow.com/questions/30875279/how-to-cancel-a-cancellationtoken
But that cancelation token won't be passed on to the "next" behavior and functionality, will it?
Since its the same cancellation token being referenced by all the handlers and behaviors I believe it will.
This wouldn't change subsequent calls, you're replacing the reference on the parameter.
Oh, based on the SO I thought it would update the reference for all. In that case maybe making a mediator extension method that creates the token source with a timeout and sends with that token?
The token is simply passed through from _mediator.Send
, so if you want a different cancellation token, you can pass in something else from there.
@jbogard that makes sense - I was still hoping I could make a behavior alter the timeout duration of a request or command, based on some condition.
I'm not finding a clue to solve the problem of timeout with IPipelineBehavior
.
using MediatR;
using Polly;
public class TimeoutBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var timeoutPolicy = Policy.TimeoutAsync(2);
var response = await timeoutPolicy
.ExecuteAsync(async (token) =>
{
using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(token, cancellationToken))
{
// HOW TO?
//cancellationToken = linkedCts.Token;
return await next();
}
}, CancellationToken.None);
return response;
}
}
public class LongRunningHandler : IRequestHandler<Job, Unit>
{
public async Task<Unit> Handle(Job request, CancellationToken cancellationToken)
{
// Timeout? cancellationToken
await Task.Delay(4000, cancellationToken);
return await Unit.Task;
}
}
@hhko that is exactly the problem I am having. I believe it would be nice if the cancellation token could be altered by a behavior.
I had the exact use case as being discussed above, i.e. the ability to change the cancellation token from inside a behavior.
@jbogard would this be a possible solution?
public delegate Task<TResponse> RequestHandlerDelegate<TResponse>(CancellationToken cancellationToken = default);
and then update the Handle method in RequestHandlerWrapperImpl as below.
public override Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken,
ServiceFactory serviceFactory)
{
Task<TResponse> Handler(CancellationToken ct) =>
GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, ct);
return serviceFactory
.GetInstances<IPipelineBehavior<TRequest, TResponse>>()
.Reverse()
.Aggregate((RequestHandlerDelegate<TResponse>) Handler,
(next, pipeline) => ct => pipeline.Handle((TRequest) request, ct, next))
(cancellationToken);
}
You can decorate IMediator with Scrutor and implement polly optimistic timeout in decorator.
@jbogard this was marked as completed. Can you link to documentation or the commit where it was fixed? 🙏
GitHub's UX marked it as completed. Not me. I did a bulk-close and GitHub marked it as such.
In any case, this is better suited as a discussion, not an issue.