< Summary

Information
Class: IceRpc.Deadline.DeadlineInterceptor
Assembly: IceRpc.Deadline
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Deadline/DeadlineInterceptor.cs
Tag: 275_13775359185
Line coverage
95%
Covered lines: 42
Uncovered lines: 2
Coverable lines: 44
Total lines: 99
Line coverage: 95.4%
Branch coverage
88%
Covered branches: 16
Total branches: 18
Branch coverage: 88.8%
Method coverage
100%
Covered methods: 3
Total methods: 3
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
InvokeAsync(...)88.88%18.121892.85%
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
 32    /// <summary>Constructs a Deadline interceptor.</summary>
 33    /// <param name="next">The next invoker in the invocation pipeline.</param>
 34    /// <param name="defaultTimeout">The default timeout. When not infinite, the interceptor adds a deadline to requests
 35    /// without a deadline.</param>
 36    /// <param name="alwaysEnforceDeadline">When <see langword="true" /> and the request carries a deadline, the
 37    /// interceptor always creates a cancellation token source to enforce this deadline. When <see langword="false" />
 38    /// and the request carries a deadline, the interceptor creates a cancellation token source to enforce this deadline
 39    /// only when the invocation's cancellation token cannot be canceled. The default value is <see langword="false" />.
 40    /// </param>
 541    public DeadlineInterceptor(IInvoker next, TimeSpan defaultTimeout, bool alwaysEnforceDeadline)
 542    {
 543        _next = next;
 544        _alwaysEnforceDeadline = alwaysEnforceDeadline;
 545        _defaultTimeout = defaultTimeout;
 546    }
 47
 48    /// <inheritdoc/>
 49    public Task<IncomingResponse> InvokeAsync(OutgoingRequest request, CancellationToken cancellationToken = default)
 550    {
 551        TimeSpan? timeout = null;
 552        DateTime deadline = DateTime.MaxValue;
 53
 554        if (request.Features.Get<IDeadlineFeature>() is IDeadlineFeature deadlineFeature)
 355        {
 356            deadline = deadlineFeature.Value;
 357            if (deadline != DateTime.MaxValue && (_alwaysEnforceDeadline || !cancellationToken.CanBeCanceled))
 258            {
 259                timeout = deadline - DateTime.UtcNow;
 260            }
 361        }
 262        else if (_defaultTimeout != Timeout.InfiniteTimeSpan)
 263        {
 264            timeout = _defaultTimeout;
 265            deadline = DateTime.UtcNow + timeout.Value;
 266        }
 67
 568        if (timeout is not null && timeout.Value <= TimeSpan.Zero)
 069        {
 070            throw new TimeoutException("The request deadline has expired.");
 71        }
 72
 573        if (deadline != DateTime.MaxValue)
 574        {
 575            request.Fields = request.Fields.With(
 576                RequestFieldKey.Deadline,
 577                deadline,
 778                (ref SliceEncoder encoder, DateTime deadline) => encoder.EncodeTimeStamp(deadline));
 579        }
 80
 581        return timeout is null ? _next.InvokeAsync(request, cancellationToken) : PerformInvokeAsync(timeout.Value);
 82
 83        async Task<IncomingResponse> PerformInvokeAsync(TimeSpan timeout)
 484        {
 485            using var timeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 486            timeoutTokenSource.CancelAfter(timeout);
 87
 88            try
 489            {
 490                return await _next.InvokeAsync(request, timeoutTokenSource.Token).ConfigureAwait(false);
 91            }
 292            catch (OperationCanceledException exception) when (exception.CancellationToken == timeoutTokenSource.Token)
 293            {
 294                cancellationToken.ThrowIfCancellationRequested();
 295                throw new TimeoutException("The request deadline has expired.");
 96            }
 297        }
 598    }
 99}