You've already forked linux-packaging-mono
							
							
		
			
				
	
	
		
			525 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			525 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| //------------------------------------------------------------------------------
 | |
| // <copyright file="SqlInternalTransaction.cs" company="Microsoft">
 | |
| //     Copyright (c) Microsoft Corporation.  All rights reserved.
 | |
| // </copyright>
 | |
| // <owner current="true" primary="true">Microsoft</owner>
 | |
| // <owner current="true" primary="false">Microsoft</owner>
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| namespace System.Data.SqlClient {
 | |
| 
 | |
|     using System.Data;
 | |
|     using System.Data.Common;
 | |
|     using System.Data.ProviderBase;
 | |
|     using System.Data.Sql;
 | |
|     using System.Data.SqlTypes;
 | |
|     using System.Diagnostics;
 | |
|     using System.Threading;
 | |
| 
 | |
|     internal enum TransactionState {
 | |
|         Pending = 0,
 | |
|         Active = 1,
 | |
|         Aborted = 2,
 | |
|         Committed = 3,
 | |
|         Unknown = 4,
 | |
|     }
 | |
|     
 | |
|     internal enum TransactionType {
 | |
|         LocalFromTSQL   = 1,
 | |
|         LocalFromAPI    = 2,
 | |
|         Delegated       = 3,
 | |
|         Distributed     = 4,
 | |
|         Context         = 5,     // only valid in proc.
 | |
|     };
 | |
| 
 | |
|     sealed internal class SqlInternalTransaction {
 | |
| 
 | |
|         internal const long NullTransactionId = 0;
 | |
| 
 | |
|         private TransactionState            _transactionState;
 | |
|         private TransactionType             _transactionType;
 | |
|         private long                        _transactionId;             // passed in the MARS headers
 | |
|         private int                         _openResultCount;           // passed in the MARS headers
 | |
|         private SqlInternalConnection       _innerConnection;
 | |
|         private bool                        _disposing;                 // used to prevent us from throwing exceptions while we're disposing
 | |
|         private WeakReference               _parent;                    // weak ref to the outer transaction object; needs to be weak to allow GC to occur.
 | |
| 
 | |
|         private  static   int _objectTypeCount; // Bid counter
 | |
|         internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
 | |
| 
 | |
|         internal bool RestoreBrokenConnection { get; set; }
 | |
|         internal bool ConnectionHasBeenRestored { get; set; }
 | |
| 
 | |
|         internal SqlInternalTransaction(SqlInternalConnection innerConnection, TransactionType type, SqlTransaction outerTransaction) : this(innerConnection, type, outerTransaction, NullTransactionId) {
 | |
|         }
 | |
|         
 | |
|         internal SqlInternalTransaction(SqlInternalConnection innerConnection, TransactionType type, SqlTransaction outerTransaction, long transactionId) {
 | |
|             Bid.PoolerTrace("<sc.SqlInternalTransaction.ctor|RES|CPOOL> %d#, Created for connection %d#, outer transaction %d#, Type %d\n",
 | |
|                         ObjectID,
 | |
|                         innerConnection.ObjectID,
 | |
|                         (null != outerTransaction) ? outerTransaction.ObjectID : -1,
 | |
|                         (int)type);
 | |
| 
 | |
|             _innerConnection = innerConnection;
 | |
|             _transactionType = type;
 | |
| 
 | |
|             if (null != outerTransaction) {
 | |
|                 _parent = new WeakReference(outerTransaction);
 | |
|             }
 | |
| 
 | |
|             _transactionId = transactionId;
 | |
|             RestoreBrokenConnection = false;
 | |
|             ConnectionHasBeenRestored = false;
 | |
|         }
 | |
| 
 | |
|         internal bool HasParentTransaction {
 | |
|             get {
 | |
|                 // Return true if we are an API started local transaction, or if we were a TSQL
 | |
|                 // started local transaction and were then wrapped with a parent transaction as
 | |
|                 // a result of a later API begin transaction.
 | |
|                 bool result = ( (TransactionType.LocalFromAPI  == _transactionType) ||
 | |
|                                 (TransactionType.LocalFromTSQL == _transactionType && _parent != null) );
 | |
|                 return result;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsAborted {
 | |
|             get {
 | |
|                 return (TransactionState.Aborted == _transactionState);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsActive {
 | |
|             get {
 | |
|                 return (TransactionState.Active == _transactionState);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsCommitted {
 | |
|             get {
 | |
|                 return (TransactionState.Committed == _transactionState);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsCompleted {
 | |
|             get {
 | |
|                 return (TransactionState.Aborted   == _transactionState
 | |
|                      || TransactionState.Committed == _transactionState 
 | |
|                      || TransactionState.Unknown   == _transactionState);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsContext {
 | |
|             get {
 | |
|                 bool result = (TransactionType.Context == _transactionType);
 | |
|                 return result;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsDelegated {
 | |
|             get {
 | |
|                 bool result = (TransactionType.Delegated == _transactionType);
 | |
|                 return result;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsDistributed {
 | |
|             get {
 | |
|                 bool result = (TransactionType.Distributed == _transactionType);
 | |
|                 return result;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsLocal {
 | |
|             get {
 | |
|                 bool result = (TransactionType.LocalFromTSQL == _transactionType
 | |
|                             || TransactionType.LocalFromAPI  == _transactionType
 | |
|                             || TransactionType.Context       == _transactionType);
 | |
|                 return result;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsOrphaned {
 | |
|             get {
 | |
|                 // An internal transaction is orphaned when its parent has been
 | |
|                 // reclaimed by GC.
 | |
|                 bool result;
 | |
|                 if (null == _parent) {
 | |
|                     // No parent, so we better be LocalFromTSQL.  Should we even return in this case -
 | |
|                     // since it could be argued this is invalid?
 | |
|                     Debug.Assert(false, "Why are we calling IsOrphaned with no parent?");
 | |
|                     Debug.Assert(_transactionType == TransactionType.LocalFromTSQL, "invalid state");
 | |
|                     result = false;
 | |
|                 }
 | |
|                 else if (null == _parent.Target) {
 | |
|                     // We have an parent, but parent was GC'ed.
 | |
|                     result = true;
 | |
|                 }
 | |
|                 else {
 | |
|                     // We have an parent, and parent is alive.
 | |
|                     result = false;
 | |
|                 }
 | |
| 
 | |
|                 return result;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal bool IsZombied {
 | |
|             get {
 | |
|                 return (null == _innerConnection);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal int ObjectID {
 | |
|             get {
 | |
|                 return _objectID;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal int OpenResultsCount {
 | |
|             get {
 | |
|                 return _openResultCount;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal SqlTransaction Parent {
 | |
|             get {
 | |
|                 SqlTransaction result = null;
 | |
|                 // Should we protect against this, since this probably is an invalid state?
 | |
|                 Debug.Assert(null != _parent, "Why are we calling Parent with no parent?");
 | |
|                 if (null != _parent) {
 | |
|                     result = (SqlTransaction)_parent.Target;
 | |
|                 }
 | |
|                 return result;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal long TransactionId {
 | |
|             get {
 | |
|                 return _transactionId;
 | |
|             }
 | |
|             set {
 | |
|                 Debug.Assert(NullTransactionId == _transactionId, "setting transaction cookie while one is active?");
 | |
|                 _transactionId = value;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void Activate () {
 | |
|             _transactionState = TransactionState.Active;
 | |
|         }
 | |
| 
 | |
|         private void CheckTransactionLevelAndZombie() {
 | |
|             try {
 | |
|                 if (!IsZombied && GetServerTransactionLevel() == 0) {
 | |
|                     // If not zombied, not closed, and not in transaction, zombie.
 | |
|                     Zombie();
 | |
|                 }
 | |
|             }
 | |
|             catch (Exception e) {
 | |
|                 // 
 | |
|                 if (!ADP.IsCatchableExceptionType(e)) {
 | |
|                     throw;
 | |
|                 }
 | |
| 
 | |
|                 ADP.TraceExceptionWithoutRethrow(e);
 | |
|                 Zombie(); // If exception caught when trying to check level, zombie.
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void CloseFromConnection() {
 | |
|             SqlInternalConnection innerConnection = _innerConnection;
 | |
| 
 | |
|             Debug.Assert (innerConnection != null,"How can we be here if the connection is null?");
 | |
| 
 | |
|             Bid.PoolerTrace("<sc.SqlInteralTransaction.CloseFromConnection|RES|CPOOL> %d#, Closing\n", ObjectID);
 | |
|             bool processFinallyBlock = true;
 | |
|             try {
 | |
|                 innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.IfRollback, null, IsolationLevel.Unspecified, null, false);
 | |
|             }
 | |
|             catch (Exception e) {
 | |
|                 processFinallyBlock = ADP.IsCatchableExceptionType(e);
 | |
|                 throw;
 | |
|             }
 | |
|             finally {
 | |
|                 TdsParser.ReliabilitySection.Assert("unreliable call to CloseFromConnection");  // you need to setup for a thread abort somewhere before you call this method
 | |
|                 if (processFinallyBlock) {
 | |
|                     // Always ensure we're zombied; Yukon will send an EnvChange that
 | |
|                     // will cause the zombie, but only if we actually go to the wire;
 | |
|                     // Sphinx and Shiloh won't send the env change, so we have to handle
 | |
|                     // them ourselves.
 | |
|                     Zombie();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void Commit() {
 | |
|             IntPtr hscp;
 | |
|             Bid.ScopeEnter(out hscp, "<sc.SqlInternalTransaction.Commit|API> %d#", ObjectID);
 | |
| 
 | |
|             if (_innerConnection.IsLockedForBulkCopy) {
 | |
|                 throw SQL.ConnectionLockedForBcpEvent();
 | |
|             }
 | |
| 
 | |
|             _innerConnection.ValidateConnectionForExecute(null);
 | |
| 
 | |
|             try {
 | |
|                 // If this transaction has been completed, throw exception since it is unusable.
 | |
|                 try {
 | |
|                     // COMMIT ignores transaction names, and so there is no reason to pass it anything.  COMMIT
 | |
|                     // simply commits the transaction from the most recent BEGIN, nested or otherwise.
 | |
|                     _innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, IsolationLevel.Unspecified, null, false);
 | |
| 
 | |
|                     // SQL BU DT 291159 - perform full Zombie on pre-Yukon, but do not actually
 | |
|                     // complete internal transaction until informed by server in the case of Yukon
 | |
|                     // or later.
 | |
|                     if (!IsZombied && !_innerConnection.IsYukonOrNewer) {
 | |
|                         // Since nested transactions are no longer allowed, set flag to false.
 | |
|                         // This transaction has been completed.
 | |
|                         Zombie();
 | |
|                     }
 | |
|                     else {
 | |
|                         ZombieParent();
 | |
|                     }
 | |
|                 }
 | |
|                 catch (Exception e) {
 | |
|                     // 
 | |
|                     if (ADP.IsCatchableExceptionType(e)) {
 | |
|                         CheckTransactionLevelAndZombie();
 | |
|                     }
 | |
| 
 | |
|                     throw;
 | |
|                 }
 | |
|             }
 | |
|             finally {
 | |
|                 Bid.ScopeLeave(ref hscp);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void Completed(TransactionState transactionState) {
 | |
|             Debug.Assert (TransactionState.Active < transactionState, "invalid transaction completion state?");
 | |
|             _transactionState = transactionState;
 | |
|             Zombie();
 | |
|         }
 | |
| 
 | |
|         internal Int32 DecrementAndObtainOpenResultCount() {
 | |
|             Int32 openResultCount = Interlocked.Decrement(ref _openResultCount);
 | |
|             if (openResultCount < 0) {
 | |
|                 throw SQL.OpenResultCountExceeded();
 | |
|             }
 | |
|             return openResultCount;
 | |
|         }
 | |
| 
 | |
|         internal void Dispose() {
 | |
|             this.Dispose(true);
 | |
|             System.GC.SuppressFinalize(this);
 | |
|         }
 | |
| 
 | |
|         private /*protected override*/ void Dispose(bool disposing) {
 | |
|             Bid.PoolerTrace("<sc.SqlInteralTransaction.Dispose|RES|CPOOL> %d#, Disposing\n", ObjectID);
 | |
|             if (disposing) {
 | |
|                 if (null != _innerConnection) {
 | |
|                     // implicitly rollback if transaction still valid
 | |
|                     _disposing = true;
 | |
|                     this.Rollback();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private int GetServerTransactionLevel() {
 | |
|             // This function is needed for those times when it is impossible to determine the server's
 | |
|             // transaction level, unless the user's arguments were parsed - which is something we don't want
 | |
|             // to do.  An example when it is impossible to determine the level is after a rollback.
 | |
| 
 | |
|             // 
 | |
| 
 | |
|             using (SqlCommand transactionLevelCommand = new SqlCommand("set @out = @@trancount", (SqlConnection)(_innerConnection.Owner))) {
 | |
|                 transactionLevelCommand.Transaction = Parent;
 | |
| 
 | |
|                 SqlParameter parameter = new SqlParameter("@out", SqlDbType.Int);
 | |
|                 parameter.Direction    = ParameterDirection.Output;
 | |
|                 transactionLevelCommand.Parameters.Add(parameter);
 | |
| 
 | |
|                 // 
 | |
| 
 | |
|                 transactionLevelCommand.RunExecuteReader(0, RunBehavior.UntilDone, false /* returnDataStream */, ADP.GetServerTransactionLevel);
 | |
| 
 | |
|                 return (int)parameter.Value;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal Int32 IncrementAndObtainOpenResultCount() {
 | |
|             Int32 openResultCount = Interlocked.Increment(ref _openResultCount);
 | |
| 
 | |
|             if (openResultCount < 0) {
 | |
|                 throw SQL.OpenResultCountExceeded();
 | |
|             }
 | |
|             return openResultCount;
 | |
|         }
 | |
| 
 | |
|         internal void InitParent(SqlTransaction transaction) {
 | |
|             Debug.Assert(_parent == null, "Why do we have a parent on InitParent?");
 | |
|             _parent = new WeakReference(transaction);
 | |
|         }
 | |
| 
 | |
|         internal void Rollback() {
 | |
|             IntPtr hscp;
 | |
|             Bid.ScopeEnter(out hscp, "<sc.SqlInternalTransaction.Rollback|API> %d#", ObjectID);
 | |
| 
 | |
|             if (_innerConnection.IsLockedForBulkCopy) {
 | |
|                 throw SQL.ConnectionLockedForBcpEvent();
 | |
|             }
 | |
| 
 | |
|             _innerConnection.ValidateConnectionForExecute(null);
 | |
| 
 | |
|             try {
 | |
|                 try {
 | |
|                     // If no arg is given to ROLLBACK it will rollback to the outermost begin - rolling back
 | |
|                     // all nested transactions as well as the outermost transaction.
 | |
|                     _innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.IfRollback, null, IsolationLevel.Unspecified, null, false);
 | |
| 
 | |
|                     // Since Rollback will rollback to outermost begin, no need to check
 | |
|                     // server transaction level.  This transaction has been completed.
 | |
|                     Zombie();
 | |
|                 }
 | |
|                 catch (Exception e) {
 | |
|                     // 
 | |
|                     if (ADP.IsCatchableExceptionType(e)) {
 | |
|                         CheckTransactionLevelAndZombie();
 | |
| 
 | |
|                         if (!_disposing) {
 | |
|                             throw;
 | |
|                         }
 | |
|                     }
 | |
|                     else {
 | |
|                         throw;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             finally {
 | |
|                 Bid.ScopeLeave(ref hscp);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void Rollback(string transactionName) {
 | |
|             IntPtr hscp;
 | |
|             Bid.ScopeEnter(out hscp, "<sc.SqlInternalTransaction.Rollback|API> %d#, transactionName='%ls'", ObjectID, transactionName);
 | |
| 
 | |
|             if (_innerConnection.IsLockedForBulkCopy) {
 | |
|                 throw SQL.ConnectionLockedForBcpEvent();
 | |
|             }
 | |
| 
 | |
|             _innerConnection.ValidateConnectionForExecute(null);
 | |
| 
 | |
|             try {
 | |
|                 // ROLLBACK takes either a save point name or a transaction name.  It will rollback the
 | |
|                 // transaction to either the save point with the save point name or begin with the
 | |
|                 // transacttion name.  NOTE: for simplicity it is possible to give all save point names
 | |
|                 // the same name, and ROLLBACK will simply rollback to the most recent save point with the
 | |
|                 // save point name.
 | |
|                 if (ADP.IsEmpty(transactionName))
 | |
|                     throw SQL.NullEmptyTransactionName();
 | |
| 
 | |
|                 try {
 | |
|                     _innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Rollback, transactionName, IsolationLevel.Unspecified, null, false);
 | |
| 
 | |
|                     if (!IsZombied && !_innerConnection.IsYukonOrNewer) {
 | |
|                         // Check if Zombied before making round-trip to server.
 | |
|                         // Against Yukon we receive an envchange on the ExecuteTransaction above on the
 | |
|                         // parser that calls back into SqlTransaction for the Zombie() call.
 | |
|                         CheckTransactionLevelAndZombie();
 | |
|                     }
 | |
|                 }
 | |
|                 catch (Exception e) {
 | |
|                     // 
 | |
|                     if (ADP.IsCatchableExceptionType(e)) {
 | |
|                         CheckTransactionLevelAndZombie();
 | |
|                     }
 | |
|                     throw;
 | |
|                 }
 | |
|             }
 | |
|             finally {
 | |
|                 Bid.ScopeLeave(ref hscp);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void Save(string savePointName) {
 | |
|             IntPtr hscp;
 | |
|             Bid.ScopeEnter(out hscp, "<sc.SqlInternalTransaction.Save|API> %d#, savePointName='%ls'", ObjectID, savePointName);
 | |
| 
 | |
|             _innerConnection.ValidateConnectionForExecute(null);
 | |
| 
 | |
|             try {
 | |
|                 // ROLLBACK takes either a save point name or a transaction name.  It will rollback the
 | |
|                 // transaction to either the save point with the save point name or begin with the
 | |
|                 // transacttion name.  So, to rollback a nested transaction you must have a save point.
 | |
|                 // SAVE TRANSACTION MUST HAVE AN ARGUMENT!!!  Save Transaction without an arg throws an
 | |
|                 // exception from the server.  So, an overload for SaveTransaction without an arg doesn't make
 | |
|                 // sense to have.  Save Transaction does not affect the transaction level.
 | |
|                 if (ADP.IsEmpty(savePointName))
 | |
|                     throw SQL.NullEmptyTransactionName();
 | |
| 
 | |
|                 try {
 | |
|                     _innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Save, savePointName, IsolationLevel.Unspecified, null, false);
 | |
|                 }
 | |
|                 catch (Exception e) {
 | |
|                     // 
 | |
|                     if (ADP.IsCatchableExceptionType(e)) {
 | |
|                         CheckTransactionLevelAndZombie();
 | |
|                     }
 | |
| 
 | |
|                     throw;
 | |
|                 }
 | |
|             }
 | |
|             finally {
 | |
|                 Bid.ScopeLeave(ref hscp);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal void Zombie() {
 | |
|             // Called by several places in the code to ensure that the outer
 | |
|             // transaction object has been zombied and the parser has broken
 | |
|             // it's reference to us.
 | |
| 
 | |
|             // NOTE: we'll be called from the TdsParser when it gets appropriate
 | |
|             // ENVCHANGE events that indicate the transaction has completed, however
 | |
|             // we cannot rely upon those events occuring in the case of pre-Yukon
 | |
|             // servers (and when we don't go to the wire because the connection
 | |
|             // is broken) so we can also be called from the Commit/Rollback/Save
 | |
|             // methods to handle that case as well.
 | |
| 
 | |
|             // There are two parts to a full zombie:
 | |
|             // 1) Zombie parent and disconnect outer transaction from internal transaction
 | |
|             // 2) Disconnect internal transaction from connection and parser
 | |
|             // Number 1 needs to be done whenever a SqlTransaction object is completed.  Number
 | |
|             // 2 is only done when a transaction is actually completed.  Since users can begin
 | |
|             // transactions both in and outside of the API, and since nested begins are not actual
 | |
|             // transactions we need to distinguish between #1 and #2.  See SQL BU DT 291159
 | |
|             // for further details.
 | |
| 
 | |
|             ZombieParent();
 | |
| 
 | |
|             SqlInternalConnection innerConnection = _innerConnection;
 | |
|             _innerConnection = null;
 | |
| 
 | |
|             if (null != innerConnection) {
 | |
|                 innerConnection.DisconnectTransaction(this);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void ZombieParent() {
 | |
|             if (null != _parent) {
 | |
|                 SqlTransaction parent = (SqlTransaction) _parent.Target;
 | |
|                 if (null != parent) {
 | |
|                     parent.Zombie();
 | |
|                 }
 | |
|                 _parent = null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal string TraceString() {
 | |
|             return String.Format(/*IFormatProvider*/ null, "(ObjId={0}, tranId={1}, state={2}, type={3}, open={4}, disp={5}",
 | |
|                         ObjectID, _transactionId, _transactionState, _transactionType, _openResultCount, _disposing);
 | |
|         }
 | |
|     }
 | |
| }
 |