//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// [....]
// [....]
//------------------------------------------------------------------------------
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("System.Data.DataSetExtensions, PublicKey="+AssemblyRef.EcmaPublicKeyFull)] // DevDiv Bugs 92166
namespace System.Data.SqlClient
{
using System;
using System.Collections;
using System.Configuration.Assemblies;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Data.ProviderBase;
using System.Data.Sql;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Runtime.Serialization.Formatters;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Security;
using System.Security.Permissions;
using System.Reflection;
using System.Runtime.Versioning;
using Microsoft.SqlServer.Server;
using System.Security.Principal;
using System.Diagnostics.CodeAnalysis;
[DefaultEvent("InfoMessage")]
public sealed partial class SqlConnection: DbConnection, ICloneable {
static private readonly object EventInfoMessage = new object();
private SqlDebugContext _sdc; // SQL Debugging support
private bool _AsyncCommandInProgress;
// SQLStatistics support
internal SqlStatistics _statistics;
private bool _collectstats;
private bool _fireInfoMessageEventOnUserErrors; // False by default
// root task associated with current async invocation
Tuple, Task> _currentCompletion;
private SqlCredential _credential; // SQL authentication password stored in SecureString
private string _connectionString;
private int _connectRetryCount;
// connection resiliency
private object _reconnectLock = new object();
internal Task _currentReconnectionTask;
private Task _asyncWaitingForReconnection; // current async task waiting for reconnection in non-MARS connections
private Guid _originalConnectionId = Guid.Empty;
private CancellationTokenSource _reconnectionCancellationSource;
internal SessionData _recoverySessionData;
internal WindowsIdentity _lastIdentity;
internal WindowsIdentity _impersonateIdentity;
private int _reconnectCount;
public SqlConnection(string connectionString) : this(connectionString, null) {
}
public SqlConnection(string connectionString, SqlCredential credential) : this() {
ConnectionString = connectionString; // setting connection string first so that ConnectionOption is available
if (credential != null)
{
// The following checks are necessary as setting Credential property will call CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential
// CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential it will throw InvalidOperationException rather than Arguemtn exception
// Need to call setter on Credential property rather than setting _credential directly as pool groups need to be checked
SqlConnectionString connectionOptions = (SqlConnectionString) ConnectionOptions;
if (UsesClearUserIdOrPassword(connectionOptions))
{
throw ADP.InvalidMixedArgumentOfSecureAndClearCredential();
}
if (UsesIntegratedSecurity(connectionOptions))
{
throw ADP.InvalidMixedArgumentOfSecureCredentialAndIntegratedSecurity();
}
if (UsesContextConnection(connectionOptions))
{
throw ADP.InvalidMixedArgumentOfSecureCredentialAndContextConnection();
}
Credential = credential;
}
// else
// credential == null: we should not set "Credential" as this will do additional validation check and
// checking pool groups which is not necessary. All necessary operation is already done by calling "ConnectionString = connectionString"
CacheConnectionStringProperties();
}
private SqlConnection(SqlConnection connection) { // Clone
GC.SuppressFinalize(this);
CopyFrom(connection);
_connectionString = connection._connectionString;
if (connection._credential != null)
{
SecureString password = connection._credential.Password.Copy();
password.MakeReadOnly();
_credential = new SqlCredential(connection._credential.UserId, password);
}
CacheConnectionStringProperties();
}
// This method will be called once connection string is set or changed.
private void CacheConnectionStringProperties() {
SqlConnectionString connString = ConnectionOptions as SqlConnectionString;
if (connString != null) {
_connectRetryCount = connString.ConnectRetryCount;
}
}
//
// PUBLIC PROPERTIES
//
// used to start/stop collection of statistics data and do verify the current state
//
// devnote: start/stop should not performed using a property since it requires execution of code
//
// start statistics
// set the internal flag (_statisticsEnabled) to true.
// Create a new SqlStatistics object if not already there.
// connect the parser to the object.
// if there is no parser at this time we need to connect it after creation.
//
[
DefaultValue(false),
ResCategoryAttribute(Res.DataCategory_Data),
ResDescriptionAttribute(Res.SqlConnection_StatisticsEnabled),
]
public bool StatisticsEnabled {
get {
return (_collectstats);
}
set {
if (IsContextConnection) {
if (value) {
throw SQL.NotAvailableOnContextConnection();
}
}
else {
if (value) {
// start
if (ConnectionState.Open == State) {
if (null == _statistics) {
_statistics = new SqlStatistics();
ADP.TimerCurrent(out _statistics._openTimestamp);
}
// set statistics on the parser
// update timestamp;
Debug.Assert(Parser != null, "Where's the parser?");
Parser.Statistics = _statistics;
}
}
else {
// stop
if (null != _statistics) {
if (ConnectionState.Open == State) {
// remove statistics from parser
// update timestamp;
TdsParser parser = Parser;
Debug.Assert(parser != null, "Where's the parser?");
parser.Statistics = null;
ADP.TimerCurrent(out _statistics._closeTimestamp);
}
}
}
this._collectstats = value;
}
}
}
internal bool AsyncCommandInProgress {
get {
return (_AsyncCommandInProgress);
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
set {
_AsyncCommandInProgress = value;
}
}
internal bool IsContextConnection {
get {
SqlConnectionString opt = (SqlConnectionString)ConnectionOptions;
return UsesContextConnection(opt);
}
}
// Is this connection is a Context Connection?
private bool UsesContextConnection(SqlConnectionString opt)
{
return opt != null ? opt.ContextConnection : false;
}
// Does this connection uses Integrated Security?
private bool UsesIntegratedSecurity(SqlConnectionString opt) {
return opt != null ? opt.IntegratedSecurity : false;
}
// Does this connection uses old style of clear userID or Password in connection string?
private bool UsesClearUserIdOrPassword(SqlConnectionString opt) {
bool result = false;
if (null != opt) {
result = (!ADP.IsEmpty(opt.UserID) || !ADP.IsEmpty(opt.Password));
}
return result;
}
internal SqlConnectionString.TransactionBindingEnum TransactionBinding {
get {
return ((SqlConnectionString)ConnectionOptions).TransactionBinding;
}
}
internal SqlConnectionString.TypeSystem TypeSystem {
get {
return ((SqlConnectionString)ConnectionOptions).TypeSystemVersion;
}
}
internal Version TypeSystemAssemblyVersion {
get {
return ((SqlConnectionString)ConnectionOptions).TypeSystemAssemblyVersion;
}
}
internal int ConnectRetryInterval {
get {
return ((SqlConnectionString)ConnectionOptions).ConnectRetryInterval;
}
}
override protected DbProviderFactory DbProviderFactory {
get {
return SqlClientFactory.Instance;
}
}
[
DefaultValue(""),
#pragma warning disable 618 // ignore obsolete warning about RecommendedAsConfigurable to use SettingsBindableAttribute
RecommendedAsConfigurable(true),
#pragma warning restore 618
SettingsBindableAttribute(true),
RefreshProperties(RefreshProperties.All),
ResCategoryAttribute(Res.DataCategory_Data),
Editor("Microsoft.VSDesigner.Data.SQL.Design.SqlConnectionStringEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
ResDescriptionAttribute(Res.SqlConnection_ConnectionString),
]
override public string ConnectionString {
get {
return ConnectionString_Get();
}
set {
if (_credential != null)
{
SqlConnectionString connectionOptions = new SqlConnectionString(value);
CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions);
}
ConnectionString_Set(new SqlConnectionPoolKey(value, _credential));
_connectionString = value; // Change _connectionString value only after value is validated
CacheConnectionStringProperties();
}
}
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResDescriptionAttribute(Res.SqlConnection_ConnectionTimeout),
]
override public int ConnectionTimeout {
get {
SqlConnectionString constr = (SqlConnectionString)ConnectionOptions;
return ((null != constr) ? constr.ConnectTimeout : SqlConnectionString.DEFAULT.Connect_Timeout);
}
}
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResDescriptionAttribute(Res.SqlConnection_Database),
]
override public string Database {
// if the connection is open, we need to ask the inner connection what it's
// current catalog is because it may have gotten changed, otherwise we can
// just return what the connection string had.
get {
SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection);
string result;
if (null != innerConnection) {
result = innerConnection.CurrentDatabase;
}
else {
SqlConnectionString constr = (SqlConnectionString)ConnectionOptions;
result = ((null != constr) ? constr.InitialCatalog : SqlConnectionString.DEFAULT.Initial_Catalog);
}
return result;
}
}
[
Browsable(true),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResDescriptionAttribute(Res.SqlConnection_DataSource),
]
override public string DataSource {
get {
SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection);
string result;
if (null != innerConnection) {
result = innerConnection.CurrentDataSource;
}
else {
SqlConnectionString constr = (SqlConnectionString)ConnectionOptions;
result = ((null != constr) ? constr.DataSource : SqlConnectionString.DEFAULT.Data_Source);
}
return result;
}
}
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResCategoryAttribute(Res.DataCategory_Data),
ResDescriptionAttribute(Res.SqlConnection_PacketSize),
]
public int PacketSize {
// if the connection is open, we need to ask the inner connection what it's
// current packet size is because it may have gotten changed, otherwise we
// can just return what the connection string had.
get {
if (IsContextConnection) {
throw SQL.NotAvailableOnContextConnection();
}
SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds);
int result;
if (null != innerConnection) {
result = innerConnection.PacketSize;
}
else {
SqlConnectionString constr = (SqlConnectionString)ConnectionOptions;
result = ((null != constr) ? constr.PacketSize : SqlConnectionString.DEFAULT.Packet_Size);
}
return result;
}
}
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResCategoryAttribute(Res.DataCategory_Data),
ResDescriptionAttribute(Res.SqlConnection_ClientConnectionId),
]
public Guid ClientConnectionId {
get {
SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds);
if (null != innerConnection) {
return innerConnection.ClientConnectionId;
}
else {
Task reconnectTask = _currentReconnectionTask;
if (reconnectTask != null && !reconnectTask.IsCompleted) {
return _originalConnectionId;
}
return Guid.Empty;
}
}
}
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResDescriptionAttribute(Res.SqlConnection_ServerVersion),
]
override public string ServerVersion {
get {
return GetOpenConnection().ServerVersion;
}
}
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResDescriptionAttribute(Res.DbConnection_State),
]
override public ConnectionState State {
get {
Task reconnectTask=_currentReconnectionTask;
if (reconnectTask != null && !reconnectTask.IsCompleted) {
return ConnectionState.Open;
}
return InnerConnection.State;
}
}
internal SqlStatistics Statistics {
get {
return _statistics;
}
}
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResCategoryAttribute(Res.DataCategory_Data),
ResDescriptionAttribute(Res.SqlConnection_WorkstationId),
]
public string WorkstationId {
get {
if (IsContextConnection) {
throw SQL.NotAvailableOnContextConnection();
}
// If not supplied by the user, the default value is the MachineName
// Note: In Longhorn you'll be able to rename a machine without
// rebooting. Therefore, don't cache this machine name.
SqlConnectionString constr = (SqlConnectionString)ConnectionOptions;
string result = ((null != constr) ? constr.WorkstationId : null);
if (null == result) {
// getting machine name requires Environment.Permission
// user must have that permission in order to retrieve this
result = Environment.MachineName;
}
return result;
}
}
// SqlCredential: Pair User Id and password in SecureString which are to be used for SQL authentication
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResDescriptionAttribute(Res.SqlConnection_Credential),
]
public SqlCredential Credential
{
get
{
SqlCredential result = _credential;
// When a connection is connecting or is ever opened, make credential available only if "Persist Security Info" is set to true
// otherwise, return null
SqlConnectionString connectionOptions = (SqlConnectionString) UserConnectionOptions;
if (InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo)
{
result = null;
}
return result;
}
set
{
// If a connection is connecting or is ever opened, user id/password cannot be set
if (!InnerConnection.AllowSetConnectionString)
{
throw ADP.OpenConnectionPropertySet("Credential", InnerConnection.State);
}
// check if the usage of credential has any conflict with the keys used in connection string
if (value != null)
{
CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential((SqlConnectionString) ConnectionOptions);
}
_credential = value;
// Need to call ConnectionString_Set to do proper pool group check
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential));
}
}
// CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential: check if the usage of credential has any conflict
// with the keys used in connection string
// If there is any conflict, it throws InvalidOperationException
// This is to be used setter of ConnectionString and Credential properties
private void CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(SqlConnectionString connectionOptions)
{
if (UsesClearUserIdOrPassword(connectionOptions))
{
throw ADP.InvalidMixedUsageOfSecureAndClearCredential();
}
if (UsesIntegratedSecurity(connectionOptions))
{
throw ADP.InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity();
}
if (UsesContextConnection(connectionOptions))
{
throw ADP.InvalidMixedArgumentOfSecureCredentialAndContextConnection();
}
}
//
// PUBLIC EVENTS
//
[
ResCategoryAttribute(Res.DataCategory_InfoMessage),
ResDescriptionAttribute(Res.DbConnection_InfoMessage),
]
public event SqlInfoMessageEventHandler InfoMessage {
add {
Events.AddHandler(EventInfoMessage, value);
}
remove {
Events.RemoveHandler(EventInfoMessage, value);
}
}
public bool FireInfoMessageEventOnUserErrors {
get {
return _fireInfoMessageEventOnUserErrors;
}
set {
_fireInfoMessageEventOnUserErrors = value;
}
}
// Approx. number of times that the internal connection has been reconnected
internal int ReconnectCount {
get {
return _reconnectCount;
}
}
//
// PUBLIC METHODS
//
new public SqlTransaction BeginTransaction() {
// this is just a delegate. The actual method tracks executiontime
return BeginTransaction(IsolationLevel.Unspecified, null);
}
new public SqlTransaction BeginTransaction(IsolationLevel iso) {
// this is just a delegate. The actual method tracks executiontime
return BeginTransaction(iso, null);
}
public SqlTransaction BeginTransaction(string transactionName) {
// Use transaction names only on the outermost pair of nested
// BEGIN...COMMIT or BEGIN...ROLLBACK statements. Transaction names
// are ignored for nested BEGIN's. The only way to rollback a nested
// transaction is to have a save point from a SAVE TRANSACTION call.
return BeginTransaction(IsolationLevel.Unspecified, transactionName);
}
// suppress this message - we cannot use SafeHandle here. Also, see notes in the code (VSTFDEVDIV# 560355)
[SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]
override protected DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) {
IntPtr hscp;
Bid.ScopeEnter(out hscp, " %d#, isolationLevel=%d{ds.IsolationLevel}", ObjectID, (int)isolationLevel);
try {
DbTransaction transaction = BeginTransaction(isolationLevel);
// VSTFDEVDIV# 560355 - InnerConnection doesn't maintain a ref on the outer connection (this) and
// subsequently leaves open the possibility that the outer connection could be GC'ed before the SqlTransaction
// is fully hooked up (leaving a DbTransaction with a null connection property). Ensure that this is reachable
// until the completion of BeginTransaction with KeepAlive
GC.KeepAlive(this);
return transaction;
}
finally {
Bid.ScopeLeave(ref hscp);
}
}
public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionName) {
WaitForPendingReconnection();
SqlStatistics statistics = null;
IntPtr hscp;
string xactName = ADP.IsEmpty(transactionName)? "None" : transactionName;
Bid.ScopeEnter(out hscp, " %d#, iso=%d{ds.IsolationLevel}, transactionName='%ls'\n", ObjectID, (int)iso,
xactName);
try {
statistics = SqlStatistics.StartTimer(Statistics);
// NOTE: we used to throw an exception if the transaction name was empty
// (see MDAC 50292) but that was incorrect because we have a BeginTransaction
// method that doesn't have a transactionName argument.
SqlTransaction transaction;
bool isFirstAttempt = true;
do {
transaction = GetOpenConnection().BeginSqlTransaction(iso, transactionName, isFirstAttempt); // do not reconnect twice
Debug.Assert(isFirstAttempt || !transaction.InternalTransaction.ConnectionHasBeenRestored, "Restored connection on non-first attempt");
isFirstAttempt = false;
} while (transaction.InternalTransaction.ConnectionHasBeenRestored);
// SQLBU 503873 The GetOpenConnection line above doesn't keep a ref on the outer connection (this),
// and it could be collected before the inner connection can hook it to the transaction, resulting in
// a transaction with a null connection property. Use GC.KeepAlive to ensure this doesn't happen.
GC.KeepAlive(this);
return transaction;
}
finally {
Bid.ScopeLeave(ref hscp);
SqlStatistics.StopTimer(statistics);
}
}
override public void ChangeDatabase(string database) {
SqlStatistics statistics = null;
RepairInnerConnection();
Bid.CorrelationTrace(" ObjectID%d#, ActivityID %ls\n", ObjectID);
TdsParser bestEffortCleanupTarget = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
#if DEBUG
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
RuntimeHelpers.PrepareConstrainedRegions();
try {
tdsReliabilitySection.Start();
#else
{
#endif //DEBUG
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this);
statistics = SqlStatistics.StartTimer(Statistics);
InnerConnection.ChangeDatabase(database);
}
#if DEBUG
finally {
tdsReliabilitySection.Stop();
}
#endif //DEBUG
}
catch (System.OutOfMemoryException e) {
Abort(e);
throw;
}
catch (System.StackOverflowException e) {
Abort(e);
throw;
}
catch (System.Threading.ThreadAbortException e) {
Abort(e);
SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
throw;
}
finally {
SqlStatistics.StopTimer(statistics);
}
}
static public void ClearAllPools() {
(new SqlClientPermission(PermissionState.Unrestricted)).Demand();
SqlConnectionFactory.SingletonInstance.ClearAllPools();
}
static public void ClearPool(SqlConnection connection) {
ADP.CheckArgumentNull(connection, "connection");
DbConnectionOptions connectionOptions = connection.UserConnectionOptions;
if (null != connectionOptions) {
connectionOptions.DemandPermission();
if (connection.IsContextConnection) {
throw SQL.NotAvailableOnContextConnection();
}
SqlConnectionFactory.SingletonInstance.ClearPool(connection);
}
}
object ICloneable.Clone() {
SqlConnection clone = new SqlConnection(this);
Bid.Trace(" %d#, clone=%d#\n", ObjectID, clone.ObjectID);
return clone;
}
void CloseInnerConnection() {
// CloseConnection() now handles the lock
// The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and
// the command will no longer be cancelable. It might be desirable to be able to cancel the close opperation, but this is
// outside of the scope of Whidbey RTM. See (SqlCommand::Cancel) for other lock.
InnerConnection.CloseConnection(this, ConnectionFactory);
}
override public void Close() {
IntPtr hscp;
Bid.ScopeEnter(out hscp, " %d#" , ObjectID);
Bid.CorrelationTrace(" ObjectID%d#, ActivityID %ls\n", ObjectID);
try {
SqlStatistics statistics = null;
TdsParser bestEffortCleanupTarget = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
#if DEBUG
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
RuntimeHelpers.PrepareConstrainedRegions();
try {
tdsReliabilitySection.Start();
#else
{
#endif //DEBUG
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this);
statistics = SqlStatistics.StartTimer(Statistics);
Task reconnectTask = _currentReconnectionTask;
if (reconnectTask != null && !reconnectTask.IsCompleted) {
CancellationTokenSource cts = _reconnectionCancellationSource;
if (cts != null) {
cts.Cancel();
}
AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false); // we do not need to deal with possible exceptions in reconnection
if (State != ConnectionState.Open) {// if we cancelled before the connection was opened
OnStateChange(DbConnectionInternal.StateChangeClosed);
}
}
CancelOpenAndWait();
CloseInnerConnection();
GC.SuppressFinalize(this);
if (null != Statistics) {
ADP.TimerCurrent(out _statistics._closeTimestamp);
}
}
#if DEBUG
finally {
tdsReliabilitySection.Stop();
}
#endif //DEBUG
}
catch (System.OutOfMemoryException e) {
Abort(e);
throw;
}
catch (System.StackOverflowException e) {
Abort(e);
throw;
}
catch (System.Threading.ThreadAbortException e) {
Abort(e);
SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
throw;
}
finally {
SqlStatistics.StopTimer(statistics);
}
}
finally {
SqlDebugContext sdc = _sdc;
_sdc = null;
Bid.ScopeLeave(ref hscp);
if (sdc != null) {
sdc.Dispose();
}
}
}
new public SqlCommand CreateCommand() {
return new SqlCommand(null, this);
}
private void DisposeMe(bool disposing) { // MDAC 65459
_credential = null; // clear credential here rather than in IDisposable.Dispose as this is only specific to SqlConnection only
// IDisposable.Dispose is generated code from a template and used by other providers as well
if (!disposing) {
// DevDiv2 Bug 457934:SQLConnection leaks when not disposed
// http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/457934
// For non-pooled connections we need to make sure that if the SqlConnection was not closed, then we release the GCHandle on the stateObject to allow it to be GCed
// For pooled connections, we will rely on the pool reclaiming the connection
var innerConnection = (InnerConnection as SqlInternalConnectionTds);
if ((innerConnection != null) && (!innerConnection.ConnectionOptions.Pooling)) {
var parser = innerConnection.Parser;
if ((parser != null) && (parser._physicalStateObj != null)) {
parser._physicalStateObj.DecrementPendingCallbacks(release: false);
}
}
}
}
public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction transaction) {
if (IsContextConnection) {
throw SQL.NotAvailableOnContextConnection();
}
EnlistDistributedTransactionHelper(transaction);
}
override public void Open() {
IntPtr hscp;
Bid.ScopeEnter(out hscp, " %d#", ObjectID) ;
Bid.CorrelationTrace(" ObjectID%d#, ActivityID %ls\n", ObjectID);
try {
if (StatisticsEnabled) {
if (null == _statistics) {
_statistics = new SqlStatistics();
}
else {
_statistics.ContinueOnNewConnection();
}
}
SqlStatistics statistics = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
statistics = SqlStatistics.StartTimer(Statistics);
if (!TryOpen(null)) {
throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending);
}
}
finally {
SqlStatistics.StopTimer(statistics);
}
}
finally {
Bid.ScopeLeave(ref hscp);
}
}
internal void RegisterWaitingForReconnect(Task waitingTask) {
if (((SqlConnectionString)ConnectionOptions).MARS) {
return;
}
Interlocked.CompareExchange(ref _asyncWaitingForReconnection, waitingTask, null);
if (_asyncWaitingForReconnection != waitingTask) { // somebody else managed to register
throw SQL.MARSUnspportedOnConnection();
}
}
private async Task ReconnectAsync(int timeout) {
try {
long commandTimeoutExpiration = 0;
if (timeout > 0) {
commandTimeoutExpiration = ADP.TimerCurrent() + ADP.TimerFromSeconds(timeout);
}
CancellationTokenSource cts = new CancellationTokenSource();
_reconnectionCancellationSource = cts;
CancellationToken ctoken = cts.Token;
int retryCount = _connectRetryCount; // take a snapshot: could be changed by modifying the connection string
for (int attempt = 0; attempt < retryCount; attempt++) {
if (ctoken.IsCancellationRequested) {
Bid.Trace(" Orginal ClientConnectionID %ls - reconnection cancelled\n", _originalConnectionId.ToString());
return;
}
try {
_impersonateIdentity = _lastIdentity;
try {
ForceNewConnection = true;
await OpenAsync(ctoken).ConfigureAwait(false);
// On success, increment the reconnect count - we don't really care if it rolls over since it is approx.
_reconnectCount = unchecked(_reconnectCount + 1);
#if DEBUG
Debug.Assert(_recoverySessionData._debugReconnectDataApplied, "Reconnect data was not applied !");
#endif
}
finally {
_impersonateIdentity = null;
ForceNewConnection = false;
}
Bid.Trace(" Reconnection suceeded. ClientConnectionID %ls -> %ls \n", _originalConnectionId.ToString(), ClientConnectionId.ToString());
return;
}
catch (SqlException e) {
Bid.Trace(" Orginal ClientConnectionID %ls - reconnection attempt failed error %ls\n", _originalConnectionId.ToString(), e.Message);
if (attempt == retryCount - 1) {
Bid.Trace(" Orginal ClientConnectionID %ls - give up reconnection\n", _originalConnectionId.ToString());
throw SQL.CR_AllAttemptsFailed(e, _originalConnectionId);
}
if (timeout > 0 && ADP.TimerRemaining(commandTimeoutExpiration) < ADP.TimerFromSeconds(ConnectRetryInterval)) {
throw SQL.CR_NextAttemptWillExceedQueryTimeout(e, _originalConnectionId);
}
}
await Task.Delay(1000 * ConnectRetryInterval, ctoken).ConfigureAwait(false);
}
}
finally {
_recoverySessionData = null;
_supressStateChangeForReconnection = false;
}
Debug.Assert(false, "Should not reach this point");
}
internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) {
Task runningReconnect = _currentReconnectionTask;
// This loop in the end will return not completed reconnect task or null
while (runningReconnect != null && runningReconnect.IsCompleted) {
// clean current reconnect task (if it is the same one we checked
Interlocked.CompareExchange(ref _currentReconnectionTask, null, runningReconnect);
// make sure nobody started new task (if which case we did not clean it)
runningReconnect = _currentReconnectionTask;
}
if (runningReconnect == null) {
if (_connectRetryCount > 0) {
SqlInternalConnectionTds tdsConn = GetOpenTdsConnection();
if (tdsConn._sessionRecoveryAcknowledged) {
TdsParserStateObject stateObj = tdsConn.Parser._physicalStateObj;
if (!stateObj.ValidateSNIConnection()) {
if (tdsConn.Parser._sessionPool != null) {
if (tdsConn.Parser._sessionPool.ActiveSessionsCount > 0) {
// >1 MARS session
if (beforeDisconnect != null) {
beforeDisconnect();
}
OnError(SQL.CR_UnrecoverableClient(ClientConnectionId), true, null);
}
}
SessionData cData = tdsConn.CurrentSessionData;
cData.AssertUnrecoverableStateCountIsCorrect();
if (cData._unrecoverableStatesCount == 0) {
bool callDisconnect = false;
lock (_reconnectLock) {
tdsConn.CheckEnlistedTransactionBinding();
runningReconnect = _currentReconnectionTask; // double check after obtaining the lock
if (runningReconnect == null) {
if (cData._unrecoverableStatesCount == 0) { // could change since the first check, but now is stable since connection is know to be broken
_originalConnectionId = ClientConnectionId;
Bid.Trace(" Connection ClientConnectionID %ls is invalid, reconnecting\n", _originalConnectionId.ToString());
_recoverySessionData = cData;
if (beforeDisconnect != null) {
beforeDisconnect();
}
try {
_supressStateChangeForReconnection = true;
tdsConn.DoomThisConnection();
}
catch (SqlException) {
}
runningReconnect = Task.Run(() => ReconnectAsync(timeout));
// if current reconnect is not null, somebody already started reconnection task - some kind of race condition
Debug.Assert(_currentReconnectionTask == null, "Duplicate reconnection tasks detected");
_currentReconnectionTask = runningReconnect;
}
}
else {
callDisconnect = true;
}
}
if (callDisconnect && beforeDisconnect != null) {
beforeDisconnect();
}
}
else {
if (beforeDisconnect != null) {
beforeDisconnect();
}
OnError(SQL.CR_UnrecoverableServer(ClientConnectionId), true, null);
}
} // ValidateSNIConnection
} // sessionRecoverySupported
} // connectRetryCount>0
}
else { // runningReconnect = null
if (beforeDisconnect != null) {
beforeDisconnect();
}
}
return runningReconnect;
}
// this is straightforward, but expensive method to do connection resiliency - it take locks and all prepartions as for TDS request
partial void RepairInnerConnection() {
WaitForPendingReconnection();
if (_connectRetryCount == 0) {
return;
}
SqlInternalConnectionTds tdsConn = InnerConnection as SqlInternalConnectionTds;
if (tdsConn != null) {
tdsConn.ValidateConnectionForExecute(null);
tdsConn.GetSessionAndReconnectIfNeeded((SqlConnection)this);
}
}
private void WaitForPendingReconnection() {
Task reconnectTask = _currentReconnectionTask;
if (reconnectTask != null && !reconnectTask.IsCompleted) {
AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false);
}
}
void CancelOpenAndWait()
{
// copy from member to avoid changes by background thread
var completion = _currentCompletion;
if (completion != null)
{
completion.Item1.TrySetCanceled();
((IAsyncResult)completion.Item2).AsyncWaitHandle.WaitOne();
}
Debug.Assert(_currentCompletion == null, "After waiting for an async call to complete, there should be no completion source");
}
public override Task OpenAsync(CancellationToken cancellationToken) {
IntPtr hscp;
Bid.ScopeEnter(out hscp, " %d#", ObjectID) ;
Bid.CorrelationTrace(" ObjectID%d#, ActivityID %ls\n", ObjectID);
try {
if (StatisticsEnabled) {
if (null == _statistics) {
_statistics = new SqlStatistics();
}
else {
_statistics.ContinueOnNewConnection();
}
}
SqlStatistics statistics = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
statistics = SqlStatistics.StartTimer(Statistics);
System.Transactions.Transaction transaction = ADP.GetCurrentTransaction();
TaskCompletionSource completion = new TaskCompletionSource(transaction);
TaskCompletionSource