< Summary

Information
Class: IceRpc.Transports.Slic.Internal.SlicPipeReader
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Transports/Slic/Internal/SlicPipeReader.cs
Tag: 1986_28452893481
Line coverage
79%
Covered lines: 106
Uncovered lines: 28
Coverable lines: 134
Total lines: 263
Line coverage: 79.1%
Branch coverage
73%
Covered branches: 28
Total branches: 38
Branch coverage: 73.6%
Method coverage
100%
Covered methods: 11
Fully covered methods: 7
Total methods: 11
Method coverage: 100%
Full method coverage: 63.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AdvanceTo(...)100%11100%
AdvanceTo(...)100%22100%
CancelPendingRead()100%11100%
Complete(...)100%22100%
ReadAsync()100%22100%
TryRead(...)50%5461.53%
.ctor(...)100%11100%
CompleteReads(...)100%44100%
ReceivedDataFrameAsync()71.42%211467.5%
ProcessReadResult(...)62.5%12860%
ThrowIfCompleted()50%2260%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Transports/Slic/Internal/SlicPipeReader.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Internal;
 4using IceRpc.Transports.Internal;
 5using System.IO.Pipelines;
 6
 7namespace IceRpc.Transports.Slic.Internal;
 8
 9// The SlicPipeReader doesn't override ReadAtLeastAsyncCore or CopyToAsync methods because:
 10// - we can't forward the calls to the internal pipe reader since reading relies on the AdvanceTo implementation to send
 11//   the StreamWindowUpdate frame once the data is examined,
 12// - the default implementation can't be much optimized.
 13internal class SlicPipeReader : PipeReader
 14{
 15    private int _examined;
 16    private volatile Exception? _exception;
 17    private long _lastExaminedOffset;
 18    private readonly Pipe _pipe;
 19    private ReadResult _readResult;
 20    // FlagEnumExtensions operations are used to update the state. These operations are atomic and don't require mutex
 21    // locking.
 22    private int _state;
 23    private readonly SlicStream _stream;
 24    private int _windowSize;
 25
 353526    public override void AdvanceTo(SequencePosition consumed) => AdvanceTo(consumed, consumed);
 27
 28    public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
 744729    {
 744730        ThrowIfCompleted();
 31
 744732        long startOffset = _readResult.Buffer.GetOffset(_readResult.Buffer.Start);
 744733        long consumedOffset = _readResult.Buffer.GetOffset(consumed) - startOffset;
 744734        long examinedOffset = _readResult.Buffer.GetOffset(examined) - startOffset;
 35
 36        // Add the additional examined bytes to the examined bytes total.
 744737        _examined += (int)(examinedOffset - _lastExaminedOffset);
 744738        _lastExaminedOffset = examinedOffset - consumedOffset;
 39
 40        // If the number of examined bytes is superior to the window update threshold, notifies the stream of the window
 41        // update. This will trigger the sending of a window update frame and allow the sender to send additional data.
 744742        if (_examined >= _stream.WindowUpdateThreshold)
 125243        {
 125244            Interlocked.Add(ref _windowSize, _examined);
 125245            _stream.WindowUpdate(_examined);
 125246            _examined = 0;
 125247        }
 48
 744749        _pipe.Reader.AdvanceTo(consumed, examined);
 744750    }
 51
 552    public override void CancelPendingRead() => _pipe.Reader.CancelPendingRead();
 53
 54    public override void Complete(Exception? exception = null)
 278555    {
 278556        if (_state.TrySetFlag(State.Completed))
 276257        {
 58            // Forcefully close the stream reads if reads were not already gracefully closed by ReadAsync or TryRead.
 276259            _stream.CloseReads(graceful: false);
 60
 276261            CompleteReads(exception: null);
 62
 276263            _pipe.Reader.Complete();
 276264        }
 278565    }
 66
 67    public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancellationToken = default)
 775168    {
 775169        ThrowIfCompleted();
 70
 775171        if (_exception is not null)
 1672        {
 1673            _stream.ThrowIfConnectionClosed();
 1374        }
 75
 774876        return ProcessReadResult(await _pipe.Reader.ReadAsync(cancellationToken).ConfigureAwait(false));
 746477    }
 78
 79    public override bool TryRead(out ReadResult result)
 7780    {
 7781        ThrowIfCompleted();
 82
 7783        if (_exception is not null)
 084        {
 085            _stream.ThrowIfConnectionClosed();
 086        }
 87
 7788        if (_pipe.Reader.TryRead(out result))
 7789        {
 7790            result = ProcessReadResult(result);
 7791            return true;
 92        }
 93        else
 094        {
 095            return false;
 96        }
 7797    }
 98
 280099    internal SlicPipeReader(SlicStream stream, SlicConnection connection)
 2800100    {
 2800101        _stream = stream;
 2800102        _windowSize = connection.InitialStreamWindowSize;
 103
 104        // We keep the default readerScheduler (ThreadPool) because the _pipe.Writer.FlushAsync executes in the
 105        // "read loop task" and we don't want this task to continue into application code. The writerScheduler
 106        // doesn't matter since _pipe.Writer.FlushAsync never blocks.
 2800107        _pipe = new(new PipeOptions(
 2800108            pool: connection.Pool,
 2800109            pauseWriterThreshold: 0,
 2800110            minimumSegmentSize: connection.MinSegmentSize,
 2800111            useSynchronizationContext: false));
 2800112    }
 113
 114    /// <summary>Completes reads.</summary>
 115    /// <param name="exception">The exception that will be raised by <see cref="ReadAsync" /> or <see cref="TryRead" />
 116    /// operation.</param>
 117    internal void CompleteReads(Exception? exception)
 3136118    {
 3136119        Interlocked.CompareExchange(ref _exception, exception, null);
 120
 3136121        if (_state.TrySetFlag(State.PipeWriterCompleted))
 2797122        {
 2797123            if (_state.HasFlag(State.PipeWriterInUse))
 3124            {
 3125                _pipe.Reader.CancelPendingRead();
 3126            }
 127            else
 2794128            {
 2794129                _pipe.Writer.Complete(exception);
 2794130            }
 2797131        }
 3136132    }
 133
 134    /// <summary>Notifies the reader of the reception of a <see cref="FrameType.Stream" /> or <see
 135    /// cref="FrameType.StreamLast" /> frame. The stream data is consumed from the connection and buffered by this
 136    /// reader on its internal pipe.</summary>
 137    /// <returns><see langword="true" /> if the data was consumed; otherwise, <see langword="false"/> if the reader was
 138    /// completed by the application.</returns>
 139    internal async ValueTask<bool> ReceivedDataFrameAsync(
 140        int dataSize,
 141        bool endStream,
 142        CancellationToken cancellationToken)
 8703143    {
 8703144        if (dataSize == 0 && !endStream)
 0145        {
 0146            throw new IceRpcException(
 0147                IceRpcError.IceRpcError,
 0148                "An empty Slic stream frame is not allowed unless endStream is true.");
 149        }
 150
 8703151        if (!_state.TrySetFlag(State.PipeWriterInUse))
 0152        {
 0153            throw new InvalidOperationException(
 0154                $"The {nameof(ReceivedDataFrameAsync)} operation is not thread safe.");
 155        }
 156
 157        try
 8703158        {
 8703159            if (_state.HasFlag(State.PipeWriterCompleted))
 0160            {
 0161                return false; // No bytes consumed because the application completed the stream input.
 162            }
 163
 8703164            int newWindowSize = Interlocked.Add(ref _windowSize, -dataSize);
 8703165            if (newWindowSize < 0)
 0166            {
 0167                throw new IceRpcException(
 0168                    IceRpcError.IceRpcError,
 0169                    "Received more data than flow control permits.");
 170            }
 171
 172            // Fill the pipe writer with dataSize bytes.
 8703173            await _stream.FillBufferWriterAsync(
 8703174                _pipe.Writer,
 8703175                dataSize,
 8703176                cancellationToken).ConfigureAwait(false);
 177
 8703178            if (endStream)
 1287179            {
 1287180                _pipe.Writer.Complete();
 1287181            }
 182            else
 7416183            {
 7416184                _ = await _pipe.Writer.FlushAsync(CancellationToken.None).ConfigureAwait(false);
 7416185            }
 186
 8703187            return true;
 188        }
 189        finally
 8703190        {
 8703191            if (_state.HasFlag(State.PipeWriterCompleted))
 3192            {
 193                // If the pipe writer has been completed while we were reading the data from the stream, we make sure to
 194                // complete the writer now since Complete or CompleteWriter didn't do it.
 3195                _pipe.Writer.Complete(_exception);
 3196            }
 8703197            _state.ClearFlag(State.PipeWriterInUse);
 8703198        }
 8703199    }
 200
 201    private ReadResult ProcessReadResult(ReadResult result)
 7541202    {
 203        // This method is called by ReadAsync or TryRead with the read result returned by the _pipe.Reader read
 204        // operation.
 7541205        if (result.IsCanceled)
 5206        {
 207            // The _pipe.Reader ReadAsync/TryRead operations can return a canceled read result for two different
 208            // reasons:
 209            // - the application called CancelPendingRead
 210            // - the connection is closed while data is written on _pipe.Writer
 5211            if (_state.HasFlag(State.PipeWriterCompleted))
 0212            {
 213                // The connection was closed while the pipe writer was in use. Either throw or return a non-canceled
 214                // result depending on the completion exception.
 0215                if (_exception is null)
 0216                {
 0217                    result = new ReadResult(result.Buffer, isCanceled: false, isCompleted: true);
 0218                }
 219                else
 0220                {
 0221                    throw ExceptionUtil.Throw(_exception);
 222                }
 0223            }
 224            // else: the application called CancelPendingRead, return the canceled read result as-is.
 5225        }
 7536226        else if (result.IsCompleted)
 1377227        {
 228            // All the data from the peer is considered read at this point. It's time to close reads on the stream. This
 229            // will write the StreamReadsClosed frame to the peer and allow it to release the stream semaphore.
 1377230            _stream.CloseReads(graceful: true);
 1377231        }
 232
 233        // Cache the read result returned to the application: AdvanceTo computes the consumed and examined offsets
 234        // against the cached buffer.
 7541235        _readResult = result;
 7541236        return result;
 7541237    }
 238
 239    private void ThrowIfCompleted()
 15275240    {
 15275241        if (_state.HasFlag(State.Completed))
 0242        {
 243            // If the reader is completed, the caller is bogus, it shouldn't call read operations after completing the
 244            // pipe reader.
 0245            throw new InvalidOperationException("Reading is not allowed once the reader is completed.");
 246        }
 15275247    }
 248
 249    /// <summary>The state enumeration is used to ensure the reader is not used after it's completed and to ensure that
 250    /// the internal pipe writer isn't completed concurrently when it's being used by <see
 251    /// cref="ReceivedDataFrameAsync" />.</summary>
 252    private enum State : int
 253    {
 254        /// <summary><see cref="Complete" /> was called on this Slic pipe reader.</summary>
 255        Completed = 1,
 256
 257        /// <summary>Data is being written to the internal pipe writer.</summary>
 258        PipeWriterInUse = 2,
 259
 260        /// <summary>The internal pipe writer was completed by <see cref="CompleteReads" />.</summary>
 261        PipeWriterCompleted = 4,
 262    }
 263}