< Summary

Information
Class: IceRpc.Deadline.DeadlineMiddleware
Assembly: IceRpc.Deadline
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Deadline/DeadlineMiddleware.cs
Tag: 1856_27024993493
Line coverage
97%
Covered lines: 37
Uncovered lines: 1
Coverable lines: 38
Total lines: 89
Line coverage: 97.3%
Branch coverage
75%
Covered branches: 6
Total branches: 8
Branch coverage: 75%
Method coverage
100%
Covered methods: 4
Fully covered methods: 3
Total methods: 4
Method coverage: 100%
Full method coverage: 75%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)50%22100%
DispatchAsync(...)83.33%6695.45%
PerformDispatchAsync()100%11100%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Deadline/DeadlineMiddleware.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Extensions.DependencyInjection;
 4using IceRpc.Features;
 5using System.Buffers;
 6using ZeroC.Slice.Codec;
 7
 8namespace IceRpc.Deadline;
 9
 10/// <summary>Represents a middleware that decodes deadline fields into deadline features. When the decoded deadline
 11/// expires, this middleware cancels the dispatch and returns an <see cref="OutgoingResponse" /> with status code
 12/// <see cref="StatusCode.DeadlineExceeded" />.</summary>
 13/// <remarks>If the peer-encoded deadline is too far in the future for this middleware to enforce (more than
 14/// ~24.8 days from now), the request is rejected with <see cref="StatusCode.NotSupported" /> instead.</remarks>
 15/// <seealso cref="DeadlineRouterExtensions"/>
 16/// <seealso cref="DeadlineDispatcherBuilderExtensions"/>
 17public class DeadlineMiddleware : IDispatcher
 18{
 19    // The maximum supported timeout (int.MaxValue ms, ~24.8 days). This is the maximum delay
 20    // CancellationTokenSource.CancelAfter accepts.
 121    private static readonly TimeSpan _maxSupportedTimeout = TimeSpan.FromMilliseconds(int.MaxValue);
 22
 23    private readonly IDispatcher _next;
 24    private readonly TimeProvider _timeProvider;
 25
 26    /// <summary>Constructs a deadline middleware.</summary>
 27    /// <param name="next">The next dispatcher in the dispatch pipeline.</param>
 28    /// <param name="timeProvider">The optional time provider used to obtain the current time. If <see langword="null"/>
 29    /// <see cref="TimeProvider.System"/>.</param>
 430    public DeadlineMiddleware(IDispatcher next, TimeProvider? timeProvider = null)
 431    {
 432        _next = next;
 433        _timeProvider = timeProvider ?? TimeProvider.System;
 434    }
 35
 36    /// <inheritdoc/>
 37    public ValueTask<OutgoingResponse> DispatchAsync(
 38        IncomingRequest request,
 39        CancellationToken cancellationToken = default)
 440    {
 41        // Check explicit field presence rather than relying on a decoded-value sentinel.
 442        if (request.Fields.TryGetValue(RequestFieldKey.Deadline, out ReadOnlySequence<byte> value))
 443        {
 444            DateTime deadline = value.DecodeSliceBuffer(
 845                (ref SliceDecoder decoder) => decoder.DecodeTimeStamp());
 446            TimeSpan timeout = deadline - _timeProvider.GetUtcNow().UtcDateTime;
 47
 448            if (timeout <= TimeSpan.Zero)
 149            {
 150                return new(new OutgoingResponse(
 151                    request,
 152                    StatusCode.DeadlineExceeded,
 153                    "The request deadline has expired."));
 54            }
 55
 56            // Reject a peer-encoded deadline that exceeds what this middleware can enforce. Silently clamping
 57            // a deadline the client asked for to a smaller value the implementation supports is worse than
 58            // failing cleanly.
 359            if (timeout > _maxSupportedTimeout)
 160            {
 161                return new(new OutgoingResponse(
 162                    request,
 163                    StatusCode.NotSupported,
 164                    $"The request deadline exceeds the maximum timeout supported by this server: {_maxSupportedTimeout}.
 65            }
 66
 267            request.Features = request.Features.With<IDeadlineFeature>(new DeadlineFeature(deadline));
 268            return PerformDispatchAsync(timeout);
 69        }
 70
 071        return _next.DispatchAsync(request, cancellationToken);
 72
 73        async ValueTask<OutgoingResponse> PerformDispatchAsync(TimeSpan timeout)
 274        {
 275            using var timeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 276            timeoutTokenSource.CancelAfter(timeout);
 77
 78            try
 279            {
 280                return await _next.DispatchAsync(request, timeoutTokenSource.Token).ConfigureAwait(false);
 81            }
 182            catch (OperationCanceledException exception) when (exception.CancellationToken == timeoutTokenSource.Token)
 183            {
 184                cancellationToken.ThrowIfCancellationRequested();
 185                return new OutgoingResponse(request, StatusCode.DeadlineExceeded, "The request deadline has expired.");
 86            }
 287        }
 488    }
 89}