Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port CoreFx PR 38271: Fix Statement Command Cancellation (Managed SNI) #248

Merged
merged 8 commits into from
Jan 8, 2020
Next Next commit
Port CoreFx PR 38271: Fix Statement Command Cancellation (Managed SNI)
  • Loading branch information
cheenamalhotra committed Oct 8, 2019
commit e161d9d1be11ccaa97deacf3b4d0e609041230c3
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;

namespace Microsoft.Data.SqlClient.SNI
{
Expand All @@ -22,6 +23,7 @@ internal class SNINpHandle : SNIHandle

private readonly string _targetServer;
private readonly object _callbackObject;
private readonly object _sendSync;

private Stream _stream;
private NamedPipeClientStream _pipeStream;
Expand All @@ -37,6 +39,7 @@ internal class SNINpHandle : SNIHandle

public SNINpHandle(string serverName, string pipeName, long timerExpire, object callbackObject)
{
_sendSync = new object();
_targetServer = serverName;
_callbackObject = callbackObject;

Expand Down Expand Up @@ -193,20 +196,48 @@ public override uint ReceiveAsync(ref SNIPacket packet)

public override uint Send(SNIPacket packet)
{
lock (this)
bool releaseLock = false;
try
{
try
// is the packet is marked out out-of-band (attention packets only) it must be
// sent immediately even if a send of recieve operation is already in progress
// because out of band packets are used to cancel ongoing operations
// so try to take the lock if possible but continue even if it can't be taken
if (packet.IsOutOfBand)
{
packet.WriteToStream(_stream);
return TdsEnums.SNI_SUCCESS;
Monitor.TryEnter(this, ref releaseLock);
}
catch (ObjectDisposedException ode)
else
{
return ReportErrorAndReleasePacket(packet, ode);
Monitor.Enter(this);
releaseLock = true;
}
catch (IOException ioe)

// this lock ensures that two packets are not being written to the transport at the same time
// so that sending a standard and an out-of-band packet are both written atomically no data is
// interleaved
lock (_sendSync)
{
return ReportErrorAndReleasePacket(packet, ioe);
try
{
packet.WriteToStream(_stream);
return TdsEnums.SNI_SUCCESS;
}
catch (ObjectDisposedException ode)
{
return ReportErrorAndReleasePacket(packet, ode);
}
catch (IOException ioe)
{
return ReportErrorAndReleasePacket(packet, ioe);
}
}
}
finally
{
if (releaseLock)
{
Monitor.Exit(this);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ internal partial class SNIPacket : IDisposable, IEquatable<SNIPacket>
private int _offset;
private string _description;
private SNIAsyncCallback _completionCallback;

private bool _isBufferFromArrayPool;

public SNIPacket() { }
Expand Down Expand Up @@ -50,6 +49,11 @@ public string Description
/// </summary>
public int DataLeft => (_length - _offset);

/// <summary>
/// Indicates that the packet should be sent out of band bypassing the normal send-recieve lock
/// </summary>
public bool IsOutOfBand { get; set; }

/// <summary>
/// Length of data
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ internal class SNITCPHandle : SNIHandle
{
private readonly string _targetServer;
private readonly object _callbackObject;
private readonly object _sendSync;
private readonly Socket _socket;
private NetworkStream _tcpStream;

Expand Down Expand Up @@ -104,6 +105,7 @@ public SNITCPHandle(string serverName, int port, long timerExpire, object callba
{
_callbackObject = callbackObject;
_targetServer = serverName;
_sendSync = new object();

try
{
Expand Down Expand Up @@ -420,24 +422,52 @@ public override void SetBufferSize(int bufferSize)
/// <returns>SNI error code</returns>
public override uint Send(SNIPacket packet)
{
lock (this)
bool releaseLock = false;
try
{
try
// is the packet is marked out out-of-band (attention packets only) it must be
// sent immediately even if a send of recieve operation is already in progress
// because out of band packets are used to cancel ongoing operations
// so try to take the lock if possible but continue even if it can't be taken
if (packet.IsOutOfBand)
{
packet.WriteToStream(_stream);
return TdsEnums.SNI_SUCCESS;
Monitor.TryEnter(this, ref releaseLock);
}
catch (ObjectDisposedException ode)
else
{
return ReportTcpSNIError(ode);
Monitor.Enter(this);
releaseLock = true;
}
catch (SocketException se)

// this lock ensures that two packets are not being written to the transport at the same time
// so that sending a standard and an out-of-band packet are both written atomically no data is
// interleaved
lock (_sendSync)
{
return ReportTcpSNIError(se);
try
{
packet.WriteToStream(_stream);
return TdsEnums.SNI_SUCCESS;
}
catch (ObjectDisposedException ode)
{
return ReportTcpSNIError(ode);
}
catch (SocketException se)
{
return ReportTcpSNIError(se);
}
catch (IOException ioe)
{
return ReportTcpSNIError(ioe);
}
}
catch (IOException ioe)
}
finally
{
if (releaseLock)
{
return ReportTcpSNIError(ioe);
Monitor.Exit(this);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ internal override PacketHandle CreateAndSetAttentionPacket()
SetPacketData(PacketHandle.FromManagedPacket(attnPacket), SQL.AttentionHeader, TdsEnums.HEADER_LEN);
_sniAsyncAttnPacket = attnPacket;
}
PacketHandle.FromManagedPacket(_sniAsyncAttnPacket).ManagedPacket.IsOutOfBand = true;
return PacketHandle.FromManagedPacket(_sniAsyncAttnPacket);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,5 +282,65 @@ private static void TimeOutDuringRead(string constr)
throw;
}
}

[CheckConnStrSetupFact]
public static void CancelDoesNotWait()
{
const int delaySeconds = 30;
const int cancelSeconds = 1;

using (SqlConnection conn = new SqlConnection(s_connStr))
using (var cmd = new SqlCommand($"WAITFOR DELAY '00:00:{delaySeconds:D2}'", conn))
{
conn.Open();

Task.Delay(TimeSpan.FromSeconds(cancelSeconds))
.ContinueWith(t => cmd.Cancel());

DateTime started = DateTime.UtcNow;
DateTime ended = default;
Exception exception = null;
try
{
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
exception = ex;
}
ended = DateTime.UtcNow;

Assert.NotNull(exception);
Assert.InRange((ended - started).TotalSeconds, cancelSeconds, delaySeconds - 1);
}
}

[CheckConnStrSetupFact]
public static async Task AsyncCancelDoesNotWait()
{
const int delaySeconds = 30;
const int cancelSeconds = 1;

using (SqlConnection conn = new SqlConnection(s_connStr))
using (var cmd = new SqlCommand($"WAITFOR DELAY '00:00:{delaySeconds:D2}'", conn))
{
await conn.OpenAsync();

DateTime started = DateTime.UtcNow;
Exception exception = null;
try
{
await cmd.ExecuteNonQueryAsync(new CancellationTokenSource(1000).Token);
}
catch (Exception ex)
{
exception = ex;
}
DateTime ended = DateTime.UtcNow;

Assert.NotNull(exception);
Assert.InRange((ended - started).TotalSeconds, cancelSeconds, delaySeconds - 1);
}
}
}
}