Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read SSL via PipeReader in .NET

Currently I have a working implementation by using an SSL Stream, wrapped in a bufferedstream, and just calling read/write on the stream using byte arrays.

I want to make this faster, and from some reading it looks like System.IO.Pipelines are the way to go for high performance IO.

A lot of articles/demos I've read only demonstrate code using a socket directly - Which doesn't seem to work with me since I'm using SSL.

I've found some extensions to get a pipereader/writer from a stream > Stream.UsePipeReader() or Stream.UsePipeWriter() so I've tried calling SSLStream.UsePipeReader()

However I consistently get the error:

System.NotSupportedException :  The ReadAsync method cannot be called when another read operation is pending.
   at System.Net.Security.SslStreamInternal.ReadAsyncInternal[TReadAdapter](TReadAdapter adapter, Memory`1 buffer)
   at Nerdbank.Streams.PipeExtensions.<>c__DisplayClass5_0.<<UsePipeReader>b__1>d.MoveNext() in D:\a\1\s\src\Nerdbank.Streams\PipeExtensions.cs:line 92
--- End of stack trace from previous location where exception was thrown ---
   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.GetReadAsyncResult()

My code to read from the pipe is:

private async Task<string> ReadAsync(PipeReader reader)
        {

            var stringBuilder = new StringBuilder();

            while (true)
            {
                var result = await reader.ReadAsync();

                var buffer = result.Buffer;
                SequencePosition? position;

                do
                {
                    // Look for a EOL in the buffer
                    position = buffer.PositionOf((byte) '\n');

                    if (position != null)
                    {
                        // Process the line
                        ProcessLine(buffer.Slice(0, position.Value), stringBuilder);
                        buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
                    }
                } while (position != null);

                reader.AdvanceTo(buffer.Start, buffer.End);

                if (result.IsCompleted)
                {
                    reader.Complete();
                    break;
                }
            }

            return stringBuilder.ToString();
        }

It is not being called by any other threads as I've tested it with a lock around it. And I'm only doing a single call at a time for testing purposes.

Can anyone tell me what I'm doing wrong?

Thanks!

like image 214
TomH Avatar asked Jul 13 '19 11:07

TomH


1 Answers

I believe I might know the answer.

Your problem might be in this line:

var result = await reader.ReadAsync();

You see, await block the execution only in the Send method, but meanwhile the code in the caller continues to execute even if the ReadAsync() has not completed yet.

Now if the Send method is called again before the ReadAsync() has completed you will get the exception that you have posted since SslStream does not allow multiple read/write operations and the code that you have posted does not prevent this from happening.

You need to write your own safety fail mechanism for this case, In order to avoid the second call before the completion of the first one.

Since you are using a loop here:

while (true)
{
  //rest of code...
}

I believe this is where you should tend your problem. You are reaching the second iteration while the first one is not completed yet, thus resulting in:

System.NotSupportedException

like image 120
Barr J Avatar answered Nov 09 '22 02:11

Barr J