Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / whidbey / NetFxQFE / ndp / fx / src / Data / System / Data / SqlClient / SqlInternalConnectionTds.cs / 1 / SqlInternalConnectionTds.cs
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// [....]
// [....]
//-----------------------------------------------------------------------------
namespace System.Data.SqlClient
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using SysTx = System.Transactions;
sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable {
// CONNECTION AND STATE VARIABLES
private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
private TdsParser _parser;
private SqlLoginAck _loginAck;
// FOR POOLING
private bool _fConnectionOpen = false;
// FOR CONNECTION RESET MANAGEMENT
private bool _fResetConnection;
private string _originalDatabase;
private string _currentFailoverPartner; // only set by ENV change from server
private string _originalLanguage;
private string _currentLanguage;
private int _currentPacketSize;
private int _asyncCommandCount; // number of async Begins minus number of async Ends.
// FOR SSE
private string _instanceName = String.Empty;
// FOR NOTIFICATIONS
private DbConnectionPoolIdentity _identity; // Used to lookup info for notification matching Start().
// OTHER STATE VARIABLES AND REFERENCES
// don't use a SqlCommands collection because this is an internal tracking list. That is, we don't want
// the command to "know" it's in a collection.
private List _preparedCommands; // SQLBU: 446234. Use weakreference to avoid memory leak
#if WINFSFunctionality
//Hash table of UDT caches. Created in CreateAssemblyCache
private static Dictionary _assemblyCacheTable = new Dictionary();
private static object _assemblyCacheLock = new object();
#endif
// although the new password is generally not used it must be passed to the c'tor
// the new Login7 packet will always write out the new password (or a length of zero and no bytes if not present)
//
internal SqlInternalConnectionTds(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, object providerInfo, string newPassword, SqlConnection owningObject, bool redirectedUserInstance) : base(connectionOptions) {
#if DEBUG
try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath
if (null != owningObject) {
owningObject.UserConnectionOptions.DemandPermission();
}
else {
connectionOptions.DemandPermission();
}
}
catch(System.Security.SecurityException) {
System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
throw;
}
#endif
if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) {
throw SQL.UserInstanceNotAvailableInProc();
}
_identity = identity;
Debug.Assert(null!=newPassword, "newPassword argument must not be null");
_poolGroupProviderInfo = (SqlConnectionPoolGroupProviderInfo)providerInfo;
_fResetConnection = connectionOptions.ConnectionReset;
if (_fResetConnection) {
_originalDatabase = connectionOptions.InitialCatalog;
_originalLanguage = connectionOptions.CurrentLanguage;
}
RuntimeHelpers.PrepareConstrainedRegions();
try {
#if DEBUG
object initialReliabilitySlotValue = Thread.GetData(TdsParser.ReliabilitySlot);
RuntimeHelpers.PrepareConstrainedRegions();
try {
Thread.SetData(TdsParser.ReliabilitySlot, true);
#endif //DEBUG
OpenLoginEnlist(owningObject, connectionOptions, newPassword, redirectedUserInstance);
#if DEBUG
}
finally {
Thread.SetData(TdsParser.ReliabilitySlot, initialReliabilitySlotValue);
}
#endif //DEBUG
}
catch (System.OutOfMemoryException) {
DoomThisConnection();
throw;
}
catch (System.StackOverflowException) {
DoomThisConnection();
throw;
}
catch (System.Threading.ThreadAbortException) {
DoomThisConnection();
throw;
}
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, constructed new TDS internal connection\n", ObjectID);
}
}
#if WINFSFunctionality
internal AssemblyCache AssemblyCache{
get{
return CreateAssemblyCache();
}
}
#endif
override internal SqlInternalTransaction CurrentTransaction {
get {
return _parser.CurrentTransaction;
}
}
override internal SqlInternalTransaction AvailableInternalTransaction {
get {
return _parser._fResetConnection ? null : CurrentTransaction;
}
}
override internal SqlInternalTransaction PendingTransaction {
get {
return _parser.PendingTransaction;
}
}
internal DbConnectionPoolIdentity Identity {
get {
return _identity;
}
}
internal string InstanceName {
get {
return _instanceName;
}
}
override internal bool IsLockedForBulkCopy {
get {
return (!Parser.MARSOn && Parser._physicalStateObj.BcpLock);
}
}
override protected internal bool IsNonPoolableTransactionRoot {
get {
return IsTransactionRoot && (!IsKatmaiOrNewer || null == Pool);
}
}
override internal bool IsShiloh {
get {
return _loginAck.isVersion8;
}
}
override internal bool IsYukonOrNewer {
get {
return _parser.IsYukonOrNewer;
}
}
override internal bool IsKatmaiOrNewer {
get {
return _parser.IsKatmaiOrNewer;
}
}
#if WINFSFunctionality
override internal bool IsWinFS {
get {
return _parser.IsWinFS;
}
}
#endif
internal int PacketSize {
get {
return _currentPacketSize;
}
}
internal TdsParser Parser {
get {
return _parser;
}
}
internal string ServerProvidedFailOverPartner {
get {
return _currentFailoverPartner;
}
}
internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo {
get {
return _poolGroupProviderInfo;
}
}
override protected bool ReadyToPrepareTransaction {
get {
//
bool result = (null == FindLiveReader(null)); // can't prepare with a live data reader...
return result;
}
}
override public string ServerVersion {
get {
return(String.Format((IFormatProvider)null, "{0:00}.{1:00}.{2:0000}", _loginAck.majorVersion,
(short) _loginAck.minorVersion, _loginAck.buildNum));
}
}
////////////////////////////////////////////////////////////////////////////////////////
// GENERAL METHODS
////////////////////////////////////////////////////////////////////////////////////////
override protected void ChangeDatabaseInternal(string database) {
// MDAC 73598 - add brackets around database
database = SqlConnection.FixupDatabaseTransactionName(database);
_parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj);
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
}
override public void Dispose() {
if (Bid.AdvancedOn) {
Bid.Trace(" %d# disposing\n", base.ObjectID);
}
try {
TdsParser parser = Interlocked.Exchange(ref _parser, null); // guard against multiple concurrent dispose calls -- Delegated Transactions might cause this.
Debug.Assert(parser != null && _fConnectionOpen || parser == null && !_fConnectionOpen, "Unexpected state on dispose");
if (null != parser) {
parser.Disconnect();
}
}
finally { //
// close will always close, even if exception is thrown
// remember to null out any object references
_loginAck = null;
_fConnectionOpen = false; // mark internal connection as closed
}
base.Dispose();
}
override internal void ValidateConnectionForExecute(SqlCommand command) {
SqlDataReader reader = null;
if (Parser.MARSOn) {
if (null != command) { // command can't have datareader already associated with it
reader = FindLiveReader(command);
}
}
else { // single datareader per connection
reader = FindLiveReader(null);
}
if (null != reader) {
// if MARS is on, then a datareader associated with the command exists
// or if MARS is off, then a datareader exists
throw ADP.OpenReaderExists(); // MDAC 66411
}
else if (!Parser.MARSOn && Parser._physicalStateObj._pendingData) {
Parser._physicalStateObj.CleanWire();
}
Debug.Assert(!Parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!");
Parser.RollbackOrphanedAPITransactions();
}
////////////////////////////////////////////////////////////////////////////////////////
// POOLING METHODS
////////////////////////////////////////////////////////////////////////////////////////
override protected void Activate(SysTx.Transaction transaction) {
FailoverPermissionDemand(); // Demand for unspecified failover pooled connections
// When we're required to automatically enlist in transactions and
// there is one we enlist in it. On the other hand, if there isn't a
// transaction and we are currently enlisted in one, then we
// unenlist from it.
//
// Regardless of whether we're required to automatically enlist,
// when there is not a current transaction, we cannot leave the
// connection enlisted in a transaction.
if (null != transaction){
if (ConnectionOptions.Enlist) {
Enlist(transaction);
}
}
else {
Enlist(null);
}
}
override protected void InternalDeactivate() {
// When we're deactivated, the user must have called End on all
// the async commands, or we don't know that we're in a state that
// we can recover from. We doom the connection in this case, to
// prevent odd cases when we go to the wire.
if (0 != _asyncCommandCount) {
DoomThisConnection();
}
// If we're deactivating with a delegated transaction, we
// should not be cleaning up the parser just yet, that will
// cause our transaction to be rolled back and the connection
// to be reset. We'll get called again once the delegated
// transaction is completed and we can do it all then.
if (!IsNonPoolableTransactionRoot) {
Debug.Assert(null != _parser, "Deactivating a disposed connection?");
_parser.Deactivate(IsConnectionDoomed);
if (!IsConnectionDoomed) {
ResetConnection();
}
}
}
private void ResetConnection() {
// For implicit pooled connections, if connection reset behavior is specified,
// reset the database and language properties back to default. It is important
// to do this on activate so that the hashtable is correct before SqlConnection
// obtains a clone.
Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction.");
Debug.Assert(!_parser._physicalStateObj._pendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data.");
if (_fResetConnection) {
// Ensure we are either going against shiloh, or we are not enlisted in a
// distributed transaction - otherwise don't reset!
if (IsShiloh) {
// Prepare the parser for the connection reset - the next time a trip
// to the server is made.
_parser.PrepareResetConnection(IsTransactionRoot && !IsNonPoolableTransactionRoot);
}
else if (!IsEnlistedInTransaction) {
// If not Shiloh, we are going against Sphinx. On Sphinx, we
// may only reset if not enlisted in a distributed transaction.
try {
// execute sp
_parser.TdsExecuteSQLBatch("sp_reset_connection", 30, null, _parser._physicalStateObj);
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
}
catch (Exception e) {
//
if (!ADP.IsCatchableExceptionType(e)) {
throw;
}
DoomThisConnection();
ADP.TraceExceptionWithoutRethrow(e);
}
}
// Reset hashtable values, since calling reset will not send us env_changes.
CurrentDatabase = _originalDatabase;
_currentLanguage = _originalLanguage;
}
}
internal void DecrementAsyncCount() {
Interlocked.Decrement(ref _asyncCommandCount);
}
internal void IncrementAsyncCount() {
Interlocked.Increment(ref _asyncCommandCount);
}
////////////////////////////////////////////////////////////////////////////////////////
// LOCAL TRANSACTION METHODS
////////////////////////////////////////////////////////////////////////////////////////
override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) {
TdsParser parser = Parser;
if (null != parser) {
parser.DisconnectTransaction(internalTransaction);
}
}
internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso) {
ExecuteTransaction(transactionRequest, name, iso, null, false);
}
override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) {
if (IsConnectionDoomed) { // doomed means we can't do anything else...
if (transactionRequest == TransactionRequest.Rollback
|| transactionRequest == TransactionRequest.IfRollback) {
return;
}
throw SQL.ConnectionDoomed();
}
if (transactionRequest == TransactionRequest.Commit
|| transactionRequest == TransactionRequest.Rollback
|| transactionRequest == TransactionRequest.IfRollback) {
if (!Parser.MARSOn && Parser._physicalStateObj.BcpLock) {
throw SQL.ConnectionLockedForBcpEvent();
}
}
string transactionName = (null == name) ? String.Empty : name;
if (!_parser.IsYukonOrNewer) {
ExecuteTransactionPreYukon(transactionRequest, transactionName, iso, internalTransaction);
}
else {
ExecuteTransactionYukon(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest);
}
}
internal void ExecuteTransactionPreYukon(TransactionRequest transactionRequest, string transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction) {
StringBuilder sqlBatch = new StringBuilder();
switch (iso) {
case IsolationLevel.Unspecified:
break;
case IsolationLevel.ReadCommitted:
sqlBatch.Append(TdsEnums.TRANS_READ_COMMITTED);
sqlBatch.Append(";");
break;
case IsolationLevel.ReadUncommitted:
sqlBatch.Append(TdsEnums.TRANS_READ_UNCOMMITTED);
sqlBatch.Append(";");
break;
case IsolationLevel.RepeatableRead:
sqlBatch.Append(TdsEnums.TRANS_REPEATABLE_READ);
sqlBatch.Append(";");
break;
case IsolationLevel.Serializable:
sqlBatch.Append(TdsEnums.TRANS_SERIALIZABLE);
sqlBatch.Append(";");
break;
case IsolationLevel.Snapshot:
throw SQL.SnapshotNotSupported(IsolationLevel.Snapshot);
case IsolationLevel.Chaos:
throw SQL.NotSupportedIsolationLevel(iso);
default:
throw ADP.InvalidIsolationLevel(iso);
}
if (!ADP.IsEmpty(transactionName)) {
transactionName = " " + SqlConnection.FixupDatabaseTransactionName(transactionName);
}
switch (transactionRequest) {
case TransactionRequest.Begin:
sqlBatch.Append(TdsEnums.TRANS_BEGIN);
sqlBatch.Append(transactionName);
break;
case TransactionRequest.Promote:
Debug.Assert(false, "Promote called with transaction name or on pre-Yukon!");
break;
case TransactionRequest.Commit:
sqlBatch.Append(TdsEnums.TRANS_COMMIT);
sqlBatch.Append(transactionName);
break;
case TransactionRequest.Rollback:
sqlBatch.Append(TdsEnums.TRANS_ROLLBACK);
sqlBatch.Append(transactionName);
break;
case TransactionRequest.IfRollback:
sqlBatch.Append(TdsEnums.TRANS_IF_ROLLBACK);
sqlBatch.Append(transactionName);
break;
case TransactionRequest.Save:
sqlBatch.Append(TdsEnums.TRANS_SAVE);
sqlBatch.Append(transactionName);
break;
default:
Debug.Assert(false, "Unknown transaction type");
break;
}
_parser.TdsExecuteSQLBatch(sqlBatch.ToString(), ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj);
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
// Prior to Yukon, we didn't have any transaction tokens to manage,
// or any feedback to know when one was created, so we just presume
// that successful execution of the request caused the transaction
// to be created, and we set that on the parser.
if (TransactionRequest.Begin == transactionRequest) {
Debug.Assert(null != internalTransaction, "Begin Transaction request without internal transaction");
_parser.CurrentTransaction = internalTransaction;
}
}
internal void ExecuteTransactionYukon(TransactionRequest transactionRequest, string transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) {
TdsEnums.TransactionManagerRequestType requestType = TdsEnums.TransactionManagerRequestType.Begin;
TdsEnums.TransactionManagerIsolationLevel isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
switch (iso) {
case IsolationLevel.Unspecified:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.Unspecified;
break;
case IsolationLevel.ReadCommitted:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
break;
case IsolationLevel.ReadUncommitted:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadUncommitted;
break;
case IsolationLevel.RepeatableRead:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.RepeatableRead;
break;
case IsolationLevel.Serializable:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.Serializable;
break;
case IsolationLevel.Snapshot:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.Snapshot;
break;
case IsolationLevel.Chaos:
throw SQL.NotSupportedIsolationLevel(iso);
default:
throw ADP.InvalidIsolationLevel(iso);
}
TdsParserStateObject stateObj = _parser._physicalStateObj;
TdsParser parser = _parser;
bool mustPutSession = false;
bool mustRelease = false;
try {
switch (transactionRequest) {
case TransactionRequest.Begin:
requestType = TdsEnums.TransactionManagerRequestType.Begin;
break;
case TransactionRequest.Promote:
requestType = TdsEnums.TransactionManagerRequestType.Promote;
break;
case TransactionRequest.Commit:
requestType = TdsEnums.TransactionManagerRequestType.Commit;
break;
case TransactionRequest.IfRollback:
// Map IfRollback to Rollback since with Yukon and beyond we should never need
// the if since the server will inform us when transactions have completed
// as a result of an error on the server.
case TransactionRequest.Rollback:
requestType = TdsEnums.TransactionManagerRequestType.Rollback;
break;
case TransactionRequest.Save:
requestType = TdsEnums.TransactionManagerRequestType.Save;
break;
default:
Debug.Assert(false, "Unknown transaction type");
break;
}
// SQLBUDT #20010853 - Promote, Commit and Rollback requests for
// delegated transactions often happen while there is an open result
// set, so we need to handle them by using a different MARS session,
// otherwise we'll write on the physical state objects while someone
// else is using it. When we don't have MARS enabled, we need to
// lock the physical state object to syncronize it's use at least
// until we increment the open results count. Once it's been
// incremented the delegated transaction requests will fail, so they
// won't stomp on anything.
//
// We need to keep this lock through the duration of the TM reqeuest
// so that we won't hijack a different request's data stream and a
// different request won't hijack ours, so we have a lock here on
// an object that the ExecTMReq will also lock, but since we're on
// the same thread, the lock is a no-op.
if (null != internalTransaction && internalTransaction.IsDelegated) {
if (_parser.MARSOn) {
stateObj = _parser.GetSession(this);
mustPutSession = true;
}
else if (internalTransaction.OpenResultsCount == 0) {
Monitor.Enter(stateObj);
mustRelease = true;
if (internalTransaction.OpenResultsCount != 0) {
throw SQL.CannotCompleteDelegatedTransactionWithOpenResults();
}
}
else {
throw SQL.CannotCompleteDelegatedTransactionWithOpenResults();
}
}
// SQLBU #406778 - _parser may be nulled out during TdsExecuteTrannsactionManagerRequest.
// Only use local variable after this call.
_parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel,
ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest);
}
finally {
if (mustPutSession) {
parser.PutSession(stateObj);
}
if (mustRelease) {
Monitor.Exit(stateObj);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
// DISTRIBUTED TRANSACTION METHODS
////////////////////////////////////////////////////////////////////////////////////////
override internal void DelegatedTransactionEnded() {
//
base.DelegatedTransactionEnded();
}
override protected byte[] GetDTCAddress() {
byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser._physicalStateObj);
Debug.Assert(null != dtcAddress, "null dtcAddress?");
return dtcAddress;
}
override protected void PropagateTransactionCookie(byte[] cookie) {
_parser.PropagateDistributedTransaction(cookie, ConnectionOptions.ConnectTimeout, _parser._physicalStateObj);
}
////////////////////////////////////////////////////////////////////////////////////////
// LOGIN-RELATED METHODS
////////////////////////////////////////////////////////////////////////////////////////
private void CompleteLogin(bool enlistOK) {
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
Debug.Assert(SniContext.Snix_Login == Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Snix_Login; actual Value: {0}", Parser._physicalStateObj.SniContext));
_parser._physicalStateObj.SniContext = SniContext.Snix_EnableMars;
_parser.EnableMars(ConnectionOptions.DataSource);
_fConnectionOpen = true; // mark connection as open
// for non-pooled connections, enlist in a distributed transaction
// if present - and user specified to enlist
if(enlistOK && ConnectionOptions.Enlist) {
_parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist;
SysTx.Transaction tx = ADP.GetCurrentTransaction();
Enlist(tx);
}
_parser._physicalStateObj.SniContext=SniContext.Snix_Login;
}
private void Login(long timerExpire, string newPassword) {
// create a new login record
SqlLogin login = new SqlLogin();
// gather all the settings the user set in the connection string or
// properties and do the login
CurrentDatabase = ConnectionOptions.InitialCatalog;
_currentPacketSize = ConnectionOptions.PacketSize;
_currentLanguage = ConnectionOptions.CurrentLanguage;
int timeout = 0;
// If a timeout tick value is specified, compute the timeout based
// upon the amount of time left.
if (Int64.MaxValue != timerExpire) {
long t = ADP.TimerRemainingSeconds(timerExpire);
if ((long)Int32.MaxValue > t) {
timeout = (int)t;
}
}
login.timeout = timeout;
login.userInstance = ConnectionOptions.UserInstance;
login.hostName = ConnectionOptions.ObtainWorkstationId();
login.userName = ConnectionOptions.UserID;
login.password = ConnectionOptions.Password;
login.applicationName = ConnectionOptions.ApplicationName;
login.language = _currentLanguage;
if (!login.userInstance) { // Do not send attachdbfilename or database to SSE primary instance
login.database = CurrentDatabase;;
login.attachDBFilename = ConnectionOptions.AttachDBFilename;
}
login.serverName = ConnectionOptions.DataSource;
login.useReplication = ConnectionOptions.Replication;
login.useSSPI = ConnectionOptions.IntegratedSecurity;
login.packetSize = _currentPacketSize;
login.newPassword = newPassword;
_parser.TdsLogin(login);
}
private void LoginFailure() {
Bid.Trace(" %d#\n", ObjectID);
// If the parser was allocated and we failed, then we must have failed on
// either the Connect or Login, either way we should call Disconnect.
// Disconnect can be called if the connection is already closed - becomes
// no-op, so no issues there.
if (_parser != null) {
_parser.Disconnect();
}
//
}
private void OpenLoginEnlist(SqlConnection owningObject, SqlConnectionString connectionOptions, string newPassword, bool redirectedUserInstance) {
long timerStart = ADP.TimerCurrent();
bool useFailoverPartner; // should we use primary or secondary first
string dataSource = ConnectionOptions.DataSource;
string failoverPartner;
if (null != PoolGroupProviderInfo) {
useFailoverPartner = PoolGroupProviderInfo.UseFailoverPartner;
failoverPartner = PoolGroupProviderInfo.FailoverPartner;
}
else {
// Only ChangePassword or SSE User Instance comes through this code path.
useFailoverPartner = false;
failoverPartner = ConnectionOptions.FailoverPartner;
}
bool hasFailoverPartner = !ADP.IsEmpty(failoverPartner);
// Open the connection and Login
try {
if (hasFailoverPartner) {
LoginWithFailover(
useFailoverPartner,
dataSource,
failoverPartner,
newPassword,
redirectedUserInstance,
owningObject,
connectionOptions,
timerStart);
}
else {
LoginNoFailover(dataSource, newPassword, redirectedUserInstance,
owningObject, connectionOptions, timerStart);
}
}
catch (Exception e) {
//
if (ADP.IsCatchableExceptionType(e)) {
LoginFailure();
}
throw;
}
#if DEBUG
_parser._physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
#endif
}
// Attempt to login to a host that does not have a failover partner
//
// Will repeatedly attempt to connect, but back off between each attempt so as not to clog the network.
// Back off period increases for first few failures: 100ms, 200ms, 400ms, 800ms, then 1000ms for subsequent attempts
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// DEVNOTE: The logic in this method is paralleled by the logic in LoginWithFailover.
// Changes to either one should be examined to see if they need to be reflected in the other
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
private void LoginNoFailover(string host, string newPassword, bool redirectedUserInstance,
SqlConnection owningObject, SqlConnectionString connectionOptions, long timerStart) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, host=%s\n", ObjectID, host);
}
int timeout = ConnectionOptions.ConnectTimeout;
long timerExpire;
int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
ServerInfo serverInfo = new ServerInfo(ConnectionOptions.NetworkLibrary, host);
ResolveExtendedServerName(serverInfo, !redirectedUserInstance, owningObject);
// Timeout of 0 should map to maximum (MDAC 90672). Netlib doesn't do that, so we have to
if (0 == timeout) {
timerExpire = Int64.MaxValue;
}
else {
timerExpire = checked(timerStart + ADP.TimerFromSeconds(timeout));
}
// Only three ways out of this loop:
// 1) Successfully connected
// 2) Parser threw exception while main timer was expired
// 3) Parser threw logon failure-related exception
// 4) Parser threw exception in post-initial connect code,
// such as pre-login handshake or during actual logon. (parser state != Closed)
//
// Of these methods, only #1 exits normally. This preserves the call stack on the exception
// back into the parser for the error cases.
while(true) {
// Re-allocate parser each time to make sure state is known
_parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
try {
//
AttemptOneLogin( serverInfo,
newPassword,
true, // ignore timeout for SniOpen call
timerExpire,
owningObject);
break; // leave the while loop -- we've successfully connected
}
catch (SqlException sqlex) {
if (null == _parser
|| TdsParserState.Closed != _parser.State
|| (TdsEnums.LOGON_FAILED == sqlex.Number) // actual logon failed, i.e. bad password
|| (TdsEnums.PASSWORD_EXPIRED == sqlex.Number) // actual logon failed, i.e. password isExpired
|| ADP.TimerHasExpired(timerExpire)) { // no more time to try again
throw; // Caller will call LoginFailure()
}
// Check sleep interval to make sure we won't exceed the timeout
// Do this in the catch block so we can re-throw the current exception
long remainingMilliseconds = ADP.TimerRemainingMilliseconds(timerExpire);
if (remainingMilliseconds <= sleepInterval) {
throw;
}
//
}
// We only get here when we failed to connect, but are going to re-try
// Switch to failover logic if the server provided a partner
if (null != ServerProvidedFailOverPartner) {
LoginWithFailover(
true, // start by using failover partner, since we already failed to connect to the primary
host,
ServerProvidedFailOverPartner,
newPassword,
redirectedUserInstance,
owningObject,
connectionOptions,
timerStart);
return; // LoginWithFailover successfully connected and handled entire connection setup
}
// Sleep for a bit to prevent clogging the network with requests,
// then update sleep interval for next iteration (max 1 second interval)
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
}
Thread.Sleep(sleepInterval);
sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
}
if (null != PoolGroupProviderInfo) {
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
// partner; save this information in _currentFailoverPartner.
PoolGroupProviderInfo.FailoverCheck(this, false, connectionOptions, ServerProvidedFailOverPartner);
}
CurrentDataSource = host;
}
// Attempt to login to a host that has a failover partner
//
// Connection & timeout sequence is
// First target, timeout = interval * 1
// second target, timeout = interval * 1
// sleep for 100ms
// First target, timeout = interval * 2
// Second target, timeout = interval * 2
// sleep for 200ms
// First Target, timeout = interval * 3
// etc.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// DEVNOTE: The logic in this method is paralleled by the logic in LoginNoFailover.
// Changes to either one should be examined to see if they need to be reflected in the other
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
private void LoginWithFailover(
bool useFailoverHost,
string primaryHost,
string failoverHost,
string newPassword,
bool redirectedUserInstance,
SqlConnection owningObject,
SqlConnectionString connectionOptions,
long timerStart
) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, useFailover=%d{bool}, primary=", ObjectID, useFailoverHost);
Bid.PutStr(primaryHost);
Bid.PutStr(", failover=");
Bid.PutStr(failoverHost);
Bid.PutStr("\n");
}
int timeout = ConnectionOptions.ConnectTimeout;
long timerExpire;
int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
long timeoutUnitInterval;
string protocol = ConnectionOptions.NetworkLibrary;
ServerInfo primaryServerInfo = new ServerInfo(protocol, primaryHost);
ServerInfo failoverServerInfo = new ServerInfo(protocol, failoverHost);
ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, owningObject);
if (null == ServerProvidedFailOverPartner) {// No point in resolving the failover partner when we're going to override it below
// Don't resolve aliases if failover == primary //
ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryHost, owningObject);
}
// Timeout of 0 should map to maximum (MDAC 90672). Netlib doesn't do that, so we have to
if (0 == timeout) {
timerExpire = Int64.MaxValue;
timeoutUnitInterval = checked((long) ADP.FailoverTimeoutStep * ADP.TimerFromSeconds(ADP.DefaultConnectionTimeout));
}
else {
long timerTimeout = ADP.TimerFromSeconds(timeout); // ConnectTimeout is in seconds, we need timer ticks
timerExpire = checked(timerStart + timerTimeout);
timeoutUnitInterval = checked((long) (ADP.FailoverTimeoutStep * timerTimeout));
}
// Initialize loop variables
bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)?
long intervalExpire = checked(timerStart + timeoutUnitInterval);
int attemptNumber = 0;
// Only three ways out of this loop:
// 1) Successfully connected
// 2) Parser threw exception while main timer was expired
// 3) Parser threw logon failure-related exception (LOGON_FAILED, PASSWORD_EXPIRED, etc)
//
// Of these methods, only #1 exits normally. This preserves the call stack on the exception
// back into the parser for the error cases.
while (true) {
// Re-allocate parser each time to make sure state is known
_parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
ServerInfo currentServerInfo;
if (useFailoverHost) {
if (!failoverDemandDone) {
FailoverPermissionDemand();
failoverDemandDone = true;
}
// Primary server may give us a different failover partner than the connection string indicates. Update it
if (null != ServerProvidedFailOverPartner && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, new failover partner=%s\n", ObjectID, ServerProvidedFailOverPartner);
}
failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner);
}
currentServerInfo = failoverServerInfo;
}
else {
currentServerInfo = primaryServerInfo;
}
try {
// Attempt login. Use timerInterval for attempt timeout unless infinite timeout was requested.
AttemptOneLogin(
currentServerInfo,
newPassword,
false, // Use timeout in SniOpen
(0 == timeout) ? timerExpire : intervalExpire,
owningObject);
break; // leave the while loop -- we've successfully connected
}
catch (SqlException sqlex) {
if ((TdsEnums.LOGON_FAILED == sqlex.Number) // actual logon failed, i.e. bad password
|| (TdsEnums.PASSWORD_EXPIRED == sqlex.Number) // actual logon failed, i.e. password isExpired
|| ADP.TimerHasExpired(timerExpire)) { // no more time to try again
throw; // Caller will call LoginFailure()
}
if (IsConnectionDoomed) {
throw;
}
if (1 == attemptNumber % 2) {
// Check sleep interval to make sure we won't exceed the timeout
// Do this in the catch block so we can re-throw the current exception
long remainingMilliseconds = ADP.TimerRemainingMilliseconds(timerExpire);
if (remainingMilliseconds <= sleepInterval) {
throw;
}
}
//
}
// We only get here when we failed to connect, but are going to re-try
// After trying to connect to both servers fails, sleep for a bit to prevent clogging
// the network with requests, then update sleep interval for next iteration (max 1 second interval)
if (1 == attemptNumber % 2) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
}
Thread.Sleep(sleepInterval);
sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
}
// Update timeout interval (but no more than the point where we're supposed to fail: timerExpire)
attemptNumber++;
intervalExpire = checked(ADP.TimerCurrent() + (timeoutUnitInterval * ((attemptNumber / 2) + 1)));
if (intervalExpire > timerExpire) {
intervalExpire = timerExpire;
}
// try again, this time swapping primary/secondary servers
useFailoverHost = !useFailoverHost;
}
// If we get here, connection/login succeeded! Just a few more checks & record-keeping
// if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
if (useFailoverHost && null == ServerProvidedFailOverPartner) {
throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase);
}
if (null != PoolGroupProviderInfo) {
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
// partner; save this information in _currentFailoverPartner.
PoolGroupProviderInfo.FailoverCheck(this, useFailoverHost, connectionOptions, ServerProvidedFailOverPartner);
}
CurrentDataSource = (useFailoverHost ? failoverHost : primaryHost);
}
private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnection owningObject) {
if (serverInfo.ExtendedServerName == null) {
string host = serverInfo.UserServerName;
string protocol = serverInfo.UserProtocol;
if (aliasLookup) { // We skip this for UserInstances...
// Perform registry lookup to see if host is an alias. It will appropriately set host and protocol, if an Alias.
TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
//
if ((null != owningObject) && ((SqlConnectionString)owningObject.UserConnectionOptions).EnforceLocalHost) {
// verify LocalHost for |DataDirectory| usage
SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
}
// else if (null == owningObject) && EnforceLocalHost, then its a PoolCreateRequest and safe to create
}
serverInfo.SetDerivedNames(protocol, host);
}
}
// Common code path for making one attempt to establish a connection and log in to server.
private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, bool ignoreSniOpenTimeout,
long timerExpire, SqlConnection owningObject) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, timout=%d{ticks}, server=", ObjectID, timerExpire - ADP.TimerCurrent());
Bid.PutStr(serverInfo.ExtendedServerName);
Bid.Trace("\n");
}
_parser._physicalStateObj.SniContext = SniContext.Snix_Connect;
_parser.Connect(serverInfo,
this,
ignoreSniOpenTimeout,
timerExpire,
ConnectionOptions.Encrypt,
ConnectionOptions.TrustServerCertificate,
ConnectionOptions.IntegratedSecurity,
owningObject);
_parser._physicalStateObj.SniContext = SniContext.Snix_Login;
this.Login(timerExpire, newPassword);
CompleteLogin(!ConnectionOptions.Pooling);
}
internal void FailoverPermissionDemand() {
if (null != PoolGroupProviderInfo) {
PoolGroupProviderInfo.FailoverPermissionDemand();
}
}
#if WINFSFunctionality
////////////////////////////////////////////////////////////////////////////////////////
// UDT METHODS
////////////////////////////////////////////////////////////////////////////////////////
//helper to create the cache object.
//called from the property accessor, as well as internal functions
private AssemblyCache CreateAssemblyCache() {
AssemblyCache cache;
Debug.Assert(ConnectionOptions != null, "ConnectionOptions");
Debug.Assert(!ADP.IsEmpty(CurrentDataSource), "CurrentDataSource");
if (!_assemblyCacheTable.TryGetValue(CurrentDataSource, out cache)) {
lock(_assemblyCacheLock) {
if (!_assemblyCacheTable.TryGetValue(CurrentDataSource, out cache)) {
cache = new AssemblyCache();
_assemblyCacheTable.Add(CurrentDataSource,cache);
}
}
}
Debug.Assert(cache != null, "Internal Error! AssemblyCache could not be created");
return cache;
}
#endif
////////////////////////////////////////////////////////////////////////////////////////
// PREPARED COMMAND METHODS
////////////////////////////////////////////////////////////////////////////////////////
override internal void AddPreparedCommand(SqlCommand cmd) {
if (_preparedCommands == null)
_preparedCommands = new List(5);
for (int i = 0; i < _preparedCommands.Count; ++i) {
if (!_preparedCommands[i].IsAlive) { // reuse the dead weakreference
_preparedCommands[i].Target = cmd;
return;
}
}
_preparedCommands.Add(new WeakReference(cmd));
}
override internal void ClearPreparedCommands() {
//
// be sure to unprepare all prepared commands
//
if (null != _preparedCommands) {
// note that unpreparing a command will cause the command object to call RemovePreparedCommand
// on this connection.
for (int i = 0; i < _preparedCommands.Count; ++i) {
SqlCommand cmd = _preparedCommands[i].Target as SqlCommand;
if (null != cmd) {
cmd.Unprepare(true);
_preparedCommands[i].Target = null;
}
}
_preparedCommands = null;
}
}
override internal void RemovePreparedCommand(SqlCommand cmd) {
if (_preparedCommands == null || _preparedCommands.Count == 0)
return;
for (int i = 0; i < _preparedCommands.Count; i++)
if (_preparedCommands[i].Target == cmd) {
_preparedCommands[i].Target = null; // don't shrink the list, just keep the reference for reuse
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////
// PARSER CALLBACKS
////////////////////////////////////////////////////////////////////////////////////////
internal void BreakConnection() {
Bid.Trace(" %d#, Breaking connection.\n", ObjectID);
DoomThisConnection(); // Mark connection as unusable, so it will be destroyed
if (null != Connection) {
Connection.Close();
}
}
internal void OnEnvChange(SqlEnvChange rec) {
switch (rec.type) {
case TdsEnums.ENV_DATABASE:
// If connection is not open, store the server value as the original.
if (!_fConnectionOpen)
_originalDatabase = rec.newValue;
CurrentDatabase = rec.newValue;
break;
case TdsEnums.ENV_LANG:
// If connection is not open, store the server value as the original.
if (!_fConnectionOpen)
_originalLanguage = rec.newValue;
_currentLanguage = rec.newValue; //
break;
case TdsEnums.ENV_PACKETSIZE:
_currentPacketSize = Int32.Parse(rec.newValue, CultureInfo.InvariantCulture);
break;
case TdsEnums.ENV_CHARSET:
case TdsEnums.ENV_LOCALEID:
case TdsEnums.ENV_COMPFLAGS:
case TdsEnums.ENV_COLLATION:
case TdsEnums.ENV_BEGINTRAN:
case TdsEnums.ENV_COMMITTRAN:
case TdsEnums.ENV_ROLLBACKTRAN:
case TdsEnums.ENV_ENLISTDTC:
case TdsEnums.ENV_DEFECTDTC:
// only used on parser
break;
case TdsEnums.ENV_LOGSHIPNODE:
_currentFailoverPartner = rec.newValue;
break;
case TdsEnums.ENV_PROMOTETRANSACTION:
PromotedDTCToken = rec.newBinValue;
break;
case TdsEnums.ENV_TRANSACTIONENDED:
break;
case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
case TdsEnums.ENV_SPRESETCONNECTIONACK:
// For now we skip these Yukon only env change notifications
break;
case TdsEnums.ENV_USERINSTANCE:
_instanceName = rec.newValue;
break;
default:
Debug.Assert(false, "Missed token in EnvChange!");
break;
}
}
internal void OnLoginAck(SqlLoginAck rec) {
_loginAck = rec;
//
}
}
internal sealed class ServerInfo {
private string _extendedServerName; // the resolved servername with protocol
private string _resolvedServerName; // the resolved servername only
private string _userProtocol; // the user specified protocol
private string _userServerName; // the user specified servername
internal ServerInfo (string userProtocol, string userServerName) {
_userProtocol = userProtocol;
_userServerName = userServerName;
}
internal string ExtendedServerName {
get {
return _extendedServerName;
}
// setter will go away
set {
_extendedServerName = value;
}
}
internal string ResolvedServerName {
get {
return _resolvedServerName;
}
// setter will go away
set {
_resolvedServerName = value;
}
}
internal string UserProtocol {
get {
return _userProtocol;
}
}
internal string UserServerName {
get {
return _userServerName;
}
}
internal void SetDerivedNames(string protocol, string serverName) {
// The following concatenates the specified netlib network protocol to the host string, if netlib is not null
// and the flag is on. This allows the user to specify the network protocol for the connection - but only
// when using the Dbnetlib dll. If the protocol is not specified, the netlib will
// try all protocols in the order listed in the Client Network Utility. Connect will
// then fail if all protocols fail.
if (!ADP.IsEmpty(protocol)) {
ExtendedServerName = protocol + ":" + serverName;
}
else {
ExtendedServerName = serverName;
}
ResolvedServerName = serverName;
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// [....]
// [....]
//-----------------------------------------------------------------------------
namespace System.Data.SqlClient
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using SysTx = System.Transactions;
sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable {
// CONNECTION AND STATE VARIABLES
private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
private TdsParser _parser;
private SqlLoginAck _loginAck;
// FOR POOLING
private bool _fConnectionOpen = false;
// FOR CONNECTION RESET MANAGEMENT
private bool _fResetConnection;
private string _originalDatabase;
private string _currentFailoverPartner; // only set by ENV change from server
private string _originalLanguage;
private string _currentLanguage;
private int _currentPacketSize;
private int _asyncCommandCount; // number of async Begins minus number of async Ends.
// FOR SSE
private string _instanceName = String.Empty;
// FOR NOTIFICATIONS
private DbConnectionPoolIdentity _identity; // Used to lookup info for notification matching Start().
// OTHER STATE VARIABLES AND REFERENCES
// don't use a SqlCommands collection because this is an internal tracking list. That is, we don't want
// the command to "know" it's in a collection.
private List _preparedCommands; // SQLBU: 446234. Use weakreference to avoid memory leak
#if WINFSFunctionality
//Hash table of UDT caches. Created in CreateAssemblyCache
private static Dictionary _assemblyCacheTable = new Dictionary();
private static object _assemblyCacheLock = new object();
#endif
// although the new password is generally not used it must be passed to the c'tor
// the new Login7 packet will always write out the new password (or a length of zero and no bytes if not present)
//
internal SqlInternalConnectionTds(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, object providerInfo, string newPassword, SqlConnection owningObject, bool redirectedUserInstance) : base(connectionOptions) {
#if DEBUG
try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath
if (null != owningObject) {
owningObject.UserConnectionOptions.DemandPermission();
}
else {
connectionOptions.DemandPermission();
}
}
catch(System.Security.SecurityException) {
System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
throw;
}
#endif
if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) {
throw SQL.UserInstanceNotAvailableInProc();
}
_identity = identity;
Debug.Assert(null!=newPassword, "newPassword argument must not be null");
_poolGroupProviderInfo = (SqlConnectionPoolGroupProviderInfo)providerInfo;
_fResetConnection = connectionOptions.ConnectionReset;
if (_fResetConnection) {
_originalDatabase = connectionOptions.InitialCatalog;
_originalLanguage = connectionOptions.CurrentLanguage;
}
RuntimeHelpers.PrepareConstrainedRegions();
try {
#if DEBUG
object initialReliabilitySlotValue = Thread.GetData(TdsParser.ReliabilitySlot);
RuntimeHelpers.PrepareConstrainedRegions();
try {
Thread.SetData(TdsParser.ReliabilitySlot, true);
#endif //DEBUG
OpenLoginEnlist(owningObject, connectionOptions, newPassword, redirectedUserInstance);
#if DEBUG
}
finally {
Thread.SetData(TdsParser.ReliabilitySlot, initialReliabilitySlotValue);
}
#endif //DEBUG
}
catch (System.OutOfMemoryException) {
DoomThisConnection();
throw;
}
catch (System.StackOverflowException) {
DoomThisConnection();
throw;
}
catch (System.Threading.ThreadAbortException) {
DoomThisConnection();
throw;
}
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, constructed new TDS internal connection\n", ObjectID);
}
}
#if WINFSFunctionality
internal AssemblyCache AssemblyCache{
get{
return CreateAssemblyCache();
}
}
#endif
override internal SqlInternalTransaction CurrentTransaction {
get {
return _parser.CurrentTransaction;
}
}
override internal SqlInternalTransaction AvailableInternalTransaction {
get {
return _parser._fResetConnection ? null : CurrentTransaction;
}
}
override internal SqlInternalTransaction PendingTransaction {
get {
return _parser.PendingTransaction;
}
}
internal DbConnectionPoolIdentity Identity {
get {
return _identity;
}
}
internal string InstanceName {
get {
return _instanceName;
}
}
override internal bool IsLockedForBulkCopy {
get {
return (!Parser.MARSOn && Parser._physicalStateObj.BcpLock);
}
}
override protected internal bool IsNonPoolableTransactionRoot {
get {
return IsTransactionRoot && (!IsKatmaiOrNewer || null == Pool);
}
}
override internal bool IsShiloh {
get {
return _loginAck.isVersion8;
}
}
override internal bool IsYukonOrNewer {
get {
return _parser.IsYukonOrNewer;
}
}
override internal bool IsKatmaiOrNewer {
get {
return _parser.IsKatmaiOrNewer;
}
}
#if WINFSFunctionality
override internal bool IsWinFS {
get {
return _parser.IsWinFS;
}
}
#endif
internal int PacketSize {
get {
return _currentPacketSize;
}
}
internal TdsParser Parser {
get {
return _parser;
}
}
internal string ServerProvidedFailOverPartner {
get {
return _currentFailoverPartner;
}
}
internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo {
get {
return _poolGroupProviderInfo;
}
}
override protected bool ReadyToPrepareTransaction {
get {
//
bool result = (null == FindLiveReader(null)); // can't prepare with a live data reader...
return result;
}
}
override public string ServerVersion {
get {
return(String.Format((IFormatProvider)null, "{0:00}.{1:00}.{2:0000}", _loginAck.majorVersion,
(short) _loginAck.minorVersion, _loginAck.buildNum));
}
}
////////////////////////////////////////////////////////////////////////////////////////
// GENERAL METHODS
////////////////////////////////////////////////////////////////////////////////////////
override protected void ChangeDatabaseInternal(string database) {
// MDAC 73598 - add brackets around database
database = SqlConnection.FixupDatabaseTransactionName(database);
_parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj);
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
}
override public void Dispose() {
if (Bid.AdvancedOn) {
Bid.Trace(" %d# disposing\n", base.ObjectID);
}
try {
TdsParser parser = Interlocked.Exchange(ref _parser, null); // guard against multiple concurrent dispose calls -- Delegated Transactions might cause this.
Debug.Assert(parser != null && _fConnectionOpen || parser == null && !_fConnectionOpen, "Unexpected state on dispose");
if (null != parser) {
parser.Disconnect();
}
}
finally { //
// close will always close, even if exception is thrown
// remember to null out any object references
_loginAck = null;
_fConnectionOpen = false; // mark internal connection as closed
}
base.Dispose();
}
override internal void ValidateConnectionForExecute(SqlCommand command) {
SqlDataReader reader = null;
if (Parser.MARSOn) {
if (null != command) { // command can't have datareader already associated with it
reader = FindLiveReader(command);
}
}
else { // single datareader per connection
reader = FindLiveReader(null);
}
if (null != reader) {
// if MARS is on, then a datareader associated with the command exists
// or if MARS is off, then a datareader exists
throw ADP.OpenReaderExists(); // MDAC 66411
}
else if (!Parser.MARSOn && Parser._physicalStateObj._pendingData) {
Parser._physicalStateObj.CleanWire();
}
Debug.Assert(!Parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!");
Parser.RollbackOrphanedAPITransactions();
}
////////////////////////////////////////////////////////////////////////////////////////
// POOLING METHODS
////////////////////////////////////////////////////////////////////////////////////////
override protected void Activate(SysTx.Transaction transaction) {
FailoverPermissionDemand(); // Demand for unspecified failover pooled connections
// When we're required to automatically enlist in transactions and
// there is one we enlist in it. On the other hand, if there isn't a
// transaction and we are currently enlisted in one, then we
// unenlist from it.
//
// Regardless of whether we're required to automatically enlist,
// when there is not a current transaction, we cannot leave the
// connection enlisted in a transaction.
if (null != transaction){
if (ConnectionOptions.Enlist) {
Enlist(transaction);
}
}
else {
Enlist(null);
}
}
override protected void InternalDeactivate() {
// When we're deactivated, the user must have called End on all
// the async commands, or we don't know that we're in a state that
// we can recover from. We doom the connection in this case, to
// prevent odd cases when we go to the wire.
if (0 != _asyncCommandCount) {
DoomThisConnection();
}
// If we're deactivating with a delegated transaction, we
// should not be cleaning up the parser just yet, that will
// cause our transaction to be rolled back and the connection
// to be reset. We'll get called again once the delegated
// transaction is completed and we can do it all then.
if (!IsNonPoolableTransactionRoot) {
Debug.Assert(null != _parser, "Deactivating a disposed connection?");
_parser.Deactivate(IsConnectionDoomed);
if (!IsConnectionDoomed) {
ResetConnection();
}
}
}
private void ResetConnection() {
// For implicit pooled connections, if connection reset behavior is specified,
// reset the database and language properties back to default. It is important
// to do this on activate so that the hashtable is correct before SqlConnection
// obtains a clone.
Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction.");
Debug.Assert(!_parser._physicalStateObj._pendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data.");
if (_fResetConnection) {
// Ensure we are either going against shiloh, or we are not enlisted in a
// distributed transaction - otherwise don't reset!
if (IsShiloh) {
// Prepare the parser for the connection reset - the next time a trip
// to the server is made.
_parser.PrepareResetConnection(IsTransactionRoot && !IsNonPoolableTransactionRoot);
}
else if (!IsEnlistedInTransaction) {
// If not Shiloh, we are going against Sphinx. On Sphinx, we
// may only reset if not enlisted in a distributed transaction.
try {
// execute sp
_parser.TdsExecuteSQLBatch("sp_reset_connection", 30, null, _parser._physicalStateObj);
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
}
catch (Exception e) {
//
if (!ADP.IsCatchableExceptionType(e)) {
throw;
}
DoomThisConnection();
ADP.TraceExceptionWithoutRethrow(e);
}
}
// Reset hashtable values, since calling reset will not send us env_changes.
CurrentDatabase = _originalDatabase;
_currentLanguage = _originalLanguage;
}
}
internal void DecrementAsyncCount() {
Interlocked.Decrement(ref _asyncCommandCount);
}
internal void IncrementAsyncCount() {
Interlocked.Increment(ref _asyncCommandCount);
}
////////////////////////////////////////////////////////////////////////////////////////
// LOCAL TRANSACTION METHODS
////////////////////////////////////////////////////////////////////////////////////////
override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) {
TdsParser parser = Parser;
if (null != parser) {
parser.DisconnectTransaction(internalTransaction);
}
}
internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso) {
ExecuteTransaction(transactionRequest, name, iso, null, false);
}
override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) {
if (IsConnectionDoomed) { // doomed means we can't do anything else...
if (transactionRequest == TransactionRequest.Rollback
|| transactionRequest == TransactionRequest.IfRollback) {
return;
}
throw SQL.ConnectionDoomed();
}
if (transactionRequest == TransactionRequest.Commit
|| transactionRequest == TransactionRequest.Rollback
|| transactionRequest == TransactionRequest.IfRollback) {
if (!Parser.MARSOn && Parser._physicalStateObj.BcpLock) {
throw SQL.ConnectionLockedForBcpEvent();
}
}
string transactionName = (null == name) ? String.Empty : name;
if (!_parser.IsYukonOrNewer) {
ExecuteTransactionPreYukon(transactionRequest, transactionName, iso, internalTransaction);
}
else {
ExecuteTransactionYukon(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest);
}
}
internal void ExecuteTransactionPreYukon(TransactionRequest transactionRequest, string transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction) {
StringBuilder sqlBatch = new StringBuilder();
switch (iso) {
case IsolationLevel.Unspecified:
break;
case IsolationLevel.ReadCommitted:
sqlBatch.Append(TdsEnums.TRANS_READ_COMMITTED);
sqlBatch.Append(";");
break;
case IsolationLevel.ReadUncommitted:
sqlBatch.Append(TdsEnums.TRANS_READ_UNCOMMITTED);
sqlBatch.Append(";");
break;
case IsolationLevel.RepeatableRead:
sqlBatch.Append(TdsEnums.TRANS_REPEATABLE_READ);
sqlBatch.Append(";");
break;
case IsolationLevel.Serializable:
sqlBatch.Append(TdsEnums.TRANS_SERIALIZABLE);
sqlBatch.Append(";");
break;
case IsolationLevel.Snapshot:
throw SQL.SnapshotNotSupported(IsolationLevel.Snapshot);
case IsolationLevel.Chaos:
throw SQL.NotSupportedIsolationLevel(iso);
default:
throw ADP.InvalidIsolationLevel(iso);
}
if (!ADP.IsEmpty(transactionName)) {
transactionName = " " + SqlConnection.FixupDatabaseTransactionName(transactionName);
}
switch (transactionRequest) {
case TransactionRequest.Begin:
sqlBatch.Append(TdsEnums.TRANS_BEGIN);
sqlBatch.Append(transactionName);
break;
case TransactionRequest.Promote:
Debug.Assert(false, "Promote called with transaction name or on pre-Yukon!");
break;
case TransactionRequest.Commit:
sqlBatch.Append(TdsEnums.TRANS_COMMIT);
sqlBatch.Append(transactionName);
break;
case TransactionRequest.Rollback:
sqlBatch.Append(TdsEnums.TRANS_ROLLBACK);
sqlBatch.Append(transactionName);
break;
case TransactionRequest.IfRollback:
sqlBatch.Append(TdsEnums.TRANS_IF_ROLLBACK);
sqlBatch.Append(transactionName);
break;
case TransactionRequest.Save:
sqlBatch.Append(TdsEnums.TRANS_SAVE);
sqlBatch.Append(transactionName);
break;
default:
Debug.Assert(false, "Unknown transaction type");
break;
}
_parser.TdsExecuteSQLBatch(sqlBatch.ToString(), ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj);
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
// Prior to Yukon, we didn't have any transaction tokens to manage,
// or any feedback to know when one was created, so we just presume
// that successful execution of the request caused the transaction
// to be created, and we set that on the parser.
if (TransactionRequest.Begin == transactionRequest) {
Debug.Assert(null != internalTransaction, "Begin Transaction request without internal transaction");
_parser.CurrentTransaction = internalTransaction;
}
}
internal void ExecuteTransactionYukon(TransactionRequest transactionRequest, string transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) {
TdsEnums.TransactionManagerRequestType requestType = TdsEnums.TransactionManagerRequestType.Begin;
TdsEnums.TransactionManagerIsolationLevel isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
switch (iso) {
case IsolationLevel.Unspecified:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.Unspecified;
break;
case IsolationLevel.ReadCommitted:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
break;
case IsolationLevel.ReadUncommitted:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadUncommitted;
break;
case IsolationLevel.RepeatableRead:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.RepeatableRead;
break;
case IsolationLevel.Serializable:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.Serializable;
break;
case IsolationLevel.Snapshot:
isoLevel = TdsEnums.TransactionManagerIsolationLevel.Snapshot;
break;
case IsolationLevel.Chaos:
throw SQL.NotSupportedIsolationLevel(iso);
default:
throw ADP.InvalidIsolationLevel(iso);
}
TdsParserStateObject stateObj = _parser._physicalStateObj;
TdsParser parser = _parser;
bool mustPutSession = false;
bool mustRelease = false;
try {
switch (transactionRequest) {
case TransactionRequest.Begin:
requestType = TdsEnums.TransactionManagerRequestType.Begin;
break;
case TransactionRequest.Promote:
requestType = TdsEnums.TransactionManagerRequestType.Promote;
break;
case TransactionRequest.Commit:
requestType = TdsEnums.TransactionManagerRequestType.Commit;
break;
case TransactionRequest.IfRollback:
// Map IfRollback to Rollback since with Yukon and beyond we should never need
// the if since the server will inform us when transactions have completed
// as a result of an error on the server.
case TransactionRequest.Rollback:
requestType = TdsEnums.TransactionManagerRequestType.Rollback;
break;
case TransactionRequest.Save:
requestType = TdsEnums.TransactionManagerRequestType.Save;
break;
default:
Debug.Assert(false, "Unknown transaction type");
break;
}
// SQLBUDT #20010853 - Promote, Commit and Rollback requests for
// delegated transactions often happen while there is an open result
// set, so we need to handle them by using a different MARS session,
// otherwise we'll write on the physical state objects while someone
// else is using it. When we don't have MARS enabled, we need to
// lock the physical state object to syncronize it's use at least
// until we increment the open results count. Once it's been
// incremented the delegated transaction requests will fail, so they
// won't stomp on anything.
//
// We need to keep this lock through the duration of the TM reqeuest
// so that we won't hijack a different request's data stream and a
// different request won't hijack ours, so we have a lock here on
// an object that the ExecTMReq will also lock, but since we're on
// the same thread, the lock is a no-op.
if (null != internalTransaction && internalTransaction.IsDelegated) {
if (_parser.MARSOn) {
stateObj = _parser.GetSession(this);
mustPutSession = true;
}
else if (internalTransaction.OpenResultsCount == 0) {
Monitor.Enter(stateObj);
mustRelease = true;
if (internalTransaction.OpenResultsCount != 0) {
throw SQL.CannotCompleteDelegatedTransactionWithOpenResults();
}
}
else {
throw SQL.CannotCompleteDelegatedTransactionWithOpenResults();
}
}
// SQLBU #406778 - _parser may be nulled out during TdsExecuteTrannsactionManagerRequest.
// Only use local variable after this call.
_parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel,
ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest);
}
finally {
if (mustPutSession) {
parser.PutSession(stateObj);
}
if (mustRelease) {
Monitor.Exit(stateObj);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
// DISTRIBUTED TRANSACTION METHODS
////////////////////////////////////////////////////////////////////////////////////////
override internal void DelegatedTransactionEnded() {
//
base.DelegatedTransactionEnded();
}
override protected byte[] GetDTCAddress() {
byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser._physicalStateObj);
Debug.Assert(null != dtcAddress, "null dtcAddress?");
return dtcAddress;
}
override protected void PropagateTransactionCookie(byte[] cookie) {
_parser.PropagateDistributedTransaction(cookie, ConnectionOptions.ConnectTimeout, _parser._physicalStateObj);
}
////////////////////////////////////////////////////////////////////////////////////////
// LOGIN-RELATED METHODS
////////////////////////////////////////////////////////////////////////////////////////
private void CompleteLogin(bool enlistOK) {
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
Debug.Assert(SniContext.Snix_Login == Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Snix_Login; actual Value: {0}", Parser._physicalStateObj.SniContext));
_parser._physicalStateObj.SniContext = SniContext.Snix_EnableMars;
_parser.EnableMars(ConnectionOptions.DataSource);
_fConnectionOpen = true; // mark connection as open
// for non-pooled connections, enlist in a distributed transaction
// if present - and user specified to enlist
if(enlistOK && ConnectionOptions.Enlist) {
_parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist;
SysTx.Transaction tx = ADP.GetCurrentTransaction();
Enlist(tx);
}
_parser._physicalStateObj.SniContext=SniContext.Snix_Login;
}
private void Login(long timerExpire, string newPassword) {
// create a new login record
SqlLogin login = new SqlLogin();
// gather all the settings the user set in the connection string or
// properties and do the login
CurrentDatabase = ConnectionOptions.InitialCatalog;
_currentPacketSize = ConnectionOptions.PacketSize;
_currentLanguage = ConnectionOptions.CurrentLanguage;
int timeout = 0;
// If a timeout tick value is specified, compute the timeout based
// upon the amount of time left.
if (Int64.MaxValue != timerExpire) {
long t = ADP.TimerRemainingSeconds(timerExpire);
if ((long)Int32.MaxValue > t) {
timeout = (int)t;
}
}
login.timeout = timeout;
login.userInstance = ConnectionOptions.UserInstance;
login.hostName = ConnectionOptions.ObtainWorkstationId();
login.userName = ConnectionOptions.UserID;
login.password = ConnectionOptions.Password;
login.applicationName = ConnectionOptions.ApplicationName;
login.language = _currentLanguage;
if (!login.userInstance) { // Do not send attachdbfilename or database to SSE primary instance
login.database = CurrentDatabase;;
login.attachDBFilename = ConnectionOptions.AttachDBFilename;
}
login.serverName = ConnectionOptions.DataSource;
login.useReplication = ConnectionOptions.Replication;
login.useSSPI = ConnectionOptions.IntegratedSecurity;
login.packetSize = _currentPacketSize;
login.newPassword = newPassword;
_parser.TdsLogin(login);
}
private void LoginFailure() {
Bid.Trace(" %d#\n", ObjectID);
// If the parser was allocated and we failed, then we must have failed on
// either the Connect or Login, either way we should call Disconnect.
// Disconnect can be called if the connection is already closed - becomes
// no-op, so no issues there.
if (_parser != null) {
_parser.Disconnect();
}
//
}
private void OpenLoginEnlist(SqlConnection owningObject, SqlConnectionString connectionOptions, string newPassword, bool redirectedUserInstance) {
long timerStart = ADP.TimerCurrent();
bool useFailoverPartner; // should we use primary or secondary first
string dataSource = ConnectionOptions.DataSource;
string failoverPartner;
if (null != PoolGroupProviderInfo) {
useFailoverPartner = PoolGroupProviderInfo.UseFailoverPartner;
failoverPartner = PoolGroupProviderInfo.FailoverPartner;
}
else {
// Only ChangePassword or SSE User Instance comes through this code path.
useFailoverPartner = false;
failoverPartner = ConnectionOptions.FailoverPartner;
}
bool hasFailoverPartner = !ADP.IsEmpty(failoverPartner);
// Open the connection and Login
try {
if (hasFailoverPartner) {
LoginWithFailover(
useFailoverPartner,
dataSource,
failoverPartner,
newPassword,
redirectedUserInstance,
owningObject,
connectionOptions,
timerStart);
}
else {
LoginNoFailover(dataSource, newPassword, redirectedUserInstance,
owningObject, connectionOptions, timerStart);
}
}
catch (Exception e) {
//
if (ADP.IsCatchableExceptionType(e)) {
LoginFailure();
}
throw;
}
#if DEBUG
_parser._physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
#endif
}
// Attempt to login to a host that does not have a failover partner
//
// Will repeatedly attempt to connect, but back off between each attempt so as not to clog the network.
// Back off period increases for first few failures: 100ms, 200ms, 400ms, 800ms, then 1000ms for subsequent attempts
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// DEVNOTE: The logic in this method is paralleled by the logic in LoginWithFailover.
// Changes to either one should be examined to see if they need to be reflected in the other
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
private void LoginNoFailover(string host, string newPassword, bool redirectedUserInstance,
SqlConnection owningObject, SqlConnectionString connectionOptions, long timerStart) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, host=%s\n", ObjectID, host);
}
int timeout = ConnectionOptions.ConnectTimeout;
long timerExpire;
int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
ServerInfo serverInfo = new ServerInfo(ConnectionOptions.NetworkLibrary, host);
ResolveExtendedServerName(serverInfo, !redirectedUserInstance, owningObject);
// Timeout of 0 should map to maximum (MDAC 90672). Netlib doesn't do that, so we have to
if (0 == timeout) {
timerExpire = Int64.MaxValue;
}
else {
timerExpire = checked(timerStart + ADP.TimerFromSeconds(timeout));
}
// Only three ways out of this loop:
// 1) Successfully connected
// 2) Parser threw exception while main timer was expired
// 3) Parser threw logon failure-related exception
// 4) Parser threw exception in post-initial connect code,
// such as pre-login handshake or during actual logon. (parser state != Closed)
//
// Of these methods, only #1 exits normally. This preserves the call stack on the exception
// back into the parser for the error cases.
while(true) {
// Re-allocate parser each time to make sure state is known
_parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
try {
//
AttemptOneLogin( serverInfo,
newPassword,
true, // ignore timeout for SniOpen call
timerExpire,
owningObject);
break; // leave the while loop -- we've successfully connected
}
catch (SqlException sqlex) {
if (null == _parser
|| TdsParserState.Closed != _parser.State
|| (TdsEnums.LOGON_FAILED == sqlex.Number) // actual logon failed, i.e. bad password
|| (TdsEnums.PASSWORD_EXPIRED == sqlex.Number) // actual logon failed, i.e. password isExpired
|| ADP.TimerHasExpired(timerExpire)) { // no more time to try again
throw; // Caller will call LoginFailure()
}
// Check sleep interval to make sure we won't exceed the timeout
// Do this in the catch block so we can re-throw the current exception
long remainingMilliseconds = ADP.TimerRemainingMilliseconds(timerExpire);
if (remainingMilliseconds <= sleepInterval) {
throw;
}
//
}
// We only get here when we failed to connect, but are going to re-try
// Switch to failover logic if the server provided a partner
if (null != ServerProvidedFailOverPartner) {
LoginWithFailover(
true, // start by using failover partner, since we already failed to connect to the primary
host,
ServerProvidedFailOverPartner,
newPassword,
redirectedUserInstance,
owningObject,
connectionOptions,
timerStart);
return; // LoginWithFailover successfully connected and handled entire connection setup
}
// Sleep for a bit to prevent clogging the network with requests,
// then update sleep interval for next iteration (max 1 second interval)
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
}
Thread.Sleep(sleepInterval);
sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
}
if (null != PoolGroupProviderInfo) {
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
// partner; save this information in _currentFailoverPartner.
PoolGroupProviderInfo.FailoverCheck(this, false, connectionOptions, ServerProvidedFailOverPartner);
}
CurrentDataSource = host;
}
// Attempt to login to a host that has a failover partner
//
// Connection & timeout sequence is
// First target, timeout = interval * 1
// second target, timeout = interval * 1
// sleep for 100ms
// First target, timeout = interval * 2
// Second target, timeout = interval * 2
// sleep for 200ms
// First Target, timeout = interval * 3
// etc.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// DEVNOTE: The logic in this method is paralleled by the logic in LoginNoFailover.
// Changes to either one should be examined to see if they need to be reflected in the other
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
private void LoginWithFailover(
bool useFailoverHost,
string primaryHost,
string failoverHost,
string newPassword,
bool redirectedUserInstance,
SqlConnection owningObject,
SqlConnectionString connectionOptions,
long timerStart
) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, useFailover=%d{bool}, primary=", ObjectID, useFailoverHost);
Bid.PutStr(primaryHost);
Bid.PutStr(", failover=");
Bid.PutStr(failoverHost);
Bid.PutStr("\n");
}
int timeout = ConnectionOptions.ConnectTimeout;
long timerExpire;
int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
long timeoutUnitInterval;
string protocol = ConnectionOptions.NetworkLibrary;
ServerInfo primaryServerInfo = new ServerInfo(protocol, primaryHost);
ServerInfo failoverServerInfo = new ServerInfo(protocol, failoverHost);
ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, owningObject);
if (null == ServerProvidedFailOverPartner) {// No point in resolving the failover partner when we're going to override it below
// Don't resolve aliases if failover == primary //
ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryHost, owningObject);
}
// Timeout of 0 should map to maximum (MDAC 90672). Netlib doesn't do that, so we have to
if (0 == timeout) {
timerExpire = Int64.MaxValue;
timeoutUnitInterval = checked((long) ADP.FailoverTimeoutStep * ADP.TimerFromSeconds(ADP.DefaultConnectionTimeout));
}
else {
long timerTimeout = ADP.TimerFromSeconds(timeout); // ConnectTimeout is in seconds, we need timer ticks
timerExpire = checked(timerStart + timerTimeout);
timeoutUnitInterval = checked((long) (ADP.FailoverTimeoutStep * timerTimeout));
}
// Initialize loop variables
bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)?
long intervalExpire = checked(timerStart + timeoutUnitInterval);
int attemptNumber = 0;
// Only three ways out of this loop:
// 1) Successfully connected
// 2) Parser threw exception while main timer was expired
// 3) Parser threw logon failure-related exception (LOGON_FAILED, PASSWORD_EXPIRED, etc)
//
// Of these methods, only #1 exits normally. This preserves the call stack on the exception
// back into the parser for the error cases.
while (true) {
// Re-allocate parser each time to make sure state is known
_parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
ServerInfo currentServerInfo;
if (useFailoverHost) {
if (!failoverDemandDone) {
FailoverPermissionDemand();
failoverDemandDone = true;
}
// Primary server may give us a different failover partner than the connection string indicates. Update it
if (null != ServerProvidedFailOverPartner && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, new failover partner=%s\n", ObjectID, ServerProvidedFailOverPartner);
}
failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner);
}
currentServerInfo = failoverServerInfo;
}
else {
currentServerInfo = primaryServerInfo;
}
try {
// Attempt login. Use timerInterval for attempt timeout unless infinite timeout was requested.
AttemptOneLogin(
currentServerInfo,
newPassword,
false, // Use timeout in SniOpen
(0 == timeout) ? timerExpire : intervalExpire,
owningObject);
break; // leave the while loop -- we've successfully connected
}
catch (SqlException sqlex) {
if ((TdsEnums.LOGON_FAILED == sqlex.Number) // actual logon failed, i.e. bad password
|| (TdsEnums.PASSWORD_EXPIRED == sqlex.Number) // actual logon failed, i.e. password isExpired
|| ADP.TimerHasExpired(timerExpire)) { // no more time to try again
throw; // Caller will call LoginFailure()
}
if (IsConnectionDoomed) {
throw;
}
if (1 == attemptNumber % 2) {
// Check sleep interval to make sure we won't exceed the timeout
// Do this in the catch block so we can re-throw the current exception
long remainingMilliseconds = ADP.TimerRemainingMilliseconds(timerExpire);
if (remainingMilliseconds <= sleepInterval) {
throw;
}
}
//
}
// We only get here when we failed to connect, but are going to re-try
// After trying to connect to both servers fails, sleep for a bit to prevent clogging
// the network with requests, then update sleep interval for next iteration (max 1 second interval)
if (1 == attemptNumber % 2) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
}
Thread.Sleep(sleepInterval);
sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
}
// Update timeout interval (but no more than the point where we're supposed to fail: timerExpire)
attemptNumber++;
intervalExpire = checked(ADP.TimerCurrent() + (timeoutUnitInterval * ((attemptNumber / 2) + 1)));
if (intervalExpire > timerExpire) {
intervalExpire = timerExpire;
}
// try again, this time swapping primary/secondary servers
useFailoverHost = !useFailoverHost;
}
// If we get here, connection/login succeeded! Just a few more checks & record-keeping
// if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
if (useFailoverHost && null == ServerProvidedFailOverPartner) {
throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase);
}
if (null != PoolGroupProviderInfo) {
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
// partner; save this information in _currentFailoverPartner.
PoolGroupProviderInfo.FailoverCheck(this, useFailoverHost, connectionOptions, ServerProvidedFailOverPartner);
}
CurrentDataSource = (useFailoverHost ? failoverHost : primaryHost);
}
private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnection owningObject) {
if (serverInfo.ExtendedServerName == null) {
string host = serverInfo.UserServerName;
string protocol = serverInfo.UserProtocol;
if (aliasLookup) { // We skip this for UserInstances...
// Perform registry lookup to see if host is an alias. It will appropriately set host and protocol, if an Alias.
TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
//
if ((null != owningObject) && ((SqlConnectionString)owningObject.UserConnectionOptions).EnforceLocalHost) {
// verify LocalHost for |DataDirectory| usage
SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
}
// else if (null == owningObject) && EnforceLocalHost, then its a PoolCreateRequest and safe to create
}
serverInfo.SetDerivedNames(protocol, host);
}
}
// Common code path for making one attempt to establish a connection and log in to server.
private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, bool ignoreSniOpenTimeout,
long timerExpire, SqlConnection owningObject) {
if (Bid.AdvancedOn) {
Bid.Trace(" %d#, timout=%d{ticks}, server=", ObjectID, timerExpire - ADP.TimerCurrent());
Bid.PutStr(serverInfo.ExtendedServerName);
Bid.Trace("\n");
}
_parser._physicalStateObj.SniContext = SniContext.Snix_Connect;
_parser.Connect(serverInfo,
this,
ignoreSniOpenTimeout,
timerExpire,
ConnectionOptions.Encrypt,
ConnectionOptions.TrustServerCertificate,
ConnectionOptions.IntegratedSecurity,
owningObject);
_parser._physicalStateObj.SniContext = SniContext.Snix_Login;
this.Login(timerExpire, newPassword);
CompleteLogin(!ConnectionOptions.Pooling);
}
internal void FailoverPermissionDemand() {
if (null != PoolGroupProviderInfo) {
PoolGroupProviderInfo.FailoverPermissionDemand();
}
}
#if WINFSFunctionality
////////////////////////////////////////////////////////////////////////////////////////
// UDT METHODS
////////////////////////////////////////////////////////////////////////////////////////
//helper to create the cache object.
//called from the property accessor, as well as internal functions
private AssemblyCache CreateAssemblyCache() {
AssemblyCache cache;
Debug.Assert(ConnectionOptions != null, "ConnectionOptions");
Debug.Assert(!ADP.IsEmpty(CurrentDataSource), "CurrentDataSource");
if (!_assemblyCacheTable.TryGetValue(CurrentDataSource, out cache)) {
lock(_assemblyCacheLock) {
if (!_assemblyCacheTable.TryGetValue(CurrentDataSource, out cache)) {
cache = new AssemblyCache();
_assemblyCacheTable.Add(CurrentDataSource,cache);
}
}
}
Debug.Assert(cache != null, "Internal Error! AssemblyCache could not be created");
return cache;
}
#endif
////////////////////////////////////////////////////////////////////////////////////////
// PREPARED COMMAND METHODS
////////////////////////////////////////////////////////////////////////////////////////
override internal void AddPreparedCommand(SqlCommand cmd) {
if (_preparedCommands == null)
_preparedCommands = new List(5);
for (int i = 0; i < _preparedCommands.Count; ++i) {
if (!_preparedCommands[i].IsAlive) { // reuse the dead weakreference
_preparedCommands[i].Target = cmd;
return;
}
}
_preparedCommands.Add(new WeakReference(cmd));
}
override internal void ClearPreparedCommands() {
//
// be sure to unprepare all prepared commands
//
if (null != _preparedCommands) {
// note that unpreparing a command will cause the command object to call RemovePreparedCommand
// on this connection.
for (int i = 0; i < _preparedCommands.Count; ++i) {
SqlCommand cmd = _preparedCommands[i].Target as SqlCommand;
if (null != cmd) {
cmd.Unprepare(true);
_preparedCommands[i].Target = null;
}
}
_preparedCommands = null;
}
}
override internal void RemovePreparedCommand(SqlCommand cmd) {
if (_preparedCommands == null || _preparedCommands.Count == 0)
return;
for (int i = 0; i < _preparedCommands.Count; i++)
if (_preparedCommands[i].Target == cmd) {
_preparedCommands[i].Target = null; // don't shrink the list, just keep the reference for reuse
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////
// PARSER CALLBACKS
////////////////////////////////////////////////////////////////////////////////////////
internal void BreakConnection() {
Bid.Trace(" %d#, Breaking connection.\n", ObjectID);
DoomThisConnection(); // Mark connection as unusable, so it will be destroyed
if (null != Connection) {
Connection.Close();
}
}
internal void OnEnvChange(SqlEnvChange rec) {
switch (rec.type) {
case TdsEnums.ENV_DATABASE:
// If connection is not open, store the server value as the original.
if (!_fConnectionOpen)
_originalDatabase = rec.newValue;
CurrentDatabase = rec.newValue;
break;
case TdsEnums.ENV_LANG:
// If connection is not open, store the server value as the original.
if (!_fConnectionOpen)
_originalLanguage = rec.newValue;
_currentLanguage = rec.newValue; //
break;
case TdsEnums.ENV_PACKETSIZE:
_currentPacketSize = Int32.Parse(rec.newValue, CultureInfo.InvariantCulture);
break;
case TdsEnums.ENV_CHARSET:
case TdsEnums.ENV_LOCALEID:
case TdsEnums.ENV_COMPFLAGS:
case TdsEnums.ENV_COLLATION:
case TdsEnums.ENV_BEGINTRAN:
case TdsEnums.ENV_COMMITTRAN:
case TdsEnums.ENV_ROLLBACKTRAN:
case TdsEnums.ENV_ENLISTDTC:
case TdsEnums.ENV_DEFECTDTC:
// only used on parser
break;
case TdsEnums.ENV_LOGSHIPNODE:
_currentFailoverPartner = rec.newValue;
break;
case TdsEnums.ENV_PROMOTETRANSACTION:
PromotedDTCToken = rec.newBinValue;
break;
case TdsEnums.ENV_TRANSACTIONENDED:
break;
case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
case TdsEnums.ENV_SPRESETCONNECTIONACK:
// For now we skip these Yukon only env change notifications
break;
case TdsEnums.ENV_USERINSTANCE:
_instanceName = rec.newValue;
break;
default:
Debug.Assert(false, "Missed token in EnvChange!");
break;
}
}
internal void OnLoginAck(SqlLoginAck rec) {
_loginAck = rec;
//
}
}
internal sealed class ServerInfo {
private string _extendedServerName; // the resolved servername with protocol
private string _resolvedServerName; // the resolved servername only
private string _userProtocol; // the user specified protocol
private string _userServerName; // the user specified servername
internal ServerInfo (string userProtocol, string userServerName) {
_userProtocol = userProtocol;
_userServerName = userServerName;
}
internal string ExtendedServerName {
get {
return _extendedServerName;
}
// setter will go away
set {
_extendedServerName = value;
}
}
internal string ResolvedServerName {
get {
return _resolvedServerName;
}
// setter will go away
set {
_resolvedServerName = value;
}
}
internal string UserProtocol {
get {
return _userProtocol;
}
}
internal string UserServerName {
get {
return _userServerName;
}
}
internal void SetDerivedNames(string protocol, string serverName) {
// The following concatenates the specified netlib network protocol to the host string, if netlib is not null
// and the flag is on. This allows the user to specify the network protocol for the connection - but only
// when using the Dbnetlib dll. If the protocol is not specified, the netlib will
// try all protocols in the order listed in the Client Network Utility. Connect will
// then fail if all protocols fail.
if (!ADP.IsEmpty(protocol)) {
ExtendedServerName = protocol + ":" + serverName;
}
else {
ExtendedServerName = serverName;
}
ResolvedServerName = serverName;
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- SqlTypesSchemaImporter.cs
- InternalConfigConfigurationFactory.cs
- PtsHost.cs
- CombinedGeometry.cs
- File.cs
- WeakEventTable.cs
- DataFormats.cs
- CompiledQueryCacheEntry.cs
- RegistrySecurity.cs
- UnaryNode.cs
- ContentElementCollection.cs
- ReadOnlyKeyedCollection.cs
- ToolboxItemLoader.cs
- RequestQueryParser.cs
- CellRelation.cs
- ElementAction.cs
- SqlTypeConverter.cs
- TranslateTransform3D.cs
- UnmanagedMarshal.cs
- FolderBrowserDialogDesigner.cs
- ProviderUtil.cs
- SynchronizationFilter.cs
- Point3DCollection.cs
- SectionInput.cs
- BooleanConverter.cs
- StorageAssociationTypeMapping.cs
- VectorKeyFrameCollection.cs
- Menu.cs
- SqlFormatter.cs
- Version.cs
- LinkConverter.cs
- BasicKeyConstraint.cs
- PtsHost.cs
- WebEventCodes.cs
- HtmlGenericControl.cs
- PeerValidationBehavior.cs
- AttachedPropertyBrowsableAttribute.cs
- ValueOfAction.cs
- StringToken.cs
- CodeMemberEvent.cs
- TemplatedAdorner.cs
- Brushes.cs
- InternalConfigSettingsFactory.cs
- ObjectItemCollection.cs
- FormsAuthenticationUser.cs
- ListViewCommandEventArgs.cs
- WindowsTooltip.cs
- TriggerCollection.cs
- HostingEnvironment.cs
- UnknownBitmapDecoder.cs
- ContentPlaceHolder.cs
- GrammarBuilderBase.cs
- NavigationService.cs
- TableStyle.cs
- StorageFunctionMapping.cs
- HopperCache.cs
- XD.cs
- UnicastIPAddressInformationCollection.cs
- IntSecurity.cs
- HttpServerUtilityWrapper.cs
- CollectionBase.cs
- MappingSource.cs
- NamespaceDisplay.xaml.cs
- TransformerInfoCollection.cs
- BuildDependencySet.cs
- AnnotationHelper.cs
- XmlReader.cs
- SendActivityDesignerTheme.cs
- QilBinary.cs
- ByteStream.cs
- TextBoxAutomationPeer.cs
- TableAutomationPeer.cs
- NaturalLanguageHyphenator.cs
- ProfileManager.cs
- DropTarget.cs
- XamlInterfaces.cs
- TabControlEvent.cs
- SiteMapNodeCollection.cs
- WCFModelStrings.Designer.cs
- ScriptingSectionGroup.cs
- Range.cs
- IxmlLineInfo.cs
- LogArchiveSnapshot.cs
- Form.cs
- RootContext.cs
- XmlObjectSerializerWriteContextComplex.cs
- ConfigurationPropertyCollection.cs
- TextRangeProviderWrapper.cs
- ClientConfigPaths.cs
- _NetworkingPerfCounters.cs
- XmlRawWriterWrapper.cs
- WindowsListViewSubItem.cs
- HtmlTable.cs
- Button.cs
- PeerConnector.cs
- KeyFrames.cs
- AdornerDecorator.cs
- SchemaComplexType.cs
- HttpListenerTimeoutManager.cs
- DeploymentSection.cs