Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Add async overloads to SslOverTdsStream #27743

Merged
merged 8 commits into from
Mar 6, 2018
Merged

Conversation

saurabh500
Copy link
Contributor

Added ReadAsync and WriteAsync overloads to SslOverTdsStream in SqlClient so that the async apis over encrypted connection are full async instead of calling the Read and Write methods of SslOverTdsStream.

I also modified the SNITcpPacket to call into the Stream's WriteAsync APIs.

This impacts SSL connections to Sql server.
Tests done:

  1. Tested the SqlClient Manual Tests.
  2. Tested the EF tests.

@@ -48,6 +50,51 @@ public void FinishHandshake()
/// <param name="count">Byte count</param>
/// <returns>Bytes read</returns>
public override int Read(byte[] buffer, int offset, int count)
{
return ReadInternal(buffer, offset, count, CancellationToken.None, false).GetAwaiter().GetResult();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub I have tried to reuse code and use the same method for Read and ReadAsync. Looking forward to any feedback here.
From what I understand there should be no allocations due to this approach, but looking forward to your review.

uint status = TdsEnums.SNI_SUCCESS;
try
{
await stream.WriteAsync(_data, 0, _length, CancellationToken.None);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: please add .ConfigureAwait(false) to the end of this

}
});

packet.WriteToStreamAsync(_stream, cb);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could end up invoking callback while holding the lock. Is that ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am looking into the need for the locks in Async Scenarios and running some tests to validate the removal of these locks only in case of async reads and writes.
SqlClient on Windows uses locks for some sync cases, however no locks are used for async scenarios and that parity can be applied to the Managed Networking stack of SqlClient as well.
If my validations don't show any regressions, then I will update the code with locks removed from async reads and writes.

SNIPacket newPacket = packet;

