< Summary

Information
Class: IceRpc.Internal.IceDuplexConnectionDecorator
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Internal/IceDuplexConnectionDecorator.cs
Tag: 592_20856082467
Line coverage
98%
Covered lines: 54
Uncovered lines: 1
Coverable lines: 55
Total lines: 112
Line coverage: 98.1%
Branch coverage
50%
Covered branches: 2
Total branches: 4
Branch coverage: 50%
Method coverage
90%
Covered methods: 10
Total methods: 11
Method coverage: 90.9%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ConnectAsync(...)100%11100%
Dispose()100%11100%
ReadAsync(...)50%22100%
PerformReadAsync()100%11100%
ShutdownWriteAsync(...)100%210%
WriteAsync(...)50%22100%
PerformWriteAsync()100%11100%
ScheduleHeartbeat()100%11100%
CancelWriteTimer()100%11100%
RescheduleWriteTimer()100%11100%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Internal/IceDuplexConnectionDecorator.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Transports;
 4using System.Buffers;
 5using System.Diagnostics;
 6
 7namespace IceRpc.Internal;
 8
 9/// <summary>Decorates <see cref="ReadAsync" /> to fail if no byte is received for over readIdleTimeout. Also decorates
 10/// <see cref="WriteAsync" /> to send a heartbeat (writeIdleTimeout / 2) after a successful write. Both sides of the
 11/// connection are expected to use the same idle timeouts.</summary>
 12internal class IceDuplexConnectionDecorator : IDuplexConnection
 13{
 14    private readonly IDuplexConnection _decoratee;
 19315    private readonly CancellationTokenSource _readCts = new();
 16    private readonly TimeSpan _readIdleTimeout;
 17    private readonly TimeSpan _writeIdleTimeout;
 18    private readonly Timer _writeTimer;
 19
 20    public Task<TransportConnectionInformation> ConnectAsync(CancellationToken cancellationToken) =>
 10121        _decoratee.ConnectAsync(cancellationToken);
 22
 23    public void Dispose()
 21224    {
 21225        _decoratee.Dispose();
 21226        _readCts.Dispose();
 27
 28        // Using Dispose is fine, there's no need to wait for the sendHeartbeat to complete if it's running.
 21229        _writeTimer.Dispose();
 21230    }
 31
 32    public ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 1951233    {
 1951234        return _readIdleTimeout == Timeout.InfiniteTimeSpan ?
 1951235            _decoratee.ReadAsync(buffer, cancellationToken) :
 1951236            PerformReadAsync();
 37
 38        async ValueTask<int> PerformReadAsync()
 1951239        {
 40            try
 1951241            {
 1951242                using CancellationTokenRegistration _ = cancellationToken.UnsafeRegister(
 7843                    cts => ((CancellationTokenSource)cts!).Cancel(),
 1951244                    _readCts);
 1951245                _readCts.CancelAfter(_readIdleTimeout); // enable idle timeout before reading
 1951246                return await _decoratee.ReadAsync(buffer, _readCts.Token).ConfigureAwait(false);
 47            }
 6448            catch (OperationCanceledException)
 6449            {
 6450                cancellationToken.ThrowIfCancellationRequested();
 51
 252                throw new IceRpcException(
 253                    IceRpcError.ConnectionIdle,
 254                    $"The connection did not receive any bytes for over {_readIdleTimeout.TotalSeconds} s.");
 55            }
 56            finally
 1951257            {
 1951258                _readCts.CancelAfter(Timeout.InfiniteTimeSpan); // disable idle timeout if not canceled
 1951259            }
 1936060        }
 1951261    }
 62
 63    public Task ShutdownWriteAsync(CancellationToken cancellationToken) =>
 064        _decoratee.ShutdownWriteAsync(cancellationToken);
 65
 66    public ValueTask WriteAsync(ReadOnlySequence<byte> buffer, CancellationToken cancellationToken)
 287167    {
 287168        return _writeIdleTimeout == Timeout.InfiniteTimeSpan ?
 287169            _decoratee.WriteAsync(buffer, cancellationToken) :
 287170            PerformWriteAsync();
 71
 72        async ValueTask PerformWriteAsync()
 287173        {
 74            // No need to send a heartbeat now since we're about to write.
 287175            CancelWriteTimer();
 76
 287177            await _decoratee.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
 78
 79            // After each successful write, we schedule a heartbeat at _writeIdleTimeout / 2 in the future.
 80            // Since each heartbeat is itself a write, if there is no application activity at all, we'll send successive
 81            // heartbeats at _writeIdleTimeout / 2 intervals.
 286682            RescheduleWriteTimer();
 286683        }
 287184    }
 85
 86    /// <summary>Constructs a decorator that ensures a call to <see cref="ReadAsync" /> will fail after readIdleTimeout.
 87    /// This decorator also schedules a heartbeat after each write (see <see cref="RescheduleWriteTimer" />).</summary>
 19388    internal IceDuplexConnectionDecorator(
 19389        IDuplexConnection decoratee,
 19390        TimeSpan readIdleTimeout,
 19391        TimeSpan writeIdleTimeout,
 19392        Action sendHeartbeat)
 19393    {
 19394        Debug.Assert(writeIdleTimeout != Timeout.InfiniteTimeSpan);
 19395        _decoratee = decoratee;
 19396        _readIdleTimeout = readIdleTimeout; // can be infinite i.e. disabled
 19397        _writeIdleTimeout = writeIdleTimeout;
 20698        _writeTimer = new Timer(_ => sendHeartbeat());
 99        // We can't schedule the initial heartbeat yet. The heartbeat is an ice protocol frame; we can send it only once
 100        // the connection is connected at the ice protocol level.
 193101    }
 102
 103    /// <summary>Schedules the initial heartbeat. Called by a client IceProtocolConnection after it receives the
 104    /// initial ValidateConnection frame from the server.</summary>
 82105    internal void ScheduleHeartbeat() => RescheduleWriteTimer();
 106
 107    /// <summary>Cancels the write timer.</summary>
 2871108    private void CancelWriteTimer() => _writeTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
 109
 110    /// <summary>Schedules or reschedules the write timer. We send a heartbeat when this timer expires.</summary>
 2948111    private void RescheduleWriteTimer() => _writeTimer.Change(_writeIdleTimeout / 2, Timeout.InfiniteTimeSpan);
 112}