< Summary

Information
Class: IceRpc.Internal.IceDuplexConnectionDecorator
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Internal/IceDuplexConnectionDecorator.cs
Tag: 1986_28452893481
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;
 22915    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) =>
 11921        _decoratee.ConnectAsync(cancellationToken);
 22
 23    public void Dispose()
 25224    {
 25225        _decoratee.Dispose();
 25226        _readCts.Dispose();
 27
 28        // Using Dispose is fine, there's no need to wait for the sendHeartbeat to complete if it's running.
 25229        _writeTimer.Dispose();
 25230    }
 31
 32    public ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
 1975333    {
 1975334        return _readIdleTimeout == Timeout.InfiniteTimeSpan ?
 1975335            _decoratee.ReadAsync(buffer, cancellationToken) :
 1975336            PerformReadAsync();
 37
 38        async ValueTask<int> PerformReadAsync()
 1975339        {
 40            try
 1975341            {
 1975342                using CancellationTokenRegistration _ = cancellationToken.UnsafeRegister(
 11043                    cts => ((CancellationTokenSource)cts!).Cancel(),
 1975344                    _readCts);
 1975345                _readCts.CancelAfter(_readIdleTimeout); // enable idle timeout before reading
 1975346                return await _decoratee.ReadAsync(buffer, _readCts.Token).ConfigureAwait(false);
 47            }
 7848            catch (OperationCanceledException)
 7849            {
 7850                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
 1975357            {
 1975358                _readCts.CancelAfter(Timeout.InfiniteTimeSpan); // disable idle timeout if not canceled
 1975359            }
 1956960        }
 1975361    }
 62
 63    public Task ShutdownWriteAsync(CancellationToken cancellationToken) =>
 064        _decoratee.ShutdownWriteAsync(cancellationToken);
 65
 66    public ValueTask WriteAsync(ReadOnlySequence<byte> buffer, CancellationToken cancellationToken)
 292167    {
 292168        return _writeIdleTimeout == Timeout.InfiniteTimeSpan ?
 292169            _decoratee.WriteAsync(buffer, cancellationToken) :
 292170            PerformWriteAsync();
 71
 72        async ValueTask PerformWriteAsync()
 292173        {
 74            // No need to send a heartbeat now since we're about to write.
 292175            CancelWriteTimer();
 76
 292177            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.
 291682            RescheduleWriteTimer();
 291683        }
 292184    }
 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>
 22988    internal IceDuplexConnectionDecorator(
 22989        IDuplexConnection decoratee,
 22990        TimeSpan readIdleTimeout,
 22991        TimeSpan writeIdleTimeout,
 22992        Action sendHeartbeat)
 22993    {
 22994        Debug.Assert(writeIdleTimeout != Timeout.InfiniteTimeSpan);
 22995        _decoratee = decoratee;
 22996        _readIdleTimeout = readIdleTimeout; // can be infinite i.e. disabled
 22997        _writeIdleTimeout = writeIdleTimeout;
 24398        _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.
 229101    }
 102
 103    /// <summary>Schedules the initial heartbeat. Called by a client IceProtocolConnection after it receives the
 104    /// initial ValidateConnection frame from the server.</summary>
 100105    internal void ScheduleHeartbeat() => RescheduleWriteTimer();
 106
 107    /// <summary>Cancels the write timer.</summary>
 2921108    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>
 3016111    private void RescheduleWriteTimer() => _writeTimer.Change(_writeIdleTimeout / 2, Timeout.InfiniteTimeSpan);
 112}