_writeTaskFactory.StartNew(() =>
SNIAsyncCallback cb = callback ?? _sendCallback;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these changes also need to be ported to NpHandle? If so, we might as well make a base class for both handles, since they only differ in the underlying streams they open/use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change NpHandle as well.

@@ -249,7 +249,7 @@ public void ReadFromStreamAsync(Stream stream, SNIAsyncCallback callback, bool i
options |= TaskContinuationOptions.LongRunning;
}

stream.ReadAsync(_data, 0, _capacity).ContinueWith(t =>
stream.ReadAsync(_data, 0, _capacity, CancellationToken.None).ContinueWith(t =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use await?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@corivera I will send out another PR changing the readAsync piece and making it use async await.

currentOffset += currentCount;
}
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra space

@@ -47,7 +49,50 @@ public void FinishHandshake()
/// <param name="offset">Offset</param>
/// <param name="count">Byte count</param>
/// <returns>Bytes read</returns>
public override int Read(byte[] buffer, int offset, int count)
public override int Read(byte[] buffer, int offset, int count) =>
ReadInternal(buffer, offset, count, CancellationToken.None, false).GetAwaiter().GetResult();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like previously this would have called the synchronous Read methods on the underlying stream, but now it's invoking the asynchronous ones and then blocking on the result. Why? That's generally not a good direction to go.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nevermind, I see you're switching in the implementation on using either the sync or async methods. Ok.

Could you name the argument to ReadInternal so that it's clear what the bool means, e.g. async: false instead of just false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

@@ -195,6 +280,7 @@ public override long Seek(long offset, SeekOrigin origin)
throw new NotSupportedException();
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra space

else
{
readBytes += _stream.Read(packetData, readBytes, TdsEnums.HEADER_LEN - readBytes);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you could simplify this slightly, e.g.

readBytes += async ?
    await _stream.ReadAsync(packetData, readBytes, TdsEnums.HEADER_LEN - readBytes, token).ConfigureAwait(false) :
    _stream.Read(packetData, readBytes, TdsEnums.HEADER_LEN - readBytes);

readBytes += _stream.Read(packetData, readBytes, TdsEnums.HEADER_LEN - readBytes);
if (async)
{
readBytes += await _stream.ReadAsync(packetData, readBytes, TdsEnums.HEADER_LEN - readBytes, token).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This library targets netstandard2.0 rather than netcoreapp2.1, right? If it targeted the latter, it'd be nice to use the new Memory-based overloads, but I don't think it does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, SqlClient targets netstandard2.0. I would like to change it to target netcoreapp
I will open an issue to track the work. Then SqlClient can benefit more from the Memory Based overloads.

/// <param name="count"></param>
/// <param name="token"></param>
/// <param name="async"></param>
/// <returns></returns>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you can delete all of these empty comments

@saurabh500 saurabh500 self-assigned this Mar 5, 2018
@saurabh500
Copy link
Contributor Author

In the commit that I have pushed , I have added an additional change which removes locks in SNITcpHandle.cs during async reads and writes.
I tested the changes with our server based tests and Entity Framework's tests.
The removal of locks confirms with our implementation on Windows using Win32 APIs where no locks are acquired during async reads and writes.

@saurabh500
Copy link
Contributor Author

@stephentoub is this good with you?

SNILoadHandle.SingletonInstance.LastError = new SNIError(provider, SNICommon.InternalExceptionError, e);
status = TdsEnums.SNI_ERROR;
}
callback(this, status);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's expected to happen if the callback throws an exception? Is that possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callback is not expected to throw any exceptions and handles all the exception using a
try { } catch (Exception e) {} block and handling all the exception itself.

});

packet.WriteToStreamAsync(_stream, cb, SNIProviders.TCP_PROV);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question I had before... this is now potentially invoking cb while holding the lock... is that ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could remove the locks from async reads and writes. As a result none of the operations are now holding a lock. I had another change for removal of locks during async reads and writes being tested. I clubbed the change with this PR. Please see comment at #27743 (comment)

}
catch (IOException ioe)
{
return ReportErrorAndReleasePacket(packet, ioe);
Copy link
Member

@stephentoub stephentoub Mar 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these catch blocks could be consolidated, e.g.

catch (Exception e) when (e is ObjectDisposedException || e is SocketException || e is IOException)
{
    return ReportErrorAndReleasePacket(packet, e);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

@saurabh500
Copy link
Contributor Author

@dotnet-bot Test NETFX x86 Release Build please
Test Windows x64 Debug Build please

@saurabh500
Copy link
Contributor Author

NETFX x86 Release Build has been timing out

13:11:14   call xunit.console.exe System.Web.HttpUtility.Tests.dll  -noshadow -xml testResults.xml -notrait Benchmark=true -notrait category=nonnetfxtests -notrait category=nonwindowstests  -notrait category=OuterLoop -notrait category=failing
13:11:14   popd
13:11:14   ===========================================================================================================
13:11:14   xUnit.net Console Runner (64-bit Desktop .NET 4.0.30319.42000)
13:11:15     Discovering: System.Web.HttpUtility.Tests
13:11:15     Discovered:  System.Web.HttpUtility.Tests
13:11:15     Starting:    System.Web.HttpUtility.Tests
13:11:18     Finished:    System.Web.HttpUtility.Tests
13:11:18   === TEST EXECUTION SUMMARY ===
13:11:18      System.Web.HttpUtility.Tests  Total: 2363, Errors: 0, Failed: 0, Skipped: 0, Time: 0.686s
13:11:18   ----- end 13:11:18.71 ----- exit code 0 ----------------------------------------------------------
14:46:30 Cancelling nested steps due to timeout
14:46:30 Sending interrupt signal to process
14:46:40 After 10s process did not stop
[Pipeline] }

The changes in this PR only impact Linux and macOS.
I am merging this PR.

@saurabh500 saurabh500 merged commit 68cfdde into dotnet:master Mar 6, 2018
@karelz karelz added this to the 2.1.0 milestone Mar 10, 2018
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
* Add async overloads for SslOverTdsStream for async operations

* Remove Unused Code

* Expression bodied syntax

* Address CR comments and remove locks in async operations

* Remove Read Locks

* Remove async logs from NpHandle

* Remove extra space

* Consolidate the exceptions


Commit migrated from dotnet/corefx@68cfdde
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants