2010 lines
92 KiB
C#
2010 lines
92 KiB
C#
|
//------------------------------------------------------------------------------
|
||
|
// <copyright file="SqlInternalConnectionTds.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
// <owner current="true" primary="true">[....]</owner>
|
||
|
// <owner current="true" primary="false">[....]</owner>
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
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;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Threading.Tasks;
|
||
|
|
||
|
|
||
|
internal class SessionStateRecord {
|
||
|
internal bool _recoverable;
|
||
|
internal UInt32 _version;
|
||
|
internal Int32 _dataLength;
|
||
|
internal byte[] _data;
|
||
|
}
|
||
|
|
||
|
internal class SessionData {
|
||
|
internal const int _maxNumberOfSessionStates = 256;
|
||
|
internal UInt32 _tdsVersion;
|
||
|
internal bool _encrypted;
|
||
|
|
||
|
internal string _database;
|
||
|
internal SqlCollation _collation;
|
||
|
internal string _language;
|
||
|
|
||
|
internal string _initialDatabase;
|
||
|
internal SqlCollation _initialCollation;
|
||
|
internal string _initialLanguage;
|
||
|
|
||
|
internal byte _unrecoverableStatesCount = 0;
|
||
|
internal Dictionary<string, Tuple<string, string>> _resolvedAliases;
|
||
|
|
||
|
#if DEBUG
|
||
|
internal bool _debugReconnectDataApplied;
|
||
|
#endif
|
||
|
|
||
|
internal SessionStateRecord[] _delta = new SessionStateRecord[_maxNumberOfSessionStates];
|
||
|
internal bool _deltaDirty = false;
|
||
|
internal byte[][] _initialState = new byte[_maxNumberOfSessionStates][];
|
||
|
|
||
|
public SessionData(SessionData recoveryData) {
|
||
|
_initialDatabase = recoveryData._initialDatabase;
|
||
|
_initialCollation = recoveryData._initialCollation;
|
||
|
_initialLanguage = recoveryData._initialLanguage;
|
||
|
_resolvedAliases = recoveryData._resolvedAliases;
|
||
|
|
||
|
for (int i = 0; i < _maxNumberOfSessionStates; i++) {
|
||
|
if (recoveryData._initialState[i] != null) {
|
||
|
_initialState[i] = (byte[])recoveryData._initialState[i].Clone();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public SessionData() {
|
||
|
_resolvedAliases = new Dictionary<string, Tuple<string, string>>(2);
|
||
|
}
|
||
|
|
||
|
public void Reset() {
|
||
|
_database = null;
|
||
|
_collation = null;
|
||
|
_language = null;
|
||
|
if (_deltaDirty) {
|
||
|
_delta = new SessionStateRecord[_maxNumberOfSessionStates];
|
||
|
_deltaDirty = false;
|
||
|
}
|
||
|
_unrecoverableStatesCount = 0;
|
||
|
}
|
||
|
|
||
|
[Conditional("DEBUG")]
|
||
|
public void AssertUnrecoverableStateCountIsCorrect() {
|
||
|
byte unrecoverableCount = 0;
|
||
|
foreach (var state in _delta) {
|
||
|
if (state != null && !state._recoverable)
|
||
|
unrecoverableCount++;
|
||
|
}
|
||
|
Debug.Assert(unrecoverableCount == _unrecoverableStatesCount, "Unrecoverable count does not match");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
private SqlCredential _credential;
|
||
|
|
||
|
// Connection Resiliency
|
||
|
private bool _sessionRecoveryRequested;
|
||
|
internal bool _sessionRecoveryAcknowledged;
|
||
|
internal SessionData _currentSessionData; // internal for use from TdsParser only, otehr should use CurrentSessionData property that will fix database and language
|
||
|
private SessionData _recoverySessionData;
|
||
|
|
||
|
internal SessionData CurrentSessionData {
|
||
|
get {
|
||
|
if (_currentSessionData != null) {
|
||
|
_currentSessionData._database = CurrentDatabase;
|
||
|
_currentSessionData._language = _currentLanguage;
|
||
|
}
|
||
|
return _currentSessionData;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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().
|
||
|
|
||
|
// FOR SYNCHRONIZATION IN TdsParser
|
||
|
// How to use these locks:
|
||
|
// 1. Whenever writing to the connection (with the exception of Cancellation) the _parserLock MUST be taken
|
||
|
// 2. _parserLock will also be taken during close (to prevent closing in the middle of a write)
|
||
|
// 3. Whenever you have the _parserLock and are calling a method that would cause the connection to close if it failed (with the exception of any writing method), you MUST set ThreadHasParserLockForClose to true
|
||
|
// * This is to prevent the connection deadlocking with itself (since you already have the _parserLock, and Closing the connection will attempt to re-take that lock)
|
||
|
// * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unneccesary
|
||
|
// * If you have a method that takes _parserLock, it is a good idea check ThreadHasParserLockForClose first (if you don't expect _parserLock to be taken by something higher on the stack, then you should at least assert that it is false)
|
||
|
// 4. ThreadHasParserLockForClose is thread-specific - this means that you must set it to false before returning a Task, and set it back to true in the continuation
|
||
|
// 5. ThreadHasParserLockForClose should only be modified if you currently own the _parserLock
|
||
|
// 6. Reading ThreadHasParserLockForClose is thread-safe
|
||
|
internal class SyncAsyncLock
|
||
|
{
|
||
|
SemaphoreSlim semaphore = new SemaphoreSlim(1);
|
||
|
|
||
|
internal void Wait(bool canReleaseFromAnyThread)
|
||
|
{
|
||
|
Monitor.Enter(semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
|
||
|
if (canReleaseFromAnyThread || semaphore.CurrentCount==0) {
|
||
|
semaphore.Wait();
|
||
|
if (canReleaseFromAnyThread) {
|
||
|
Monitor.Exit(semaphore);
|
||
|
}
|
||
|
else {
|
||
|
semaphore.Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken) {
|
||
|
lockTaken = false;
|
||
|
bool hasMonitor = false;
|
||
|
try {
|
||
|
Monitor.TryEnter(semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
|
||
|
if (hasMonitor) {
|
||
|
if ((canReleaseFromAnyThread) || (semaphore.CurrentCount == 0)) {
|
||
|
if (semaphore.Wait(timeout)) {
|
||
|
if (canReleaseFromAnyThread) {
|
||
|
Monitor.Exit(semaphore);
|
||
|
hasMonitor = false;
|
||
|
}
|
||
|
else {
|
||
|
semaphore.Release();
|
||
|
}
|
||
|
lockTaken = true;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
lockTaken = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if ((!lockTaken) && (hasMonitor)) {
|
||
|
Monitor.Exit(semaphore);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void Release()
|
||
|
{
|
||
|
if (semaphore.CurrentCount==0) { // semaphore methods were used for locking
|
||
|
semaphore.Release();
|
||
|
}
|
||
|
else {
|
||
|
Monitor.Exit(semaphore);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
internal bool CanBeReleasedFromAnyThread {
|
||
|
get {
|
||
|
return semaphore.CurrentCount==0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Necessary but not sufficient condition for thread to have lock (since sempahore may be obtained by any thread)
|
||
|
internal bool ThreadMayHaveLock() {
|
||
|
return Monitor.IsEntered(semaphore) || semaphore.CurrentCount == 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
internal SyncAsyncLock _parserLock = new SyncAsyncLock();
|
||
|
private int _threadIdOwningParserLock = -1;
|
||
|
|
||
|
private SqlConnectionTimeoutErrorInternal timeoutErrorInternal;
|
||
|
|
||
|
internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal
|
||
|
{
|
||
|
get { return timeoutErrorInternal; }
|
||
|
}
|
||
|
|
||
|
// OTHER STATE VARIABLES AND REFERENCES
|
||
|
|
||
|
internal Guid _clientConnectionId = Guid.Empty;
|
||
|
|
||
|
// Routing information (ROR)
|
||
|
RoutingInfo _routingInfo = null;
|
||
|
private Guid _originalClientConnectionId = Guid.Empty;
|
||
|
private string _routingDestination = null;
|
||
|
|
||
|
// 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,
|
||
|
SqlCredential credential,
|
||
|
object providerInfo,
|
||
|
string newPassword,
|
||
|
SecureString newSecurePassword,
|
||
|
bool redirectedUserInstance,
|
||
|
SqlConnectionString userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand)
|
||
|
SessionData reconnectSessionData = null) : base(connectionOptions) {
|
||
|
|
||
|
#if DEBUG
|
||
|
if (reconnectSessionData != null) {
|
||
|
reconnectSessionData._debugReconnectDataApplied = true;
|
||
|
}
|
||
|
try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath
|
||
|
if (userConnectionOptions != null) {
|
||
|
// As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string
|
||
|
userConnectionOptions.DemandPermission();
|
||
|
}
|
||
|
else {
|
||
|
connectionOptions.DemandPermission();
|
||
|
}
|
||
|
}
|
||
|
catch(System.Security.SecurityException) {
|
||
|
System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
|
||
|
throw;
|
||
|
}
|
||
|
#endif
|
||
|
Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, "Reconnect data supplied with CR turned off");
|
||
|
|
||
|
if (connectionOptions.ConnectRetryCount > 0) {
|
||
|
_recoverySessionData = reconnectSessionData;
|
||
|
if (reconnectSessionData == null) {
|
||
|
_currentSessionData = new SessionData();
|
||
|
}
|
||
|
else {
|
||
|
_currentSessionData = new SessionData(_recoverySessionData);
|
||
|
_originalDatabase = _recoverySessionData._initialDatabase;
|
||
|
_originalLanguage = _recoverySessionData._initialLanguage;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) {
|
||
|
throw SQL.UserInstanceNotAvailableInProc();
|
||
|
}
|
||
|
|
||
|
_identity = identity;
|
||
|
Debug.Assert(newSecurePassword != null || newPassword != null, "cannot have both new secure change password and string based change password to be null");
|
||
|
Debug.Assert(credential == null || (String.IsNullOrEmpty(connectionOptions.UserID) && String.IsNullOrEmpty(connectionOptions.Password)), "cannot mix the new secure password system and the connection string based password");
|
||
|
|
||
|
Debug.Assert(credential == null || !connectionOptions.IntegratedSecurity, "Cannot use SqlCredential and Integrated Security");
|
||
|
Debug.Assert(credential == null || !connectionOptions.ContextConnection, "Cannot use SqlCredential with context connection");
|
||
|
|
||
|
_poolGroupProviderInfo = (SqlConnectionPoolGroupProviderInfo)providerInfo;
|
||
|
_fResetConnection = connectionOptions.ConnectionReset;
|
||
|
if (_fResetConnection && _recoverySessionData == null) {
|
||
|
_originalDatabase = connectionOptions.InitialCatalog;
|
||
|
_originalLanguage = connectionOptions.CurrentLanguage;
|
||
|
}
|
||
|
|
||
|
timeoutErrorInternal = new SqlConnectionTimeoutErrorInternal();
|
||
|
_credential = credential;
|
||
|
|
||
|
_parserLock.Wait(canReleaseFromAnyThread:false);
|
||
|
ThreadHasParserLockForClose = true; // In case of error, let ourselves know that we already own the parser lock
|
||
|
RuntimeHelpers.PrepareConstrainedRegions();
|
||
|
try {
|
||
|
#if DEBUG
|
||
|
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
|
||
|
|
||
|
RuntimeHelpers.PrepareConstrainedRegions();
|
||
|
try {
|
||
|
tdsReliabilitySection.Start();
|
||
|
#else
|
||
|
{
|
||
|
#endif //DEBUG
|
||
|
var timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout);
|
||
|
OpenLoginEnlist(timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance);
|
||
|
}
|
||
|
#if DEBUG
|
||
|
finally {
|
||
|
tdsReliabilitySection.Stop();
|
||
|
}
|
||
|
#endif //DEBUG
|
||
|
}
|
||
|
catch (System.OutOfMemoryException) {
|
||
|
DoomThisConnection();
|
||
|
throw;
|
||
|
}
|
||
|
catch (System.StackOverflowException) {
|
||
|
DoomThisConnection();
|
||
|
throw;
|
||
|
}
|
||
|
catch (System.Threading.ThreadAbortException) {
|
||
|
DoomThisConnection();
|
||
|
throw;
|
||
|
}
|
||
|
finally {
|
||
|
ThreadHasParserLockForClose = false;
|
||
|
_parserLock.Release();
|
||
|
}
|
||
|
if (Bid.AdvancedOn) {
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.ctor|ADV> %d#, constructed new TDS internal connection\n", ObjectID);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal Guid ClientConnectionId {
|
||
|
get {
|
||
|
return _clientConnectionId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal Guid OriginalClientConnectionId {
|
||
|
get {
|
||
|
return _originalClientConnectionId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal string RoutingDestination {
|
||
|
get {
|
||
|
return _routingDestination;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get boolean that specifies whether an enlisted transaction can be unbound from
|
||
|
/// the connection when that transaction completes.
|
||
|
/// </summary>
|
||
|
/// <value>
|
||
|
/// This override always returns false.
|
||
|
/// </value>
|
||
|
/// <remarks>
|
||
|
/// The SqlInternalConnectionTds.CheckEnlistedTransactionBinding method handles implicit unbinding for disposed transactions.
|
||
|
/// </remarks>
|
||
|
protected override bool UnbindOnTransactionCompletion
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// GENERAL METHODS
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
[SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
|
||
|
override protected void ChangeDatabaseInternal(string database) {
|
||
|
// MDAC 73598 - add brackets around database
|
||
|
database = SqlConnection.FixupDatabaseTransactionName(database);
|
||
|
Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
|
||
|
Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
|
||
|
_parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
|
||
|
}
|
||
|
|
||
|
override public void Dispose() {
|
||
|
if (Bid.AdvancedOn) {
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.Dispose|ADV> %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 { // UNDONE: MDAC 77928
|
||
|
// 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) {
|
||
|
TdsParser parser = _parser;
|
||
|
if ((parser == null) || (parser.State == TdsParserState.Broken) || (parser.State == TdsParserState.Closed)) {
|
||
|
throw ADP.ClosedConnectionError();
|
||
|
}
|
||
|
else {
|
||
|
SqlDataReader reader = null;
|
||
|
if (parser.MARSOn) {
|
||
|
if (null != command) { // command can't have datareader already associated with it
|
||
|
reader = FindLiveReader(command);
|
||
|
}
|
||
|
}
|
||
|
else { // single execution/datareader per connection
|
||
|
if (_asyncCommandCount > 0) {
|
||
|
throw SQL.MARSUnspportedOnConnection();
|
||
|
}
|
||
|
|
||
|
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.DrainData(parser._physicalStateObj);
|
||
|
}
|
||
|
Debug.Assert(!parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!");
|
||
|
|
||
|
parser.RollbackOrphanedAPITransactions();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Validate the enlisted transaction state, taking into consideration the ambient transaction and transaction unbinding mode.
|
||
|
/// If there is no enlisted transaction, this method is a nop.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// <para>
|
||
|
/// This method must be called while holding a lock on the SqlInternalConnection instance,
|
||
|
/// to ensure we don't accidentally execute after the transaction has completed on a different thread,
|
||
|
/// causing us to unwittingly execute in auto-commit mode.
|
||
|
/// </para>
|
||
|
///
|
||
|
/// <para>
|
||
|
/// When using Explicit transaction unbinding,
|
||
|
/// verify that the enlisted transaction is active and equal to the current ambient transaction.
|
||
|
/// </para>
|
||
|
///
|
||
|
/// <para>
|
||
|
/// When using Implicit transaction unbinding,
|
||
|
/// verify that the enlisted transaction is active.
|
||
|
/// If it is not active, and the transaction object has been diposed, unbind from the transaction.
|
||
|
/// If it is not active and not disposed, throw an exception.
|
||
|
/// </para>
|
||
|
/// </remarks>
|
||
|
internal void CheckEnlistedTransactionBinding()
|
||
|
{
|
||
|
// If we are enlisted in a transaction, check that transaction is active.
|
||
|
// When using explicit transaction unbinding, also verify that the enlisted transaction is the current transaction.
|
||
|
SysTx.Transaction enlistedTransaction = EnlistedTransaction;
|
||
|
|
||
|
if (enlistedTransaction != null)
|
||
|
{
|
||
|
bool requireExplicitTransactionUnbind = ConnectionOptions.TransactionBinding == SqlConnectionString.TransactionBindingEnum.ExplicitUnbind;
|
||
|
|
||
|
if (requireExplicitTransactionUnbind)
|
||
|
{
|
||
|
SysTx.Transaction currentTransaction = SysTx.Transaction.Current;
|
||
|
|
||
|
if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status || !enlistedTransaction.Equals(currentTransaction))
|
||
|
{
|
||
|
throw ADP.TransactionConnectionMismatch();
|
||
|
}
|
||
|
}
|
||
|
else // implicit transaction unbind
|
||
|
{
|
||
|
if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status)
|
||
|
{
|
||
|
if (EnlistedTransactionDisposed)
|
||
|
{
|
||
|
DetachTransaction(enlistedTransaction, true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
throw ADP.TransactionCompletedButNotDisposed();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override bool IsConnectionAlive(bool throwOnException)
|
||
|
{
|
||
|
bool isAlive = false;
|
||
|
#if DEBUG
|
||
|
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
|
||
|
|
||
|
RuntimeHelpers.PrepareConstrainedRegions();
|
||
|
try
|
||
|
{
|
||
|
tdsReliabilitySection.Start();
|
||
|
#endif //DEBUG
|
||
|
|
||
|
isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException);
|
||
|
|
||
|
#if DEBUG
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
tdsReliabilitySection.Stop();
|
||
|
}
|
||
|
#endif //DEBUG
|
||
|
return isAlive;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// 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 || IsConnectionDoomed, "Deactivating a disposed connection?");
|
||
|
if (_parser != null) {
|
||
|
|
||
|
_parser.Deactivate(IsConnectionDoomed);
|
||
|
|
||
|
if (!IsConnectionDoomed) {
|
||
|
ResetConnection();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
|
||
|
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
|
||
|
Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("sp_reset_connection", 30, null, _parser._physicalStateObj, sync: true);
|
||
|
Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
|
||
|
_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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function will not handle idle connection resiliency, as older servers will not support it
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch(sqlBatch.ToString(), ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
|
||
|
Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
|
||
|
_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 releaseConnectionLock = false;
|
||
|
|
||
|
Debug.Assert(!ThreadHasParserLockForClose || _parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken");
|
||
|
if (!ThreadHasParserLockForClose) {
|
||
|
_parserLock.Wait(canReleaseFromAnyThread:false);
|
||
|
ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already own the parser lock
|
||
|
releaseConnectionLock = true;
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// only restore if connection lock has been taken within the function
|
||
|
if (internalTransaction != null && internalTransaction.RestoreBrokenConnection && releaseConnectionLock) {
|
||
|
Task reconnectTask = internalTransaction.Parent.Connection.ValidateAndReconnect(() => {
|
||
|
ThreadHasParserLockForClose = false;
|
||
|
_parserLock.Release();
|
||
|
releaseConnectionLock = false;
|
||
|
}, 0);
|
||
|
if (reconnectTask != null) {
|
||
|
AsyncHelper.WaitForCompletion(reconnectTask, 0); // there is no specific timeout for BeginTransaction, uses ConnectTimeout
|
||
|
internalTransaction.ConnectionHasBeenRestored = true;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// 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) {
|
||
|
throw SQL.CannotCompleteDelegatedTransactionWithOpenResults(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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 (releaseConnectionLock) {
|
||
|
ThreadHasParserLockForClose = false;
|
||
|
_parserLock.Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// DISTRIBUTED TRANSACTION METHODS
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
override internal void DelegatedTransactionEnded() {
|
||
|
//
|
||
|
base.DelegatedTransactionEnded();
|
||
|
}
|
||
|
|
||
|
override protected byte[] GetDTCAddress() {
|
||
|
byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this));
|
||
|
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);
|
||
|
|
||
|
if (_routingInfo == null) { // ROR should not affect state of connection recovery
|
||
|
if (!_sessionRecoveryAcknowledged) {
|
||
|
_currentSessionData = null;
|
||
|
if (_recoverySessionData != null) {
|
||
|
throw SQL.CR_NoCRAckAtReconnection(this);
|
||
|
}
|
||
|
}
|
||
|
if (_currentSessionData != null && _recoverySessionData==null) {
|
||
|
_currentSessionData._initialDatabase = CurrentDatabase;
|
||
|
_currentSessionData._initialCollation = _currentSessionData._collation;
|
||
|
_currentSessionData._initialLanguage = _currentLanguage;
|
||
|
}
|
||
|
bool isEncrypted = _parser.EncryptionOptions == EncryptionOptions.ON;
|
||
|
if (_recoverySessionData != null) {
|
||
|
if (_recoverySessionData._encrypted != isEncrypted) {
|
||
|
throw SQL.CR_EncryptionChanged(this);
|
||
|
}
|
||
|
}
|
||
|
if (_currentSessionData != null) {
|
||
|
_currentSessionData._encrypted = isEncrypted;
|
||
|
}
|
||
|
_recoverySessionData = null;
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
|
||
|
_fConnectionOpen = true; // mark connection as open
|
||
|
|
||
|
if (Bid.AdvancedOn) {
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ADV> Post-Login Phase: Server connection obtained.\n");
|
||
|
}
|
||
|
|
||
|
// 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(ServerInfo server, TimeoutTimer timeout, string newPassword, SecureString newSecurePassword) {
|
||
|
// 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 = server.ResolvedDatabaseName;
|
||
|
_currentPacketSize = ConnectionOptions.PacketSize;
|
||
|
_currentLanguage = ConnectionOptions.CurrentLanguage;
|
||
|
|
||
|
int timeoutInSeconds = 0;
|
||
|
|
||
|
// If a timeout tick value is specified, compute the timeout based
|
||
|
// upon the amount of time left in seconds.
|
||
|
if (!timeout.IsInfinite)
|
||
|
{
|
||
|
long t = timeout.MillisecondsRemaining/1000;
|
||
|
if ((long)Int32.MaxValue > t)
|
||
|
{
|
||
|
timeoutInSeconds = (int)t;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
login.timeout = timeoutInSeconds;
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// VSTS#795621 - Ensure ServerName is Sent During TdsLogin To Enable Sql Azure Connectivity.
|
||
|
// Using server.UserServerName (versus ConnectionOptions.DataSource) since TdsLogin requires
|
||
|
// serverName to always be non-null.
|
||
|
login.serverName = server.UserServerName;
|
||
|
|
||
|
login.useReplication = ConnectionOptions.Replication;
|
||
|
login.useSSPI = ConnectionOptions.IntegratedSecurity;
|
||
|
login.packetSize = _currentPacketSize;
|
||
|
login.newPassword = newPassword;
|
||
|
login.readOnlyIntent = ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly;
|
||
|
login.credential = _credential;
|
||
|
if (newSecurePassword != null) {
|
||
|
login.newSecurePassword = newSecurePassword;
|
||
|
}
|
||
|
|
||
|
TdsEnums.FeatureExtension requestedFeatures = TdsEnums.FeatureExtension.None;
|
||
|
if (ConnectionOptions.ConnectRetryCount>0) {
|
||
|
requestedFeatures |= TdsEnums.FeatureExtension.SessionRecovery;
|
||
|
_sessionRecoveryRequested = true;
|
||
|
}
|
||
|
|
||
|
_parser.TdsLogin(login, requestedFeatures, _recoverySessionData);
|
||
|
}
|
||
|
|
||
|
private void LoginFailure() {
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.LoginFailure|RES|CPOOL> %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(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential,
|
||
|
string newPassword, SecureString newSecurePassword, bool redirectedUserInstance) {
|
||
|
bool useFailoverPartner; // should we use primary or secondary first
|
||
|
ServerInfo dataSource = new ServerInfo(connectionOptions);
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
timeoutErrorInternal.SetInternalSourceType(useFailoverPartner ? SqlConnectionInternalSourceType.Failover : SqlConnectionInternalSourceType.Principle);
|
||
|
|
||
|
bool hasFailoverPartner = !ADP.IsEmpty(failoverPartner);
|
||
|
|
||
|
// Open the connection and Login
|
||
|
try {
|
||
|
timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
|
||
|
if (hasFailoverPartner) {
|
||
|
timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
|
||
|
LoginWithFailover(
|
||
|
useFailoverPartner,
|
||
|
dataSource,
|
||
|
failoverPartner,
|
||
|
newPassword,
|
||
|
newSecurePassword,
|
||
|
redirectedUserInstance,
|
||
|
connectionOptions,
|
||
|
credential,
|
||
|
timeout);
|
||
|
}
|
||
|
else {
|
||
|
timeoutErrorInternal.SetFailoverScenario(false); // not a failover scenario
|
||
|
LoginNoFailover(dataSource, newPassword, newSecurePassword, redirectedUserInstance,
|
||
|
connectionOptions, credential, timeout);
|
||
|
}
|
||
|
timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
|
||
|
}
|
||
|
catch (Exception e) {
|
||
|
//
|
||
|
if (ADP.IsCatchableExceptionType(e)) {
|
||
|
LoginFailure();
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
timeoutErrorInternal.SetAllCompleteMarker();
|
||
|
|
||
|
#if DEBUG
|
||
|
_parser._physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Is the given Sql error one that should prevent retrying
|
||
|
// to connect.
|
||
|
private bool IsDoNotRetryConnectError(SqlException exc) {
|
||
|
|
||
|
return (TdsEnums.LOGON_FAILED == exc.Number) // actual logon failed, i.e. bad password
|
||
|
|| (TdsEnums.PASSWORD_EXPIRED == exc.Number) // actual logon failed, i.e. password isExpired
|
||
|
|| (TdsEnums.IMPERSONATION_FAILED == exc.Number) // Insuficient privelege for named pipe, among others
|
||
|
|| exc._doNotReconnect; // Exception explicitly supressed reconnection attempts
|
||
|
}
|
||
|
|
||
|
// 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(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance,
|
||
|
SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) {
|
||
|
|
||
|
Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument
|
||
|
int routingAttempts = 0;
|
||
|
ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource
|
||
|
|
||
|
if (Bid.AdvancedOn) {
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, host=%ls\n", ObjectID, serverInfo.UserServerName);
|
||
|
}
|
||
|
int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
|
||
|
|
||
|
ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions);
|
||
|
|
||
|
long timeoutUnitInterval = 0;
|
||
|
|
||
|
if (connectionOptions.MultiSubnetFailover) {
|
||
|
// Determine unit interval
|
||
|
if (timeout.IsInfinite) {
|
||
|
timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout)));
|
||
|
}
|
||
|
else {
|
||
|
timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
|
||
|
}
|
||
|
}
|
||
|
// 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.
|
||
|
int attemptNumber = 0;
|
||
|
TimeoutTimer intervalTimer = null;
|
||
|
while(true) {
|
||
|
|
||
|
if (connectionOptions.MultiSubnetFailover) {
|
||
|
attemptNumber++;
|
||
|
// Set timeout for this attempt, but don't exceed original timer
|
||
|
long nextTimeoutInterval = checked(timeoutUnitInterval * attemptNumber);
|
||
|
long milliseconds = timeout.MillisecondsRemaining;
|
||
|
if (nextTimeoutInterval > milliseconds) {
|
||
|
nextTimeoutInterval = milliseconds;
|
||
|
}
|
||
|
intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
|
||
|
}
|
||
|
|
||
|
// Re-allocate parser each time to make sure state is known
|
||
|
// RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
|
||
|
if (_parser != null)
|
||
|
_parser.Disconnect();
|
||
|
|
||
|
_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,
|
||
|
newSecurePassword,
|
||
|
!connectionOptions.MultiSubnetFailover, // ignore timeout for SniOpen call unless MSF
|
||
|
connectionOptions.MultiSubnetFailover ? intervalTimer : timeout);
|
||
|
|
||
|
if (connectionOptions.MultiSubnetFailover && null != ServerProvidedFailOverPartner) {
|
||
|
// connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used
|
||
|
throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
|
||
|
}
|
||
|
|
||
|
if (_routingInfo != null) {
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover> Routed to %ls", serverInfo.ExtendedServerName);
|
||
|
|
||
|
if (routingAttempts > 0) {
|
||
|
throw SQL.ROR_RecursiveRoutingNotSupported(this);
|
||
|
}
|
||
|
|
||
|
if (timeout.IsExpired) {
|
||
|
throw SQL.ROR_TimeoutAfterRoutingInfo(this);
|
||
|
}
|
||
|
|
||
|
serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName);
|
||
|
timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination);
|
||
|
_originalClientConnectionId = _clientConnectionId;
|
||
|
_routingDestination = serverInfo.UserServerName;
|
||
|
|
||
|
// restore properties that could be changed by the environment tokens
|
||
|
_currentPacketSize = ConnectionOptions.PacketSize;
|
||
|
_currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
|
||
|
CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog;
|
||
|
_currentFailoverPartner = null;
|
||
|
_instanceName = String.Empty;
|
||
|
|
||
|
routingAttempts++;
|
||
|
|
||
|
continue; // repeat the loop, but skip code reserved for failed connections (after the catch)
|
||
|
}
|
||
|
else {
|
||
|
break; // leave the while loop -- we've successfully connected
|
||
|
}
|
||
|
}
|
||
|
catch (SqlException sqlex) {
|
||
|
if (null == _parser
|
||
|
|| TdsParserState.Closed != _parser.State
|
||
|
|| IsDoNotRetryConnectError(sqlex)
|
||
|
|| timeout.IsExpired) { // 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
|
||
|
if (timeout.MillisecondsRemaining <= 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) {
|
||
|
if (connectionOptions.MultiSubnetFailover) {
|
||
|
// connection failed: do not allow failover to server-provided failover partner if MultiSubnetFailover is set
|
||
|
throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
|
||
|
}
|
||
|
Debug.Assert(ConnectionOptions.ApplicationIntent != ApplicationIntent.ReadOnly, "FAILOVER+AppIntent=RO: Should already fail (at LOGSHIPNODE in OnEnvChange)");
|
||
|
|
||
|
timeoutErrorInternal.ResetAndRestartPhase();
|
||
|
timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
|
||
|
timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
|
||
|
timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
|
||
|
LoginWithFailover(
|
||
|
true, // start by using failover partner, since we already failed to connect to the primary
|
||
|
serverInfo,
|
||
|
ServerProvidedFailOverPartner,
|
||
|
newPassword,
|
||
|
newSecurePassword,
|
||
|
redirectedUserInstance,
|
||
|
connectionOptions,
|
||
|
credential,
|
||
|
timeout);
|
||
|
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("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %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 = originalServerInfo.UserServerName;
|
||
|
}
|
||
|
|
||
|
// 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,
|
||
|
ServerInfo primaryServerInfo,
|
||
|
string failoverHost,
|
||
|
string newPassword,
|
||
|
SecureString newSecurePassword,
|
||
|
bool redirectedUserInstance,
|
||
|
SqlConnectionString connectionOptions,
|
||
|
SqlCredential credential,
|
||
|
TimeoutTimer timeout
|
||
|
) {
|
||
|
|
||
|
Debug.Assert(!connectionOptions.MultiSubnetFailover, "MultiSubnetFailover should not be set if failover partner is used");
|
||
|
|
||
|
if (Bid.AdvancedOn) {
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, useFailover=%d{bool}, primary=", ObjectID, useFailoverHost);
|
||
|
Bid.PutStr(primaryServerInfo.UserServerName);
|
||
|
Bid.PutStr(", failover=");
|
||
|
Bid.PutStr(failoverHost);
|
||
|
Bid.PutStr("\n");
|
||
|
}
|
||
|
int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
|
||
|
long timeoutUnitInterval;
|
||
|
|
||
|
string protocol = ConnectionOptions.NetworkLibrary;
|
||
|
ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost);
|
||
|
|
||
|
ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions);
|
||
|
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 != primaryServerInfo.UserServerName, connectionOptions);
|
||
|
}
|
||
|
|
||
|
// Determine unit interval
|
||
|
if (timeout.IsInfinite) {
|
||
|
timeoutUnitInterval = checked((long) ADP.FailoverTimeoutStep * ADP.TimerFromSeconds(ADP.DefaultConnectionTimeout));
|
||
|
}
|
||
|
else {
|
||
|
timeoutUnitInterval = checked((long) (ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
|
||
|
}
|
||
|
|
||
|
// Initialize loop variables
|
||
|
bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)?
|
||
|
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) {
|
||
|
// Set timeout for this attempt, but don't exceed original timer
|
||
|
long nextTimeoutInterval = checked(timeoutUnitInterval * ((attemptNumber / 2) + 1));
|
||
|
long milliseconds = timeout.MillisecondsRemaining;
|
||
|
if (nextTimeoutInterval > milliseconds) {
|
||
|
nextTimeoutInterval = milliseconds;
|
||
|
}
|
||
|
|
||
|
TimeoutTimer intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
|
||
|
|
||
|
// Re-allocate parser each time to make sure state is known
|
||
|
// RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
|
||
|
if (_parser != null)
|
||
|
_parser.Disconnect();
|
||
|
|
||
|
_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("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, new failover partner=%ls\n", ObjectID, ServerProvidedFailOverPartner);
|
||
|
}
|
||
|
failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner);
|
||
|
}
|
||
|
currentServerInfo = failoverServerInfo;
|
||
|
timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
|
||
|
}
|
||
|
else {
|
||
|
currentServerInfo = primaryServerInfo;
|
||
|
timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Principle);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
// Attempt login. Use timerInterval for attempt timeout unless infinite timeout was requested.
|
||
|
AttemptOneLogin(
|
||
|
currentServerInfo,
|
||
|
newPassword,
|
||
|
newSecurePassword,
|
||
|
false, // Use timeout in SniOpen
|
||
|
intervalTimer,
|
||
|
withFailover:true
|
||
|
);
|
||
|
|
||
|
if (_routingInfo != null) {
|
||
|
// We are in login with failover scenation and server sent routing information
|
||
|
// If it is read-only routing - we did not supply AppIntent=RO (it should be checked before)
|
||
|
// If it is something else, not known yet (future server) - this client is not designed to support this.
|
||
|
// In any case, server should not have sent the routing info.
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover> Routed to %ls", _routingInfo.ServerName);
|
||
|
throw SQL.ROR_UnexpectedRoutingInfo(this);
|
||
|
}
|
||
|
|
||
|
break; // leave the while loop -- we've successfully connected
|
||
|
}
|
||
|
catch (SqlException sqlex) {
|
||
|
if (IsDoNotRetryConnectError(sqlex)
|
||
|
|| timeout.IsExpired)
|
||
|
{ // 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 original timeout
|
||
|
// Do this in the catch block so we can re-throw the current exception
|
||
|
if (timeout.MillisecondsRemaining <= 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("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
|
||
|
}
|
||
|
Thread.Sleep(sleepInterval);
|
||
|
sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
|
||
|
}
|
||
|
|
||
|
// Update attempt number and target host
|
||
|
attemptNumber++;
|
||
|
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 : primaryServerInfo.UserServerName);
|
||
|
}
|
||
|
|
||
|
private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnectionString options) {
|
||
|
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.
|
||
|
// Check if it was already resolved, during CR reconnection _currentSessionData values will be copied from
|
||
|
// _reconnectSessonData of the previous connection
|
||
|
if (_currentSessionData != null && !string.IsNullOrEmpty(host)) {
|
||
|
Tuple<string, string> hostPortPair;
|
||
|
if (_currentSessionData._resolvedAliases.TryGetValue(host, out hostPortPair)) {
|
||
|
host = hostPortPair.Item1;
|
||
|
protocol = hostPortPair.Item2;
|
||
|
}
|
||
|
else {
|
||
|
TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
|
||
|
_currentSessionData._resolvedAliases.Add(serverInfo.UserServerName, new Tuple<string, string>(host, protocol));
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
if (options.EnforceLocalHost) {
|
||
|
// verify LocalHost for |DataDirectory| usage
|
||
|
SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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, SecureString newSecurePassword, bool ignoreSniOpenTimeout, TimeoutTimer timeout, bool withFailover = false) {
|
||
|
if (Bid.AdvancedOn) {
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.AttemptOneLogin|ADV> %d#, timout=%I64d{msec}, server=", ObjectID, timeout.MillisecondsRemaining);
|
||
|
Bid.PutStr(serverInfo.ExtendedServerName);
|
||
|
Bid.Trace("\n");
|
||
|
}
|
||
|
|
||
|
_routingInfo = null; // forget routing information
|
||
|
|
||
|
_parser._physicalStateObj.SniContext = SniContext.Snix_Connect;
|
||
|
|
||
|
_parser.Connect(serverInfo,
|
||
|
this,
|
||
|
ignoreSniOpenTimeout,
|
||
|
timeout.LegacyTimerExpire,
|
||
|
ConnectionOptions.Encrypt,
|
||
|
ConnectionOptions.TrustServerCertificate,
|
||
|
ConnectionOptions.IntegratedSecurity,
|
||
|
withFailover);
|
||
|
|
||
|
timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
|
||
|
timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
|
||
|
|
||
|
_parser._physicalStateObj.SniContext = SniContext.Snix_Login;
|
||
|
this.Login(serverInfo, timeout, newPassword, newSecurePassword);
|
||
|
|
||
|
timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
|
||
|
timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
|
||
|
|
||
|
CompleteLogin(!ConnectionOptions.Pooling);
|
||
|
|
||
|
timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
|
||
|
}
|
||
|
|
||
|
|
||
|
internal void FailoverPermissionDemand() {
|
||
|
if (null != PoolGroupProviderInfo) {
|
||
|
PoolGroupProviderInfo.FailoverPermissionDemand();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// PREPARED COMMAND METHODS
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
protected override object ObtainAdditionalLocksForClose() {
|
||
|
bool obtainParserLock = !ThreadHasParserLockForClose;
|
||
|
Debug.Assert(obtainParserLock || _parserLock.ThreadMayHaveLock(), "Thread claims to have lock, but lock is not taken");
|
||
|
if (obtainParserLock) {
|
||
|
_parserLock.Wait(canReleaseFromAnyThread: false);
|
||
|
ThreadHasParserLockForClose = true;
|
||
|
}
|
||
|
return obtainParserLock;
|
||
|
}
|
||
|
|
||
|
protected override void ReleaseAdditionalLocksForClose(object lockToken) {
|
||
|
Debug.Assert(lockToken is bool, "Lock token should be boolean");
|
||
|
if ((bool)lockToken) {
|
||
|
ThreadHasParserLockForClose = false;
|
||
|
_parserLock.Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// called by SqlConnection.RepairConnection which is a relatevly expensive way of repair inner connection
|
||
|
// prior to execution of request, used from EnlistTransaction, EnlistDistributedTransaction and ChangeDatabase
|
||
|
internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = 0) {
|
||
|
|
||
|
Debug.Assert(!ThreadHasParserLockForClose, "Cannot call this method if caller has parser lock");
|
||
|
if (ThreadHasParserLockForClose) {
|
||
|
return false; // we cannot restore if we cannot release lock
|
||
|
}
|
||
|
|
||
|
_parserLock.Wait(canReleaseFromAnyThread: false);
|
||
|
ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already own the parser lock
|
||
|
bool releaseConnectionLock = true;
|
||
|
|
||
|
try {
|
||
|
RuntimeHelpers.PrepareConstrainedRegions();
|
||
|
try {
|
||
|
#if DEBUG
|
||
|
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
|
||
|
RuntimeHelpers.PrepareConstrainedRegions();
|
||
|
try {
|
||
|
tdsReliabilitySection.Start();
|
||
|
#endif //DEBUG
|
||
|
Task reconnectTask = parent.ValidateAndReconnect(() => {
|
||
|
ThreadHasParserLockForClose = false;
|
||
|
_parserLock.Release();
|
||
|
releaseConnectionLock = false;
|
||
|
}, timeout);
|
||
|
if (reconnectTask != null) {
|
||
|
AsyncHelper.WaitForCompletion(reconnectTask, timeout);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
#if DEBUG
|
||
|
}
|
||
|
finally {
|
||
|
tdsReliabilitySection.Stop();
|
||
|
}
|
||
|
#endif //DEBUG
|
||
|
}
|
||
|
catch (System.OutOfMemoryException) {
|
||
|
DoomThisConnection();
|
||
|
throw;
|
||
|
}
|
||
|
catch (System.StackOverflowException) {
|
||
|
DoomThisConnection();
|
||
|
throw;
|
||
|
}
|
||
|
catch (System.Threading.ThreadAbortException) {
|
||
|
DoomThisConnection();
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
finally {
|
||
|
if (releaseConnectionLock) {
|
||
|
ThreadHasParserLockForClose = false;
|
||
|
_parserLock.Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// PARSER CALLBACKS
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
internal void BreakConnection() {
|
||
|
var connection = Connection;
|
||
|
Bid.Trace("<sc.SqlInternalConnectionTds.BreakConnection|RES|CPOOL> %d#, Breaking connection.\n", ObjectID);
|
||
|
DoomThisConnection(); // Mark connection as unusable, so it will be destroyed
|
||
|
if (null != connection) {
|
||
|
connection.Close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal bool IgnoreEnvChange { // true if we are only draining environment change tokens, used by TdsParser
|
||
|
get {
|
||
|
return _routingInfo != null; // connection was routed, ignore rest of env change
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void OnEnvChange(SqlEnvChange rec) {
|
||
|
Debug.Assert(!IgnoreEnvChange,"This function should not be called if IgnoreEnvChange is set!");
|
||
|
switch (rec.type) {
|
||
|
case TdsEnums.ENV_DATABASE:
|
||
|
// If connection is not open and recovery is not in progresss, store the server value as the original.
|
||
|
if (!_fConnectionOpen && _recoverySessionData == null) {
|
||
|
_originalDatabase = rec.newValue;
|
||
|
}
|
||
|
|
||
|
CurrentDatabase = rec.newValue;
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_LANG:
|
||
|
// If connection is not open and recovery is not in progresss, store the server value as the original.
|
||
|
if (!_fConnectionOpen && _recoverySessionData == null) {
|
||
|
_originalLanguage = rec.newValue;
|
||
|
}
|
||
|
|
||
|
_currentLanguage = rec.newValue; // TODO: finish this.
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_PACKETSIZE:
|
||
|
_currentPacketSize = Int32.Parse(rec.newValue, CultureInfo.InvariantCulture);
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_COLLATION:
|
||
|
if (_currentSessionData != null) {
|
||
|
_currentSessionData._collation = rec.newCollation;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_CHARSET:
|
||
|
case TdsEnums.ENV_LOCALEID:
|
||
|
case TdsEnums.ENV_COMPFLAGS:
|
||
|
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:
|
||
|
if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) {
|
||
|
throw SQL.ROR_FailoverNotSupportedServer(this);
|
||
|
}
|
||
|
_currentFailoverPartner = rec.newValue;
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_PROMOTETRANSACTION:
|
||
|
PromotedDTCToken = rec.newBinValue;
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_TRANSACTIONENDED:
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
|
||
|
// For now we skip these Yukon only env change notifications
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_SPRESETCONNECTIONACK:
|
||
|
// connection is being reset
|
||
|
if (_currentSessionData != null) {
|
||
|
_currentSessionData.Reset();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_USERINSTANCE:
|
||
|
_instanceName = rec.newValue;
|
||
|
break;
|
||
|
|
||
|
case TdsEnums.ENV_ROUTING:
|
||
|
if (string.IsNullOrEmpty(rec.newRoutingInfo.ServerName) || rec.newRoutingInfo.Protocol != 0 || rec.newRoutingInfo.Port == 0) {
|
||
|
throw SQL.ROR_InvalidRoutingInfo(this);
|
||
|
}
|
||
|
_routingInfo = rec.newRoutingInfo;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Debug.Assert(false, "Missed token in EnvChange!");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void OnLoginAck(SqlLoginAck rec) {
|
||
|
_loginAck = rec;
|
||
|
//
|
||
|
if (_recoverySessionData != null) {
|
||
|
if (_recoverySessionData._tdsVersion != rec.tdsVersion) {
|
||
|
throw SQL.CR_TDSVersionNotPreserved(this);
|
||
|
}
|
||
|
}
|
||
|
if (_currentSessionData != null) {
|
||
|
_currentSessionData._tdsVersion = rec.tdsVersion;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void OnFeatureExtAck(int featureId, byte[] data) {
|
||
|
if (_routingInfo != null) {
|
||
|
return;
|
||
|
}
|
||
|
switch (featureId) {
|
||
|
case TdsEnums.FEATUREEXT_SRECOVERY: {
|
||
|
// Session recovery not requested
|
||
|
if (!_sessionRecoveryRequested) {
|
||
|
throw SQL.ParsingError();
|
||
|
}
|
||
|
_sessionRecoveryAcknowledged = true;
|
||
|
|
||
|
#if DEBUG
|
||
|
foreach (var s in _currentSessionData._delta) {
|
||
|
Debug.Assert(s==null, "Delta should be null at this point");
|
||
|
}
|
||
|
#endif
|
||
|
Debug.Assert(_currentSessionData._unrecoverableStatesCount == 0, "Unrecoverable states count should be 0");
|
||
|
|
||
|
int i = 0;
|
||
|
while (i < data.Length) {
|
||
|
byte stateId = data[i]; i++;
|
||
|
int len;
|
||
|
byte bLen = data[i]; i++;
|
||
|
if (bLen == 0xFF) {
|
||
|
len = BitConverter.ToInt32(data, i); i += 4;
|
||
|
}
|
||
|
else {
|
||
|
len = bLen;
|
||
|
}
|
||
|
byte[] stateData = new byte[len];
|
||
|
Buffer.BlockCopy(data, i, stateData, 0, len); i += len;
|
||
|
if (_recoverySessionData == null) {
|
||
|
_currentSessionData._initialState[stateId] = stateData;
|
||
|
}
|
||
|
else {
|
||
|
_currentSessionData._delta[stateId] = new SessionStateRecord { _data = stateData, _dataLength = len, _recoverable = true, _version = 0 };
|
||
|
_currentSessionData._deltaDirty = true;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
default: {
|
||
|
// Unknown feature ack
|
||
|
throw SQL.ParsingError();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Helper methods for Locks
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Indicates if the current thread claims to hold the parser lock
|
||
|
internal bool ThreadHasParserLockForClose {
|
||
|
get {
|
||
|
return _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId;
|
||
|
}
|
||
|
set {
|
||
|
Debug.Assert(_parserLock.ThreadMayHaveLock(), "Should not modify ThreadHasParserLockForClose without taking the lock first");
|
||
|
Debug.Assert(_threadIdOwningParserLock == -1 || _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId, "Another thread already claims to own the parser lock");
|
||
|
|
||
|
if (value) {
|
||
|
// If setting to true, then the thread owning the lock is the current thread
|
||
|
_threadIdOwningParserLock = Thread.CurrentThread.ManagedThreadId;
|
||
|
}
|
||
|
else if (_threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId) {
|
||
|
// If setting to false and currently owns the lock, then no-one owns the lock
|
||
|
_threadIdOwningParserLock = -1;
|
||
|
}
|
||
|
// else This thread didn't own the parser lock and doesn't claim to own it, so do nothing
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions) {
|
||
|
return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal sealed class ServerInfo {
|
||
|
internal string ExtendedServerName { get; private set; } // the resolved servername with protocol
|
||
|
internal string ResolvedServerName { get; private set; } // the resolved servername only
|
||
|
internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution
|
||
|
internal string UserProtocol { get; private set; } // the user specified protocol
|
||
|
|
||
|
// The original user-supplied server name from the connection string.
|
||
|
// If connection string has no Data Source, the value is set to string.Empty.
|
||
|
// In case of routing, will be changed to routing destination
|
||
|
internal string UserServerName
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return m_userServerName;
|
||
|
}
|
||
|
private set
|
||
|
{
|
||
|
m_userServerName = value;
|
||
|
}
|
||
|
} private string m_userServerName;
|
||
|
|
||
|
internal readonly string PreRoutingServerName;
|
||
|
|
||
|
// Initialize server info from connection options,
|
||
|
internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) {}
|
||
|
|
||
|
// Initialize server info from connection options, but override DataSource with given server name
|
||
|
internal ServerInfo (SqlConnectionString userOptions, string serverName) {
|
||
|
//-----------------
|
||
|
// Preconditions
|
||
|
Debug.Assert(null != userOptions);
|
||
|
|
||
|
//-----------------
|
||
|
//Method body
|
||
|
|
||
|
Debug.Assert(serverName != null, "server name should never be null");
|
||
|
UserServerName = (serverName ?? string.Empty); // ensure user server name is not null
|
||
|
|
||
|
UserProtocol = userOptions.NetworkLibrary;
|
||
|
ResolvedDatabaseName = userOptions.InitialCatalog;
|
||
|
PreRoutingServerName = null;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Initialize server info from connection options, but override DataSource with given server name
|
||
|
internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) {
|
||
|
//-----------------
|
||
|
// Preconditions
|
||
|
Debug.Assert(null != userOptions && null!=routing);
|
||
|
|
||
|
//-----------------
|
||
|
//Method body
|
||
|
Debug.Assert(routing.ServerName != null, "server name should never be null");
|
||
|
if (routing == null || routing.ServerName == null) {
|
||
|
UserServerName = string.Empty; // ensure user server name is not null
|
||
|
}
|
||
|
else {
|
||
|
UserServerName = string.Format(CultureInfo.InvariantCulture, "{0},{1}", routing.ServerName, routing.Port);
|
||
|
}
|
||
|
PreRoutingServerName = preRoutingServerName;
|
||
|
UserProtocol = TdsEnums.TCP;
|
||
|
SetDerivedNames(UserProtocol, UserServerName);
|
||
|
ResolvedDatabaseName = userOptions.InitialCatalog;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|