< Summary

Information
Class: IceRpc.Internal.IceDuplexConnectionDecorator
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Internal/IceDuplexConnectionDecorator.cs
Tag: 1321_24790053727
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
Fully covered methods: 9
Total methods: 11
Method coverage: 90.9%
Full method coverage: 81.8%

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;
 21915    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) =>
 11421        _decoratee.ConnectAsync(cancellationToken);
 22
 23    public void Dispose()
 24224    {
 24225        _decoratee.Dispose();
 24226        _readCts.Dispose();
 27
 28        // Using Dispose is fine, there's no need to wait for the sendHeartbeat to complete if it's running.
 24229        _writeTimer.Dispose();
 24230    }
 31
 32    public ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 1980233    {
 1980234        return _readIdleTimeout == Timeout.InfiniteTimeSpan ?
 1980235            _decoratee.ReadAsync(buffer, cancellationToken) :
 1980236            PerformReadAsync();
 37
 38        async ValueTask<int> PerformReadAsync()
 1980239        {
 40            try
 1980241            {
 1980242                using CancellationTokenRegistration _ = cancellationToken.UnsafeRegister(
 10543                    cts => ((CancellationTokenSource)cts!).Cancel(),
 1980244                    _readCts);
 1980245                _readCts.CancelAfter(_readIdleTimeout); // enable idle timeout before reading
 1980246                return await _decoratee.ReadAsync(buffer, _readCts.Token).ConfigureAwait(false);
 47            }
 7448            catch (OperationCanceledException)
 7449            {
 7450                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
 1980257            {
 1980258                _readCts.CancelAfter(Timeout.InfiniteTimeSpan); // disable idle timeout if not canceled
 1980259            }
 1962760        }
 1980261    }
 62
 63    public Task ShutdownWriteAsync(CancellationToken cancellationToken) =>
 064        _decoratee.ShutdownWriteAsync(cancellationToken);
 65
 66    public ValueTask WriteAsync(ReadOnlySequence<byte> buffer, CancellationToken cancellationToken)
 290667    {
 290668        return _writeIdleTimeout == Timeout.InfiniteTimeSpan ?
 290669            _decoratee.WriteAsync(buffer, cancellationToken) :
 290670            PerformWriteAsync();
 71
 72        async ValueTask PerformWriteAsync()
 290673        {
 74            // No need to send a heartbeat now since we're about to write.
 290675            CancelWriteTimer();
 76
 290677            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.
 290182            RescheduleWriteTimer();
 290183        }
 290684    }
 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>
 21988    internal IceDuplexConnectionDecorator(
 21989        IDuplexConnection decoratee,
 21990        TimeSpan readIdleTimeout,
 21991        TimeSpan writeIdleTimeout,
 21992        Action sendHeartbeat)
 21993    {
 21994        Debug.Assert(writeIdleTimeout != Timeout.InfiniteTimeSpan);
 21995        _decoratee = decoratee;
 21996        _readIdleTimeout = readIdleTimeout; // can be infinite i.e. disabled
 21997        _writeIdleTimeout = writeIdleTimeout;
 23298        _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.
 219101    }
 102
 103    /// <summary>Schedules the initial heartbeat. Called by a client IceProtocolConnection after it receives the
 104    /// initial ValidateConnection frame from the server.</summary>
 95105    internal void ScheduleHeartbeat() => RescheduleWriteTimer();
 106
 107    /// <summary>Cancels the write timer.</summary>
 2906108    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>
 2996111    private void RescheduleWriteTimer() => _writeTimer.Change(_writeIdleTimeout / 2, Timeout.InfiniteTimeSpan);
 112}