Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Managed SNI for Windows #16589

Merged
merged 6 commits into from
Mar 8, 2017
Merged

Conversation

saurabh500
Copy link
Contributor

@saurabh500 saurabh500 commented Mar 2, 2017

The changes include refactoring in SqlClient to build Managed SNI on Windows along with Native SNI.
This is the first step towards addressing #16129
The changes involve refactoring TdsParserStateObject into sub classes which contain the SNI specific logic and encapsulate the usage of SNIPacket and SNIHandle

There were changes done to extract parts of TdsParser into platform specific files so that the right behavior can be invoked.

The current behavior on Windows is under the flag TdsParserNativeObjectFactory.useManagedSni.
I will be adding changes to incorporate an AppContext flag to control the SNI behavior.

I ran the Functional and Manual tests to validate these changes.

cc @corivera @YoungGah @geleems

@saurabh500
Copy link
Contributor Author

Test Innerloop Windows_NT Debug Build and Test

bool lockTaken = false;
try
{
Monitor.Enter(s_dllLock, ref lockTaken);
Copy link
Member

Choose a reason for hiding this comment

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

The code is using:

bool lockTaken = false;
try
{
    Monitor.Enter(s_dllLock, ref lockTaken);
    ... // protected code
}
finally
{
    if (lockTaken) Monitor.Exit(s_dllLock);
}

That's exactly what lock does, so it could just be:

lock (s_dllLock)
{
    ... // protected code
}

if (s_userInstanceDLLHandle != IntPtr.Zero)
{
}
else
Copy link
Member

Choose a reason for hiding this comment

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

Why not if (s_userInstanceDLLHandle == IntPtr.Zero) rather than having an empty if block?

{
SNINativeMethodWrapper.SNI_Error sniError;
SNINativeMethodWrapper.SNIGetLastError(out sniError);
throw CreateLocalDBException(errorMessage: SR.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError);
Copy link
Member

Choose a reason for hiding this comment

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

It'd be great to move to an SR approach that the rest of the repo uses. Is there a plan to do so?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no such formal plan to do so. I will move to SR in the LocalDB files

@@ -48,45 +48,7 @@ internal static void ReleaseDLLHandles()
private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero;

private static object s_dllLock = new object();
Copy link
Member

Choose a reason for hiding this comment

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

Nit: readonly... I personally like to see readonly whenever possible, but in particular on lock objects, as if they're accidentally changed the results can be quite bad and hard to track down.

/// <param name="packet">SNI packet</param>
public void PacketReset(SNIHandle handle, bool write, SNIPacket packet)
public void PacketReset(SNIPacket packet)
{
packet.Reset();
}
Copy link
Member

Choose a reason for hiding this comment

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

Is this method still needed, or could the callers just do packet.Reset() instead of proxy.PacketReset(packet)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking at the calls to SNIPacket, I think the original idea was to have all operations to SNI internals like SNIPacket and SNIHandle to go through the SNIProxy. I wanted to leave it that way.

@corivera do you have any preferences here?

Copy link
Member

Choose a reason for hiding this comment

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

Just calling packet.Reset() in place of this method call seems fine. There's no complex logic in this method that merits keeping it.

uint error = TdsEnums.SNI_SUCCESS;

try { }
finally
Copy link
Member

Choose a reason for hiding this comment

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

Nit: there aren't thread aborts in coreclr. You could get rid of the try { } finally.

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

there aren't thread aborts in coreclr.

Is this one of the things which were removed and can be added in future or Thread Aborts have been replaced with something else which removes the need for empty try{} finally{ //Dosomething} pattern?

Copy link
Member

Choose a reason for hiding this comment

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

There's no plan for thread aborts to come back.
cc: @jkotas

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 want to leave these try {} finally{..} blocks as is.
I understand that the reason for which this coding pattern was followed didn't exist, but the pattern means that the code in finally must execute.

I don't see any significant advantage of removing this pattern.

Copy link
Member

Choose a reason for hiding this comment

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

we have a tracking issue to remove all try{} code. so it would be better to fix this from now.

Copy link
Contributor Author

@saurabh500 saurabh500 Mar 2, 2017

Choose a reason for hiding this comment

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

@tarekgh Can you provide the issue number?

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you. Updated the PR

_pMarsPhysicalConObj.ReleasePacket(temp);
}
}
Debug.Assert(IntPtr.Zero == temp, "unexpected syncReadPacket without corresponding SNIPacketRelease");
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused by this assert. The code just above it has code that checks whether temp is not zero, so why do we expect it to be zero here?

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 moved this code from TdsParser.cs
I don't know exactly what the intention of the original author was, but it seems like the Assert was in place to hint that temp should not be empty after the finally block has executed. If it was non-empty, then a packet release would have happened and everything would be alright. If it was not empty then a Debug Assert would catch the problem.

{
sealed internal partial class TdsParser
{
private static bool s_fSSPILoaded = false; // bool to indicate whether library has been loaded
Copy link
Member

Choose a reason for hiding this comment

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

Technically this should be volatile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point.


using MSS = Microsoft.SqlServer.Server;

namespace System.Data.SqlClient
{

struct SNIErrorDetails
Copy link
Member

@stephentoub stephentoub Mar 2, 2017

Choose a reason for hiding this comment

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

Nit: internal

// The TdsParser Object controls reading/writing to the netlib, parsing the tds,
// and surfacing objects to the user.
sealed internal class TdsParser
sealed internal partial class TdsParser
Copy link
Member

Choose a reason for hiding this comment

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

Nit: internal sealed rather than sealed internal

// The TdsParser Object controls reading/writing to the netlib, parsing the tds,
// and surfacing objects to the user.
sealed internal class TdsParser
sealed internal partial class TdsParser
{
static TdsParser()
{
// For CoreCLR, we need to register the ANSI Code Page encoding provider before attempting to get an Encoding from a CodePage
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Copy link
Member

Choose a reason for hiding this comment

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

@tarekgh, is this still necessary in .NET Core 2.0?

Copy link
Member

Choose a reason for hiding this comment

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

I think registering the encoding provider should be the app decision and not the library decision. having the library registering the provider means any app using the library will pay the cost of the encoding even if the app not using the non-Unicode encodings.

@stephentoub encoding handling didn't change in Net core 2.0. the only change we did is we have made the console support non-Unicode encodings but this is limited to the console only.


In reply to: 103917083 [](ancestors = 103917083)

Copy link
Member

Choose a reason for hiding this comment

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

cc @weshaggard


In reply to: 103972132 [](ancestors = 103972132,103917083)

Copy link
Member

Choose a reason for hiding this comment

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

@saurabh500 just want to make sure you have seen the comments here regarding using the encoding provider.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tarekgh SqlClient needs to provide the encoding related abstractions to the consumers of Sql client based on the exchange with SqlServer.
I don't think it makes sense for SqlClient to expect consumers to set the encoding provider.
Am I missing something here? Regardless if this needs to be addresses I will open another issue for this item.
Let me know if my understanding is incorrect.

Copy link
Member

Choose a reason for hiding this comment

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

I am fine to track this as separate issue but I am still seeing the clinet consuming the library can decide about the encoding. if you have a client using Unicode only DBs why you want them pay for the encoding cost? this is similar to the networking libraries which is up to the consumer of the library to decide about the encoding but not forcing every app consume the library to pay for the encoding even if they are not using it or need it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened an issue #16842

@@ -80,7 +85,7 @@ private static Task CompletedTask

internal Encoding _defaultEncoding = null; // for sql character data

private static EncryptionOptions s_sniSupportedEncryptionOption = SNILoadHandle.SingletonInstance.Options;
private static EncryptionOptions s_sniSupportedEncryptionOption = TdsParserStateObjectFactory.Singleton.EncryptionOptions;// SNILoadHandle.SingletonInstance.Options;
Copy link
Member

Choose a reason for hiding this comment

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

What does the comment mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will Remove

{
sessionHandle.Dispose();
}
session.DisposeHandle();
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Could just be:

_cache[i]?.DisposeHandle();

@@ -89,11 +85,7 @@ internal void BestEffortCleanup()
TdsParserStateObject session = _cache[i];
Copy link
Member

Choose a reason for hiding this comment

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

GitHub won't let me comment on it, but the comment above says "This is called from a ThreadAbort - ensure that it can be run from a CER catch"... thread aborts don't exist in coreclr.

@@ -64,9 +52,12 @@ sealed internal class TdsParserStateObject
internal int _outBytesUsed = TdsEnums.HEADER_LEN; // number of bytes used in internal write buffer -
// - initialize past header
// In buffer variables
private byte[] _inBuff; // internal read buffer - initialize on login
protected byte[] _inBuff; // internal read buffer - initialize on login
Copy link
Member

Choose a reason for hiding this comment

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

Nit: comment indentation looks off

#endif // MANAGED_SNI
private object _writePacketLockObject = new object(); // Used to synchronize access to _writePacketCache and _pendingWritePackets

protected object _writePacketLockObject = new object(); // Used to synchronize access to _writePacketCache and _pendingWritePackets
Copy link
Member

Choose a reason for hiding this comment

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

readonly?

@@ -195,7 +175,7 @@ sealed internal class TdsParserStateObject
internal SqlErrorCollection _preAttentionWarnings;

volatile private TaskCompletionSource<object> _writeCompletionSource = null;
volatile private int _asyncWriteCount = 0;
volatile protected int _asyncWriteCount = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: style-wise, the visibility should come first, e.g. protected volatile instead of volatile protected


public static readonly TdsParserStateObjectFactory Singleton = new TdsParserStateObjectFactory();

public EncryptionOptions EncryptionOptions {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: brace style

private SNIHandle _sessionHandle = null; // the SNI handle we're to work on
private SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS
internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn
private Dictionary<SNIPacket, SNIPacket> _pendingWritePackets = new Dictionary<SNIPacket, SNIPacket>(); // Stores write packets that have been sent to SNI, but have not yet finished writing (i.e. we are waiting for SNI's callback)
Copy link
Member

Choose a reason for hiding this comment

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

readonly?

internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn
private Dictionary<SNIPacket, SNIPacket> _pendingWritePackets = new Dictionary<SNIPacket, SNIPacket>(); // Stores write packets that have been sent to SNI, but have not yet finished writing (i.e. we are waiting for SNI's callback)

protected WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used
Copy link
Member

Choose a reason for hiding this comment

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

readonly?

if (asyncAttnPacket != null)
{
asyncAttnPacket.Dispose();
}
Copy link
Member

Choose a reason for hiding this comment

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

Nit:

packetHandle?.Dispose();
asyncAttnPacket?.Dispose();

if (null != sessionHandle || null != packetHandle)
{
try { }
finally
Copy link
Member

Choose a reason for hiding this comment

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

Nit: same try/finally thread abort comment as earlier


internal override void DisposePacketCache()
{
if (_writePacketCache != null)
Copy link
Member

Choose a reason for hiding this comment

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

In what situation will this be null?

internal override object ReadAsync(out uint error, ref object handle)
{
SNIPacket packet = null;
error = SNIProxy.Singleton.ReadAsync((SNIHandle)handle, ref packet);
Copy link
Member

Choose a reason for hiding this comment

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

Should ReadAsync take the packet via out rather than ref?

if (asyncAttnPacket != null)
{
asyncAttnPacket.Dispose();
}
Copy link
Member

Choose a reason for hiding this comment

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

Same nit as earlier


internal override void DisposePacketCache()
{
if (_writePacketCache != null)
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 as earlier

if (sessionHandle != null)
{
sessionHandle.Dispose();
}
Copy link
Member

Choose a reason for hiding this comment

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

Nit:

Handle?.Dispose();

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 think I can get rid of DisposeHandle() This is called by BestEffortCleanUp from the TdsParserSessionPool.cs which is not called by any caller.
The BestEffortCleanUp existed in NetFx for Thread Aborts.

try
{
Monitor.Enter(s_dllLock, ref lockTaken);
lock (s_dllLock) {
Copy link
Member

@corivera corivera Mar 3, 2017

Choose a reason for hiding this comment

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

You should do an auto-format pass on the changed files (for spacing, tabs, etc; this curly brace).


namespace System.Data.SqlClient
{
internal sealed class TdsParserStateObjectFactory
Copy link
Member

Choose a reason for hiding this comment

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

Should the filename for this class be TdsParserStateObjectFactory.Windows.cs ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.

{
get
{
return useManagedSni ? SNILoadHandle.SingletonInstance.Status : SNI.SNILoadHandle.SingletonInstance.Status;
Copy link
Member

Choose a reason for hiding this comment

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

Should the SNI.SNILoadHandle come first here?

{
private SNIHandle _sessionHandle = null; // the SNI handle we're to work on

private SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS
Copy link
Member

Choose a reason for hiding this comment

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

Do we still need this comment at the end of this line? Not sure what it's referring to.

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 comment with this field declaration. Looks like a sort of TODO for MARS.
This has propagated since the time the SqlClient code was ported to CoreFx and has been in the framework code as well.

I don't think this is going to be changed or revamped.

if (s_userInstanceDLLHandle == IntPtr.Zero)
{

lock (s_dllLock) {
Copy link
Member

Choose a reason for hiding this comment

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

Curly brace on new line


namespace System.Data.SqlClient
{
sealed internal partial class TdsParser
Copy link
Member

Choose a reason for hiding this comment

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

sealed comes after internal.

{
lock (_writePacketLockObject)
{
try { }
Copy link
Member

Choose a reason for hiding this comment

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

Do we still need this empty try-finally?


if (null != sessionHandle || null != packetHandle)
{
try { }
Copy link
Member

Choose a reason for hiding this comment

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

Empty try-finally

{
lock (_writePacketLockObject)
{
try { }
Copy link
Member

Choose a reason for hiding this comment

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

Empty try-finally


WaitForSSLHandShakeToComplete(ref error);

// create a new packet encryption changes the internal packet size
try { } // EmptyTry/Finally to avoid FXCop violation
Copy link
Member

Choose a reason for hiding this comment

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

Empty try-finally

@@ -2443,23 +2300,19 @@ internal void ReadSni(TaskCompletionSource<object> completion)
ChangeNetworkPacketTimeout(msecsRemaining, Timeout.Infinite);
}

SNIHandle handle = null;
object handle = null;

try { }
Copy link
Member

Choose a reason for hiding this comment

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

empty try-finally

IntPtr temp = IntPtr.Zero;
uint error = TdsEnums.SNI_SUCCESS;


Copy link
Member

@corivera corivera Mar 8, 2017

Choose a reason for hiding this comment

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

Extra line here

Copy link
Member

@corivera corivera left a comment

Choose a reason for hiding this comment

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

Approved pending my other comments

Refactor code for Read async

Cleaning up
Formatting changes

Changes for Code Review comments
Additional CR comments
@saurabh500 saurabh500 force-pushed the buildmanagedsniOnWindows branch from ab3b131 to 8a9c7ac Compare March 8, 2017 05:29
@saurabh500 saurabh500 merged commit bb05af4 into dotnet:master Mar 8, 2017
@saurabh500 saurabh500 deleted the buildmanagedsniOnWindows branch March 8, 2017 07:19
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Make Native and Managed SNI coexist on Windows and make Native SNI the default implementation of the networking interface.

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

Successfully merging this pull request may close these issues.

7 participants