MediatR icon indicating copy to clipboard operation
MediatR copied to clipboard

Timeout?

Open ffMathy opened this issue 4 years ago • 10 comments

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.

ffMathy avatar Dec 03 '20 08:12 ffMathy

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

lilasquared avatar Dec 03 '20 15:12 lilasquared

But that cancelation token won't be passed on to the "next" behavior and functionality, will it?

ffMathy avatar Dec 03 '20 16:12 ffMathy

Since its the same cancellation token being referenced by all the handlers and behaviors I believe it will.

lilasquared avatar Dec 03 '20 16:12 lilasquared

This wouldn't change subsequent calls, you're replacing the reference on the parameter.

jbogard avatar Dec 03 '20 16:12 jbogard

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?

lilasquared avatar Dec 03 '20 16:12 lilasquared

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 avatar Dec 03 '20 16:12 jbogard

@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.

ffMathy avatar Dec 03 '20 17:12 ffMathy

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 avatar Mar 09 '21 04:03 hhko

@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.

ffMathy avatar Mar 09 '21 07:03 ffMathy

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);
        }

DonnelCyril avatar Dec 09 '21 20:12 DonnelCyril

You can decorate IMediator with Scrutor and implement polly optimistic timeout in decorator.

one-smart avatar Nov 08 '22 09:11 one-smart

@jbogard this was marked as completed. Can you link to documentation or the commit where it was fixed? 🙏

ffMathy avatar Feb 02 '23 18:02 ffMathy

GitHub's UX marked it as completed. Not me. I did a bulk-close and GitHub marked it as such.

jbogard avatar Feb 02 '23 19:02 jbogard

In any case, this is better suited as a discussion, not an issue.

jbogard avatar Feb 02 '23 19:02 jbogard