< Summary

Information
Class: IceRpc.Deadline.DeadlineInterceptor
Assembly: IceRpc.Deadline
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Deadline/DeadlineInterceptor.cs
Tag: 592_20856082467
Line coverage
95%
Covered lines: 44
Uncovered lines: 2
Coverable lines: 46
Total lines: 104
Line coverage: 95.6%
Branch coverage
90%
Covered branches: 18
Total branches: 20
Branch coverage: 90%
Method coverage
100%
Covered methods: 3
Total methods: 3
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
InvokeAsync(...)88.88%18.111893.1%
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;
 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>
 544    public DeadlineInterceptor(IInvoker next, TimeSpan defaultTimeout, bool alwaysEnforceDeadline, TimeProvider? timePro
 545    {
 546        _next = next;
 547        _alwaysEnforceDeadline = alwaysEnforceDeadline;
 548        _defaultTimeout = defaultTimeout;
 549        _timeProvider = timeProvider ?? TimeProvider.System;
 550    }
 51
 52    /// <inheritdoc/>
 53    public Task<IncomingResponse> InvokeAsync(OutgoingRequest request, CancellationToken cancellationToken = default)
 554    {
 555        TimeSpan? timeout = null;
 556        DateTime deadline = DateTime.MaxValue;
 57
 558        DateTime now = _timeProvider.GetUtcNow().UtcDateTime;
 559        if (request.Features.Get<IDeadlineFeature>() is IDeadlineFeature deadlineFeature)
 360        {
 361            deadline = deadlineFeature.Value;
 362            if (deadline != DateTime.MaxValue && (_alwaysEnforceDeadline || !cancellationToken.CanBeCanceled))
 263            {
 264                timeout = deadline - now;
 265            }
 366        }
 267        else if (_defaultTimeout != Timeout.InfiniteTimeSpan)
 268        {
 269            timeout = _defaultTimeout;
 270            deadline = now + timeout.Value;
 271        }
 72
 573        if (timeout is not null && timeout.Value <= TimeSpan.Zero)
 074        {
 075            throw new TimeoutException("The request deadline has expired.");
 76        }
 77
 578        if (deadline != DateTime.MaxValue)
 579        {
 580            request.Fields = request.Fields.With(
 581                RequestFieldKey.Deadline,
 582                deadline,
 783                (ref SliceEncoder encoder, DateTime deadline) => encoder.EncodeTimeStamp(deadline));
 584        }
 85
 586        return timeout is null ? _next.InvokeAsync(request, cancellationToken) : PerformInvokeAsync(timeout.Value);
 87
 88        async Task<IncomingResponse> PerformInvokeAsync(TimeSpan timeout)
 489        {
 490            using var timeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 491            timeoutTokenSource.CancelAfter(timeout);
 92
 93            try
 494            {
 495                return await _next.InvokeAsync(request, timeoutTokenSource.Token).ConfigureAwait(false);
 96            }
 297            catch (OperationCanceledException exception) when (exception.CancellationToken == timeoutTokenSource.Token)
 298            {
 299                cancellationToken.ThrowIfCancellationRequested();
 2100                throw new TimeoutException("The request deadline has expired.");
 101            }
 2102        }
 5103    }
 104}