| | | 1 | | // Copyright (c) ZeroC, Inc. |
| | | 2 | | |
| | | 3 | | using IceRpc.Extensions.DependencyInjection; |
| | | 4 | | using IceRpc.Features; |
| | | 5 | | using ZeroC.Slice; |
| | | 6 | | |
| | | 7 | | namespace IceRpc.Deadline; |
| | | 8 | | |
| | | 9 | | /// <summary>Represents a middleware that decodes deadline fields into deadline features. When the decoded deadline |
| | | 10 | | /// expires, this middleware cancels the dispatch and returns an <see cref="OutgoingResponse" /> with status code |
| | | 11 | | /// <see cref="StatusCode.DeadlineExceeded" />.</summary> |
| | | 12 | | /// <seealso cref="DeadlineRouterExtensions"/> |
| | | 13 | | /// <seealso cref="DeadlineDispatcherBuilderExtensions"/> |
| | | 14 | | public class DeadlineMiddleware : IDispatcher |
| | | 15 | | { |
| | | 16 | | private readonly IDispatcher _next; |
| | | 17 | | private readonly TimeProvider _timeProvider; |
| | | 18 | | |
| | | 19 | | /// <summary>Constructs a deadline middleware.</summary> |
| | | 20 | | /// <param name="next">The next dispatcher in the dispatch pipeline.</param> |
| | | 21 | | /// <param name="timeProvider">The optional time provider used to obtain the current time. If <see langword="null"/> |
| | | 22 | | /// <see cref="TimeProvider.System"/>.</param> |
| | 2 | 23 | | public DeadlineMiddleware(IDispatcher next, TimeProvider? timeProvider = null) |
| | 2 | 24 | | { |
| | 2 | 25 | | _next = next; |
| | 2 | 26 | | _timeProvider = timeProvider ?? TimeProvider.System; |
| | 2 | 27 | | } |
| | | 28 | | |
| | | 29 | | /// <inheritdoc/> |
| | | 30 | | public ValueTask<OutgoingResponse> DispatchAsync( |
| | | 31 | | IncomingRequest request, |
| | | 32 | | CancellationToken cancellationToken = default) |
| | 2 | 33 | | { |
| | 2 | 34 | | TimeSpan? timeout = null; |
| | | 35 | | |
| | | 36 | | // not found returns default == DateTime.MinValue. |
| | 2 | 37 | | DateTime deadline = request.Fields.DecodeValue( |
| | 2 | 38 | | RequestFieldKey.Deadline, |
| | 4 | 39 | | (ref SliceDecoder decoder) => decoder.DecodeTimeStamp()); |
| | | 40 | | |
| | 2 | 41 | | if (deadline != DateTime.MinValue) |
| | 2 | 42 | | { |
| | 2 | 43 | | timeout = deadline - _timeProvider.GetUtcNow().UtcDateTime; |
| | | 44 | | |
| | 2 | 45 | | if (timeout <= TimeSpan.Zero) |
| | 0 | 46 | | { |
| | 0 | 47 | | return new(new OutgoingResponse( |
| | 0 | 48 | | request, |
| | 0 | 49 | | StatusCode.DeadlineExceeded, |
| | 0 | 50 | | "The request deadline has expired.")); |
| | | 51 | | } |
| | | 52 | | |
| | 2 | 53 | | request.Features = request.Features.With<IDeadlineFeature>(new DeadlineFeature(deadline)); |
| | 2 | 54 | | } |
| | | 55 | | |
| | 2 | 56 | | return timeout is null ? _next.DispatchAsync(request, cancellationToken) : PerformDispatchAsync(timeout.Value); |
| | | 57 | | |
| | | 58 | | async ValueTask<OutgoingResponse> PerformDispatchAsync(TimeSpan timeout) |
| | 2 | 59 | | { |
| | 2 | 60 | | using var timeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); |
| | 2 | 61 | | timeoutTokenSource.CancelAfter(timeout); |
| | | 62 | | |
| | | 63 | | try |
| | 2 | 64 | | { |
| | 2 | 65 | | return await _next.DispatchAsync(request, timeoutTokenSource.Token).ConfigureAwait(false); |
| | | 66 | | } |
| | 1 | 67 | | catch (OperationCanceledException exception) when (exception.CancellationToken == timeoutTokenSource.Token) |
| | 1 | 68 | | { |
| | 1 | 69 | | cancellationToken.ThrowIfCancellationRequested(); |
| | 1 | 70 | | return new OutgoingResponse(request, StatusCode.DeadlineExceeded, "The request deadline has expired."); |
| | | 71 | | } |
| | 2 | 72 | | } |
| | 2 | 73 | | } |
| | | 74 | | } |