< Summary

Information
Class: IceRpc.Internal.IceDuplexConnectionDecorator
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Internal/IceDuplexConnectionDecorator.cs
Tag: 275_13775359185
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;
 37615    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) =>
 19721        _decoratee.ConnectAsync(cancellationToken);
 22
 23    public void Dispose()
 41024    {
 41025        _decoratee.Dispose();
 41026        _readCts.Dispose();
 27
 28        // Using Dispose is fine, there's no need to wait for the sendHeartbeat to complete if it's running.
 41029        _writeTimer.Dispose();
 41030    }
 31
 32    public ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 3889733    {
 3889734        return _readIdleTimeout == Timeout.InfiniteTimeSpan ?
 3889735            _decoratee.ReadAsync(buffer, cancellationToken) :
 3889736            PerformReadAsync();
 37
 38        async ValueTask<int> PerformReadAsync()
 3889739        {
 40            try
 3889741            {
 3889742                using CancellationTokenRegistration _ = cancellationToken.UnsafeRegister(
 16143                    cts => ((CancellationTokenSource)cts!).Cancel(),
 3889744                    _readCts);
 3889745                _readCts.CancelAfter(_readIdleTimeout); // enable idle timeout before reading
 3889746                return await _decoratee.ReadAsync(buffer, _readCts.Token).ConfigureAwait(false);
 47            }
 13148            catch (OperationCanceledException)
 13149            {
 13150                cancellationToken.ThrowIfCancellationRequested();
 51
 452                throw new IceRpcException(
 453                    IceRpcError.ConnectionIdle,
 454                    $"The connection did not receive any bytes for over {_readIdleTimeout.TotalSeconds} s.");
 55            }
 56            finally
 3889757            {
 3889758                _readCts.CancelAfter(Timeout.InfiniteTimeSpan); // disable idle timeout if not canceled
 3889759            }
 3859760        }
 3889761    }
 62
 63    public Task ShutdownWriteAsync(CancellationToken cancellationToken) =>
 064        _decoratee.ShutdownWriteAsync(cancellationToken);
 65
 66    public ValueTask WriteAsync(ReadOnlySequence<byte> buffer, CancellationToken cancellationToken)
 563867    {
 563868        return _writeIdleTimeout == Timeout.InfiniteTimeSpan ?
 563869            _decoratee.WriteAsync(buffer, cancellationToken) :
 563870            PerformWriteAsync();
 71
 72        async ValueTask PerformWriteAsync()
 563873        {
 74            // No need to send a heartbeat now since we're about to write.
 563875            CancelWriteTimer();
 76
 563877            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.
 562882            RescheduleWriteTimer();
 562883        }
 563884    }
 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>
 37688    internal IceDuplexConnectionDecorator(
 37689        IDuplexConnection decoratee,
 37690        TimeSpan readIdleTimeout,
 37691        TimeSpan writeIdleTimeout,
 37692        Action sendHeartbeat)
 37693    {
 37694        Debug.Assert(writeIdleTimeout != Timeout.InfiniteTimeSpan);
 37695        _decoratee = decoratee;
 37696        _readIdleTimeout = readIdleTimeout; // can be infinite i.e. disabled
 37697        _writeIdleTimeout = writeIdleTimeout;
 40198        _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.
 376101    }
 102
 103    /// <summary>Schedules the initial heartbeat. Called by a client IceProtocolConnection after it receives the
 104    /// initial ValidateConnection frame from the server.</summary>
 159105    internal void ScheduleHeartbeat() => RescheduleWriteTimer();
 106
 107    /// <summary>Cancels the write timer.</summary>
 5638108    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>
 5787111    private void RescheduleWriteTimer() => _writeTimer.Change(_writeIdleTimeout / 2, Timeout.InfiniteTimeSpan);
 112}