< Summary

Information
Class: IceRpc.Deadline.DeadlineInterceptor
Assembly: IceRpc.Deadline
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Deadline/DeadlineInterceptor.cs
Tag: 1321_24790053727
Line coverage
96%
Covered lines: 50
Uncovered lines: 2
Coverable lines: 52
Total lines: 113
Line coverage: 96.1%
Branch coverage
88%
Covered branches: 23
Total branches: 26
Branch coverage: 88.4%
Method coverage
100%
Covered methods: 3
Fully covered methods: 2
Total methods: 3
Method coverage: 100%
Full method coverage: 66.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%66100%
InvokeAsync(...)85%202093.33%
PerformInvokeAsync()100%11100%

File(s)

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

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Extensions.DependencyInjection;
 4using IceRpc.Features;
 5using ZeroC.Slice.Codec;
 6
 7namespace IceRpc.Deadline;
 8
 9/// <summary>Represents an interceptor that sets deadlines on requests without deadlines, and enforces these deadlines.
 10/// </summary>
 11/// <remarks>When a request doesn't carry an <see cref="IDeadlineFeature"/> feature, this interceptor computes a
 12/// deadline using its configured default timeout; otherwise, it uses the request's existing deadline feature. It then
 13/// encodes the deadline value as a <see cref="RequestFieldKey.Deadline" /> field and makes the invocation throw a
 14/// <see cref="TimeoutException" /> upon expiration of this deadline.<br/>
 15/// The dispatch of a one-way request cannot be canceled since the invocation typically completes before this dispatch
 16/// starts; as a result, for a one-way request, the deadline must be enforced by a <see cref="DeadlineMiddleware"/>.
 17/// <br/>
 18/// If the server installs a <see cref="DeadlineMiddleware"/>, this deadline middleware decodes the deadline and
 19/// enforces it. In the unlikely event the middleware detects the expiration of the deadline before this interceptor,
 20/// the invocation will return an <see cref="OutgoingResponse"/> carrying status code
 21/// <see cref="StatusCode.DeadlineExceeded"/>.<br/>
 22/// The deadline interceptor must be installed before any interceptor than can run multiple times per request. In
 23/// particular, it must be installed before the retry interceptor.</remarks>
 24/// <seealso cref="DeadlinePipelineExtensions"/>
 25/// <seealso cref="DeadlineInvokerBuilderExtensions"/>
 26public class DeadlineInterceptor : IInvoker
 27{
 28    private readonly bool _alwaysEnforceDeadline;
 29    private readonly IInvoker _next;
 30    private readonly TimeSpan _defaultTimeout;
 31    private readonly TimeProvider _timeProvider;
 32
 33    /// <summary>Constructs a Deadline interceptor.</summary>
 34    /// <param name="next">The next invoker in the invocation pipeline.</param>
 35    /// <param name="defaultTimeout">The default timeout. When not infinite, the interceptor adds a deadline to requests
 36    /// without a deadline.</param>
 37    /// <param name="timeProvider">The optional time provider used to obtain the current time. If <see langword="null"/>
 38    /// <see cref="TimeProvider.System"/>.</param>
 39    /// <param name="alwaysEnforceDeadline">When <see langword="true" /> and the request carries a deadline, the
 40    /// interceptor always creates a cancellation token source to enforce this deadline. When <see langword="false" />
 41    /// and the request carries a deadline, the interceptor creates a cancellation token source to enforce this deadline
 42    /// only when the invocation's cancellation token cannot be canceled. The default value is <see langword="false" />.
 43    /// </param>
 1144    public DeadlineInterceptor(IInvoker next, TimeSpan defaultTimeout, bool alwaysEnforceDeadline, TimeProvider? timePro
 1145    {
 1146        if (defaultTimeout != Timeout.InfiniteTimeSpan && defaultTimeout <= TimeSpan.Zero)
 247        {
 248            throw new ArgumentException(
 249                $"The {nameof(defaultTimeout)} value must be positive or Timeout.InfiniteTimeSpan.",
 250                nameof(defaultTimeout));
 51        }
 52
 953        _next = next;
 954        _alwaysEnforceDeadline = alwaysEnforceDeadline;
 955        _defaultTimeout = defaultTimeout;
 956        _timeProvider = timeProvider ?? TimeProvider.System;
 957    }
 58
 59    /// <inheritdoc/>
 60    public Task<IncomingResponse> InvokeAsync(OutgoingRequest request, CancellationToken cancellationToken = default)
 861    {
 862        TimeSpan? timeout = null;
 863        DateTime deadline = DateTime.MaxValue;
 64
 865        DateTime now = _timeProvider.GetUtcNow().UtcDateTime;
 866        if (request.Features.Get<IDeadlineFeature>() is IDeadlineFeature deadlineFeature)
 667        {
 68            // Normalize to UTC.
 669            deadline = deadlineFeature.Value == DateTime.MaxValue ?
 670                DateTime.MaxValue : deadlineFeature.Value.ToUniversalTime();
 671            if (deadline != DateTime.MaxValue && (_alwaysEnforceDeadline || !cancellationToken.CanBeCanceled))
 572            {
 573                timeout = deadline - now;
 574            }
 675        }
 276        else if (_defaultTimeout != Timeout.InfiniteTimeSpan)
 277        {
 278            timeout = _defaultTimeout;
 279            deadline = now + timeout.Value;
 280        }
 81
 882        if (timeout is not null && timeout.Value <= TimeSpan.Zero)
 083        {
 084            throw new TimeoutException("The request deadline has expired.");
 85        }
 86
 887        if (deadline != DateTime.MaxValue)
 888        {
 889            request.Fields = request.Fields.With(
 890                RequestFieldKey.Deadline,
 891                deadline,
 1392                (ref SliceEncoder encoder, DateTime deadline) => encoder.EncodeTimeStamp(deadline));
 893        }
 94
 895        return timeout is null ? _next.InvokeAsync(request, cancellationToken) : PerformInvokeAsync(timeout.Value);
 96
 97        async Task<IncomingResponse> PerformInvokeAsync(TimeSpan timeout)
 798        {
 799            using var timeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 7100            timeoutTokenSource.CancelAfter(timeout);
 101
 102            try
 7103            {
 7104                return await _next.InvokeAsync(request, timeoutTokenSource.Token).ConfigureAwait(false);
 105            }
 2106            catch (OperationCanceledException exception) when (exception.CancellationToken == timeoutTokenSource.Token)
 2107            {
 2108                cancellationToken.ThrowIfCancellationRequested();
 2109                throw new TimeoutException("The request deadline has expired.");
 110            }
 5111        }
 8112    }
 113}