internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction) { this._isolationLevel = iso; this._connection = con; if (internalTransaction == null) { this._internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this); } else { this._internalTransaction = internalTransaction; this._internalTransaction.InitParent(this); } }
internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con, IsolationLevel iso, SqlInternalTransaction internalTransaction) { SqlConnection.VerifyExecutePermission(); _isolationLevel = iso; _connection = con; if (internalTransaction == null) { _internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this); } else { Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!"); _internalTransaction = internalTransaction; _internalTransaction.InitParent(this); } }
internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con, IsolationLevel iso, SqlInternalTransaction internalTransaction) { _isolationLevel = iso; _connection = con; if (internalTransaction == null) { _internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this); } else { Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!"); _internalTransaction = internalTransaction; _internalTransaction.InitParent(this); } }
internal void AutomaticEnlistment() { SysTx.Transaction currentSystemTransaction = ADP.GetCurrentTransaction(); // NOTE: Must be first to ensure _smiContext.ContextTransaction is set! SysTx.Transaction contextTransaction = _smiContext.ContextTransaction; // returns the transaction that was handed to SysTx that wraps the ContextTransactionId. long contextTransactionId = _smiContext.ContextTransactionId; if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.AutomaticEnlistment|ADV> %d#, contextTransactionId=0x%I64x, contextTransaction=%d#, currentSystemTransaction=%d#.\n", base.ObjectID, contextTransactionId, (null != contextTransaction) ? contextTransaction.GetHashCode() : 0, (null != currentSystemTransaction) ? currentSystemTransaction.GetHashCode() : 0); } if (SqlInternalTransaction.NullTransactionId != contextTransactionId) { if (null != currentSystemTransaction && contextTransaction != currentSystemTransaction) { throw SQL.NestedTransactionScopesNotSupported(); // can't use TransactionScope(RequiresNew) inside a Sql Transaction. } if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.AutomaticEnlistment|ADV> %d#, using context transaction with transactionId=0x%I64x\n", base.ObjectID, contextTransactionId); } _currentTransaction = new SqlInternalTransaction(this, TransactionType.Context, null, contextTransactionId); ContextTransaction = contextTransaction; } else if (null == currentSystemTransaction) { _currentTransaction = null; // there really isn't a transaction. if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.AutomaticEnlistment|ADV> %d#, no transaction.\n", base.ObjectID); } } else { if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.AutomaticEnlistment|ADV> %d#, using current System.Transaction.\n", base.ObjectID); } base.Enlist(currentSystemTransaction); } }
private void TransactionEnded(long transactionId, TransactionState transactionState) { // When we get notification of a completed transaction // we null out the current transaction. if (null != _currentTransaction) { #if DEBUG // Check null for case where Begin and Rollback obtained in the same message. if (0 != _currentTransaction.TransactionId) { Debug.Assert(_currentTransaction.TransactionId == transactionId, "transaction id's are not equal!"); } #endif _currentTransaction.Completed(transactionState); _currentTransaction = null; } }
internal override void ExecuteTransaction(SqlInternalConnection.TransactionRequest transactionRequest, string transactionName, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) { if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.ExecuteTransaction|ADV> %d#, transactionRequest=%ls, transactionName='%ls', isolationLevel=%ls, internalTransaction=#%d transactionId=0x%I64x.\n", base.ObjectID, transactionRequest.ToString(), (transactionName != null) ? transactionName : "null", iso.ToString(), (internalTransaction != null) ? internalTransaction.ObjectID : 0, (internalTransaction != null) ? internalTransaction.TransactionId : 0L); } switch (transactionRequest) { case SqlInternalConnection.TransactionRequest.Begin: try { this._pendingTransaction = internalTransaction; this._smiConnection.BeginTransaction(transactionName, iso, this._smiEventSink); goto Label_0121; } finally { this._pendingTransaction = null; } break; case SqlInternalConnection.TransactionRequest.Promote: base.PromotedDTCToken = this._smiConnection.PromoteTransaction(this._currentTransaction.TransactionId, this._smiEventSink); goto Label_0121; case SqlInternalConnection.TransactionRequest.Commit: break; case SqlInternalConnection.TransactionRequest.Rollback: case SqlInternalConnection.TransactionRequest.IfRollback: this._smiConnection.RollbackTransaction(this._currentTransaction.TransactionId, transactionName, this._smiEventSink); goto Label_0121; case SqlInternalConnection.TransactionRequest.Save: this._smiConnection.CreateTransactionSavePoint(this._currentTransaction.TransactionId, transactionName, this._smiEventSink); goto Label_0121; default: goto Label_0121; } this._smiConnection.CommitTransaction(this._currentTransaction.TransactionId, this._smiEventSink); Label_0121: this._smiEventSink.ProcessMessagesAndThrow(); }
public void Initialize() { // if we get here, then we know for certain that we're the delegated // transaction. SqlInternalConnection connection = _connection; SqlConnection usersConnection = connection.Connection; RuntimeHelpers.PrepareConstrainedRegions(); try { if (connection.IsEnlistedInTransaction) { // defect first connection.EnlistNull(); } _internalTransaction = new SqlInternalTransaction(connection, TransactionType.Delegated, null); connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Begin, null, _isolationLevel, _internalTransaction, true); // Handle case where ExecuteTran didn't produce a new transaction, but also didn't throw. if (null == connection.CurrentTransaction) { connection.DoomThisConnection(); throw ADP.InternalError(ADP.InternalErrorCode.UnknownTransactionFailure); } _active = true; } catch (System.OutOfMemoryException e) { usersConnection.Abort(e); throw; } catch (System.StackOverflowException e) { usersConnection.Abort(e); throw; } catch (System.Threading.ThreadAbortException e) { usersConnection.Abort(e); throw; } }
public override void Rollback() { Exception e = null; Guid operationId = s_diagnosticListener.WriteTransactionRollbackBefore(_isolationLevel, _connection, null); if (IsYukonPartialZombie) { // Put something in the trace in case a customer has an issue _internalTransaction = null; // yukon zombification } else { ZombieCheck(); SqlStatistics statistics = null; try { statistics = SqlStatistics.StartTimer(Statistics); _isFromAPI = true; _internalTransaction.Rollback(); } catch (Exception ex) { e = ex; throw; } finally { if (e != null) { s_diagnosticListener.WriteTransactionRollbackError(operationId, _isolationLevel, _connection, null, e); } else { s_diagnosticListener.WriteTransactionRollbackAfter(operationId, _isolationLevel, _connection, null); } _isFromAPI = false; SqlStatistics.StopTimer(statistics); } } }
//////////////////////////////////////////////////////////////////////////////////////// // INTERNAL METHODS //////////////////////////////////////////////////////////////////////////////////////// internal void Zombie() { // SQLBUDT #402544 For Yukon, we have to defer "zombification" until // we get past the users' next rollback, else we'll // throw an exception there that is a breaking change. // Of course, if the connection is aready closed, // then we're free to zombify... SqlInternalConnection internalConnection = (_connection.InnerConnection as SqlInternalConnection); if (null != internalConnection && internalConnection.IsYukonOrNewer && !_isFromAPI) { if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlTransaction.Zombie|ADV> %d# yukon deferred zombie\n", ObjectID); } } else { _internalTransaction = null; // pre-yukon zombification } }
internal void AutomaticEnlistment() { Transaction currentTransaction = ADP.GetCurrentTransaction(); Transaction contextTransaction = this._smiContext.ContextTransaction; long contextTransactionId = this._smiContext.ContextTransactionId; if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.AutomaticEnlistment|ADV> %d#, contextTransactionId=0x%I64x, contextTransaction=%d#, currentSystemTransaction=%d#.\n", base.ObjectID, contextTransactionId, (null != contextTransaction) ? contextTransaction.GetHashCode() : 0, (null != currentTransaction) ? currentTransaction.GetHashCode() : 0); } if (0L != contextTransactionId) { if ((null != currentTransaction) && (contextTransaction != currentTransaction)) { throw SQL.NestedTransactionScopesNotSupported(); } if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.AutomaticEnlistment|ADV> %d#, using context transaction with transactionId=0x%I64x\n", base.ObjectID, contextTransactionId); } this._currentTransaction = new SqlInternalTransaction(this, TransactionType.Context, null, contextTransactionId); this.ContextTransaction = contextTransaction; } else if (null == currentTransaction) { this._currentTransaction = null; if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.AutomaticEnlistment|ADV> %d#, no transaction.\n", base.ObjectID); } } else { if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.AutomaticEnlistment|ADV> %d#, using current System.Transaction.\n", base.ObjectID); } base.Enlist(currentTransaction); } }
private void TransactionStarted(long transactionId, bool isDistributed) { // When we get notification from the server of a new // transaction, we move any pending transaction over to // the current transaction, then we store the token in it. // if there isn't a pending transaction, then it's either // a TSQL transaction or a distributed transaction. Debug.Assert(null == _currentTransaction, "non-null current transaction with an env change"); _currentTransaction = _pendingTransaction; _pendingTransaction = null; if (null != _currentTransaction) { _currentTransaction.TransactionId = transactionId; // this is defined as a ULongLong in the server and in the TDS Spec. } else { TransactionType transactionType = (isDistributed) ? TransactionType.Distributed : TransactionType.LocalFromTSQL; _currentTransaction = new SqlInternalTransaction(this, transactionType, null, transactionId); } _currentTransaction.Activate(); // SQLBUDT #376531 -- ensure this is activated to prevent asserts later. }
public void Initialize() { SqlInternalConnection innerConnection = this._connection; SqlConnection connection = innerConnection.Connection; Bid.Trace("<sc.SqlDelegatedTransaction.Initialize|RES|CPOOL> %d#, Connection %d#, delegating transaction.\n", this.ObjectID, innerConnection.ObjectID); RuntimeHelpers.PrepareConstrainedRegions(); try { if (innerConnection.IsEnlistedInTransaction) { Bid.Trace("<sc.SqlDelegatedTransaction.Initialize|RES|CPOOL> %d#, Connection %d#, was enlisted, now defecting.\n", this.ObjectID, innerConnection.ObjectID); innerConnection.EnlistNull(); } this._internalTransaction = new SqlInternalTransaction(innerConnection, TransactionType.Delegated, null); innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Begin, null, this._isolationLevel, this._internalTransaction, true); if (innerConnection.CurrentTransaction == null) { innerConnection.DoomThisConnection(); throw ADP.InternalError(ADP.InternalErrorCode.UnknownTransactionFailure); } this._active = true; } catch (OutOfMemoryException exception3) { connection.Abort(exception3); throw; } catch (StackOverflowException exception2) { connection.Abort(exception2); throw; } catch (ThreadAbortException exception) { connection.Abort(exception); throw; } }
protected override void InternalDeactivate() { if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.Deactivate|ADV> %d#, Deactivating.\n", base.ObjectID); } if (!this.IsNonPoolableTransactionRoot) { base.Enlist(null); } if (this._currentTransaction != null) { if (this._currentTransaction.IsContext) { this._currentTransaction = null; } else if (this._currentTransaction.IsLocal) { this._currentTransaction.CloseFromConnection(); } } this.ContextTransaction = null; this._isInUse = 0; }
internal void ExecuteTransactionYukon(SqlInternalConnection.TransactionRequest transactionRequest, string transactionName, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) { TdsEnums.TransactionManagerRequestType begin = TdsEnums.TransactionManagerRequestType.Begin; TdsEnums.TransactionManagerIsolationLevel readCommitted = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted; switch (iso) { case System.Data.IsolationLevel.Unspecified: readCommitted = TdsEnums.TransactionManagerIsolationLevel.Unspecified; break; case System.Data.IsolationLevel.Chaos: throw SQL.NotSupportedIsolationLevel(iso); case System.Data.IsolationLevel.ReadUncommitted: readCommitted = TdsEnums.TransactionManagerIsolationLevel.ReadUncommitted; break; case System.Data.IsolationLevel.Serializable: readCommitted = TdsEnums.TransactionManagerIsolationLevel.Serializable; break; case System.Data.IsolationLevel.Snapshot: readCommitted = TdsEnums.TransactionManagerIsolationLevel.Snapshot; break; case System.Data.IsolationLevel.ReadCommitted: readCommitted = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted; break; case System.Data.IsolationLevel.RepeatableRead: readCommitted = TdsEnums.TransactionManagerIsolationLevel.RepeatableRead; break; default: throw ADP.InvalidIsolationLevel(iso); } TdsParserStateObject session = this._parser._physicalStateObj; TdsParser parser = this._parser; bool flag = false; bool lockTaken = false; try { switch (transactionRequest) { case SqlInternalConnection.TransactionRequest.Begin: begin = TdsEnums.TransactionManagerRequestType.Begin; break; case SqlInternalConnection.TransactionRequest.Promote: begin = TdsEnums.TransactionManagerRequestType.Promote; break; case SqlInternalConnection.TransactionRequest.Commit: begin = TdsEnums.TransactionManagerRequestType.Commit; break; case SqlInternalConnection.TransactionRequest.Rollback: case SqlInternalConnection.TransactionRequest.IfRollback: begin = TdsEnums.TransactionManagerRequestType.Rollback; break; case SqlInternalConnection.TransactionRequest.Save: begin = TdsEnums.TransactionManagerRequestType.Save; break; } if ((internalTransaction != null) && internalTransaction.IsDelegated) { if (!this._parser.MARSOn) { if (internalTransaction.OpenResultsCount != 0) { throw SQL.CannotCompleteDelegatedTransactionWithOpenResults(); } Monitor.Enter(session, ref lockTaken); if (internalTransaction.OpenResultsCount != 0) { throw SQL.CannotCompleteDelegatedTransactionWithOpenResults(); } } else { session = this._parser.GetSession(this); flag = true; } } this._parser.TdsExecuteTransactionManagerRequest(null, begin, transactionName, readCommitted, base.ConnectionOptions.ConnectTimeout, internalTransaction, session, isDelegateControlRequest); } finally { if (flag) { parser.PutSession(session); } if (lockTaken) { Monitor.Exit(session); } } }
// Write mars header data, not including the mars header length private void WriteMarsHeaderData(TdsParserStateObject stateObj, SqlInternalTransaction transaction) { // Function to send over additional payload header data for Yukon and beyond only. // These are not necessary - can have local started in distributed. // Debug.Assert(!(null != sqlTransaction && null != distributedTransaction), "Error to have local (api started) and distributed transaction at the same time!"); // Debug.Assert(!(null != _userStartedLocalTransaction && null != distributedTransaction), "Error to have local (started outside of the api) and distributed transaction at the same time!"); // We may need to update the mars header length if mars header is changed in the future WriteShort(TdsEnums.HEADERTYPE_MARS, stateObj); if (null != transaction && SqlInternalTransaction.NullTransactionId != transaction.TransactionId) { WriteLong(transaction.TransactionId, stateObj); WriteInt(stateObj.IncrementAndObtainOpenResultCount(transaction), stateObj); } else { WriteLong(SqlInternalTransaction.NullTransactionId, stateObj); WriteInt(stateObj.IncrementAndObtainOpenResultCount(null), stateObj); } }
abstract internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction);
// Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady) { Debug.Assert((SniContext.Undefined != stateObj.SniContext) && // SniContext must not be Undefined ((stateObj._attentionSent) || ((SniContext.Snix_Execute != stateObj.SniContext) && (SniContext.Snix_SendRows != stateObj.SniContext))), // SniContext should not be Execute or SendRows unless attention was sent (and, therefore, we are looking for an ACK) String.Format("Unexpected SniContext on call to TryRun; SniContext={0}", stateObj.SniContext)); if (TdsParserState.Broken == State || TdsParserState.Closed == State) { dataReady = true; return true; // Just in case this is called in a loop, expecting data to be returned. } dataReady = false; do { // If there is data ready, but we didn't exit the loop, then something is wrong Debug.Assert(!dataReady, "dataReady not expected - did we forget to skip the row?"); if (stateObj._internalTimeout) { runBehavior = RunBehavior.Attention; } if (TdsParserState.Broken == State || TdsParserState.Closed == State) break; // jump out of the loop if the state is already broken or closed. if (!stateObj._accumulateInfoEvents && (stateObj._pendingInfoEvents != null)) { if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) { SqlConnection connection = null; if (_connHandler != null) connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref // We are omitting checks for error.Class in the code below (see processing of INFO) since we know (and assert) that error class // error.Class < TdsEnums.MIN_ERROR_CLASS for info message. // Also we know that TdsEnums.MIN_ERROR_CLASS<TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS if ((connection != null) && connection.FireInfoMessageEventOnUserErrors) { foreach (SqlError error in stateObj._pendingInfoEvents) FireInfoMessageEvent(connection, stateObj, error); } else foreach (SqlError error in stateObj._pendingInfoEvents) stateObj.AddWarning(error); } stateObj._pendingInfoEvents = null; } byte token; if (!stateObj.TryReadByte(out token)) { return false; } if (!IsValidTdsToken(token)) { Debug.Assert(false, String.Format((IFormatProvider)null, "unexpected token; token = {0,-2:X2}", token)); _state = TdsParserState.Broken; _connHandler.BreakConnection(); throw SQL.ParsingError(); } int tokenLength; if (!TryGetTokenLength(token, stateObj, out tokenLength)) { return false; } switch (token) { case TdsEnums.SQLERROR: case TdsEnums.SQLINFO: { if (token == TdsEnums.SQLERROR) { stateObj._errorTokenReceived = true; // Keep track of the fact error token was received - for Done processing. } SqlError error; if (!TryProcessError(token, stateObj, out error)) { return false; } if (token == TdsEnums.SQLINFO && stateObj._accumulateInfoEvents) { Debug.Assert(error.Class < TdsEnums.MIN_ERROR_CLASS, "INFO with class > TdsEnums.MIN_ERROR_CLASS"); if (stateObj._pendingInfoEvents == null) stateObj._pendingInfoEvents = new List<SqlError>(); stateObj._pendingInfoEvents.Add(error); stateObj._syncOverAsync = true; break; } if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) { // If FireInfoMessageEventOnUserErrors is true, we have to fire event without waiting. // Otherwise we can go ahead and add it to errors/warnings collection. SqlConnection connection = null; if (_connHandler != null) connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref if ((connection != null) && (connection.FireInfoMessageEventOnUserErrors == true) && (error.Class <= TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS)) { // Fire SqlInfoMessage here FireInfoMessageEvent(connection, stateObj, error); } else { // insert error/info into the appropriate exception - warning if info, exception if error if (error.Class < TdsEnums.MIN_ERROR_CLASS) { stateObj.AddWarning(error); } else if (error.Class < TdsEnums.FATAL_ERROR_CLASS) { // Continue results processing for all non-fatal errors (<20) stateObj.AddError(error); // Add it to collection - but do NOT change run behavior UNLESS // we are in an ExecuteReader call - at which time we will be throwing // anyways so we need to consume all errors. This is not the case // if we have already given out a reader. If we have already given out // a reader we need to throw the error but not halt further processing. We used to // halt processing. if (null != dataStream) { if (!dataStream.IsInitialized) { runBehavior = RunBehavior.UntilDone; } } } else { stateObj.AddError(error); // Else we have a fatal error and we need to change the behavior // since we want the complete error information in the exception. // Besides - no further results will be received. runBehavior = RunBehavior.UntilDone; } } } else if (error.Class >= TdsEnums.FATAL_ERROR_CLASS) { stateObj.AddError(error); } break; } case TdsEnums.SQLCOLINFO: { if (null != dataStream) { _SqlMetaDataSet metaDataSet; if (!TryProcessColInfo(dataStream.MetaData, dataStream, stateObj, out metaDataSet)) { return false; } if (!dataStream.TrySetMetaData(metaDataSet, false)) { return false; } dataStream.BrowseModeInfoConsumed = true; } else { // no dataStream if (!stateObj.TrySkipBytes(tokenLength)) { return false; } } break; } case TdsEnums.SQLDONE: case TdsEnums.SQLDONEPROC: case TdsEnums.SQLDONEINPROC: { // RunBehavior can be modified if (!TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj)) { return false; } if ((token == TdsEnums.SQLDONEPROC) && (cmdHandler != null)) { cmdHandler.OnDoneProc(); } break; } case TdsEnums.SQLORDER: { // don't do anything with the order token so read off the pipe if (!stateObj.TrySkipBytes(tokenLength)) { return false; } break; } case TdsEnums.SQLALTMETADATA: { stateObj.CloneCleanupAltMetaDataSetArray(); if (stateObj._cleanupAltMetaDataSetArray == null) { // create object on demand (lazy creation) stateObj._cleanupAltMetaDataSetArray = new _SqlMetaDataSetCollection(); } _SqlMetaDataSet cleanupAltMetaDataSet; if (!TryProcessAltMetaData(tokenLength, stateObj, out cleanupAltMetaDataSet)) { return false; } stateObj._cleanupAltMetaDataSetArray.SetAltMetaData(cleanupAltMetaDataSet); if (null != dataStream) { byte metadataConsumedByte; if (!stateObj.TryPeekByte(out metadataConsumedByte)) { return false; } if (!dataStream.TrySetAltMetaDataSet(cleanupAltMetaDataSet, (TdsEnums.SQLALTMETADATA != metadataConsumedByte))) { return false; } } break; } case TdsEnums.SQLALTROW: { if (!stateObj.TryStartNewRow(isNullCompressed: false)) { // altrows are not currently null compressed return false; } // read will call run until dataReady. Must not read any data if returnimmetiately set if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) { ushort altRowId; if (!stateObj.TryReadUInt16(out altRowId)) { // get altRowId return false; } if (!TrySkipRow(stateObj._cleanupAltMetaDataSetArray.GetAltMetaData(altRowId), stateObj)) { // skip altRow return false; } } else { dataReady = true; } break; } case TdsEnums.SQLENVCHANGE: { // ENVCHANGE must be processed synchronously (since it can modify the state of many objects) stateObj._syncOverAsync = true; SqlEnvChange[] env; if (!TryProcessEnvChange(tokenLength, stateObj, out env)) { return false; } for (int ii = 0; ii < env.Length; ii++) { if (env[ii] != null && !this.Connection.IgnoreEnvChange) { switch (env[ii].type) { case TdsEnums.ENV_BEGINTRAN: // When we get notification from the server of a new // transaction, we move any pending transaction over to // the current transaction, then we store the token in it. // if there isn't a pending transaction, then it's either // a TSQL transaction or a distributed transaction. Debug.Assert(null == _currentTransaction, "non-null current transaction with an ENV Change"); _currentTransaction = _pendingTransaction; _pendingTransaction = null; if (null != _currentTransaction) { _currentTransaction.TransactionId = env[ii].newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec. } else { TransactionType transactionType = TransactionType.LocalFromTSQL; _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env[ii].newLongValue); } if (null != _statistics && !_statisticsIsInTransaction) { _statistics.SafeIncrement(ref _statistics._transactions); } _statisticsIsInTransaction = true; break; case TdsEnums.ENV_COMMITTRAN: // SQLHOT 483 // Must clear the retain id if the server-side transaction ends by anything other // than rollback. goto case TdsEnums.ENV_ROLLBACKTRAN; case TdsEnums.ENV_ROLLBACKTRAN: // When we get notification of a completed transaction // we null out the current transaction. if (null != _currentTransaction) { #if DEBUG // Check null for case where Begin and Rollback obtained in the same message. if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId) { Debug.Assert(_currentTransaction.TransactionId != env[ii].newLongValue, "transaction id's are not equal!"); } #endif if (TdsEnums.ENV_COMMITTRAN == env[ii].type) { _currentTransaction.Completed(TransactionState.Committed); } else if (TdsEnums.ENV_ROLLBACKTRAN == env[ii].type) { // Hold onto transaction id if distributed tran is rolled back. This must // be sent to the server on subsequent executions even though the transaction // is considered to be rolled back. _currentTransaction.Completed(TransactionState.Aborted); } else { _currentTransaction.Completed(TransactionState.Unknown); } _currentTransaction = null; } _statisticsIsInTransaction = false; break; case TdsEnums.ENV_ENLISTDTC: case TdsEnums.ENV_DEFECTDTC: case TdsEnums.ENV_TRANSACTIONENDED: Debug.Assert(false, "Should have thrown if DTC token encountered"); break; default: _connHandler.OnEnvChange(env[ii]); break; } } } break; } case TdsEnums.SQLLOGINACK: { SqlLoginAck ack; if (!TryProcessLoginAck(stateObj, out ack)) { return false; } _connHandler.OnLoginAck(ack); break; } case TdsEnums.SQLFEATUREEXTACK: { if (!TryProcessFeatureExtAck(stateObj)) { return false; } break; } case TdsEnums.SQLSESSIONSTATE: { if (!TryProcessSessionState(stateObj, tokenLength, _connHandler._currentSessionData)) { return false; } break; } case TdsEnums.SQLCOLMETADATA: { if (tokenLength != TdsEnums.VARNULL) { _SqlMetaDataSet metadata; if (!TryProcessMetaData(tokenLength, stateObj, out metadata)) { return false; } stateObj._cleanupMetaData = metadata; } else { if (cmdHandler != null) { stateObj._cleanupMetaData = cmdHandler.MetaData; } } if (null != dataStream) { byte peekedToken; if (!stateObj.TryPeekByte(out peekedToken)) { // temporarily cache next byte return false; } if (!dataStream.TrySetMetaData(stateObj._cleanupMetaData, (TdsEnums.SQLTABNAME == peekedToken || TdsEnums.SQLCOLINFO == peekedToken))) { return false; } } else if (null != bulkCopyHandler) { bulkCopyHandler.SetMetaData(stateObj._cleanupMetaData); } break; } case TdsEnums.SQLROW: case TdsEnums.SQLNBCROW: { Debug.Assert(stateObj._cleanupMetaData != null, "Reading a row, but the metadata is null"); if (token == TdsEnums.SQLNBCROW) { if (!stateObj.TryStartNewRow(isNullCompressed: true, nullBitmapColumnsCount: stateObj._cleanupMetaData.Length)) { return false; } } else { if (!stateObj.TryStartNewRow(isNullCompressed: false)) { return false; } } if (null != bulkCopyHandler) { if (!TryProcessRow(stateObj._cleanupMetaData, bulkCopyHandler.CreateRowBuffer(), bulkCopyHandler.CreateIndexMap(), stateObj)) { return false; } } else if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) { if (!TrySkipRow(stateObj._cleanupMetaData, stateObj)) { // skip rows return false; } } else { dataReady = true; } if (_statistics != null) { _statistics.WaitForDoneAfterRow = true; } break; } case TdsEnums.SQLRETURNSTATUS: int status; if (!stateObj.TryReadInt32(out status)) { return false; } if (cmdHandler != null) { cmdHandler.OnReturnStatus(status); } break; case TdsEnums.SQLRETURNVALUE: { SqlReturnValue returnValue; if (!TryProcessReturnValue(tokenLength, stateObj, out returnValue)) { return false; } if (cmdHandler != null) { cmdHandler.OnReturnValue(returnValue); } break; } case TdsEnums.SQLSSPI: { // token length is length of SSPI data - call ProcessSSPI with it Debug.Assert(stateObj._syncOverAsync, "ProcessSSPI does not support retry, do not attempt asynchronously"); stateObj._syncOverAsync = true; ProcessSSPI(tokenLength); break; } case TdsEnums.SQLTABNAME: { { if (!stateObj.TrySkipBytes(tokenLength)) { return false; } } break; } default: Debug.Assert(false, "Unhandled token: " + token.ToString(CultureInfo.InvariantCulture)); break; } Debug.Assert(stateObj._pendingData || !dataReady, "dataReady is set, but there is no pending data"); } // Loop while data pending & runbehavior not return immediately, OR // if in attention case, loop while no more pending data & attention has not yet been // received. while ((stateObj._pendingData && (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) || (!stateObj._pendingData && stateObj._attentionSent && !stateObj._attentionReceived)); #if DEBUG if ((stateObj._pendingData) && (!dataReady)) { byte token; if (!stateObj.TryPeekByte(out token)) { return false; } Debug.Assert(IsValidTdsToken(token), string.Format("DataReady is false, but next token is not valid: {0,-2:X2}", token)); } #endif if (!stateObj._pendingData) { if (null != CurrentTransaction) { CurrentTransaction.Activate(); } } // if we recieved an attention (but this thread didn't send it) then // we throw an Operation Cancelled error if (stateObj._attentionReceived) { // Dev11 #344723: SqlClient stress hang System_Data!Tcp::ReadSync via a call to SqlDataReader::Close // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent SpinWait.SpinUntil(() => !stateObj._attentionSending); Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent"); if (stateObj._attentionSent) { // Reset attention state. stateObj._attentionSent = false; stateObj._attentionReceived = false; if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj._internalTimeout) { // Add attention error to collection - if not RunBehavior.Clean! stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.OperationCancelled(), "", 0)); } } } if (stateObj.HasErrorOrWarning) { ThrowExceptionAndWarning(stateObj); } return true; }
internal void ExecuteTransactionYukon( TransactionRequest transactionRequest, string transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction ) { 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.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; } } // _parser may be nulled out during TdsExecuteTrannsactionManagerRequest. // Only use local variable after this call. _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel, ConnectionOptions.ConnectTimeout, internalTransaction, stateObj ); } finally { if (mustPutSession) { parser.PutSession(stateObj); } if (releaseConnectionLock) { ThreadHasParserLockForClose = false; _parserLock.Release(); } } }
internal Int32 IncrementAndObtainOpenResultCount(SqlInternalTransaction transaction) { _hasOpenResult = true; if (transaction == null) { // If we are not passed a transaction, we are not executing under a transaction // and thus we should increment the global connection result count. return _parser.IncrementNonTransactedOpenResultCount(); } else { // If we are passed a transaction, we are executing under a transaction // and thus we should increment the transaction's result count. _executedUnderTransaction = transaction; return transaction.IncrementAndObtainOpenResultCount(); } }
override internal void ExecuteTransaction( TransactionRequest transactionRequest, string transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) { if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlInternalConnectionSmi.ExecuteTransaction|ADV> %d#, transactionRequest=%ls, transactionName='%ls', isolationLevel=%ls, internalTransaction=#%d transactionId=0x%I64x.\n", base.ObjectID, transactionRequest.ToString(), (null != transactionName) ? transactionName : "null", iso.ToString(), (null != internalTransaction) ? internalTransaction.ObjectID : 0, (null != internalTransaction) ? internalTransaction.TransactionId : SqlInternalTransaction.NullTransactionId ); } switch (transactionRequest) { case TransactionRequest.Begin: try { _pendingTransaction = internalTransaction; // store this for the time being. _smiConnection.BeginTransaction(transactionName, iso, _smiEventSink); } finally { _pendingTransaction = null; } Debug.Assert(_smiEventSink.HasMessages || null != _currentTransaction, "begin transaction without TransactionStarted event?"); break; case TransactionRequest.Commit: Debug.Assert(null != _currentTransaction, "commit transaction without TransactionStarted event?"); _smiConnection.CommitTransaction(_currentTransaction.TransactionId, _smiEventSink); break; case TransactionRequest.Promote: Debug.Assert(null != _currentTransaction, "promote transaction without TransactionStarted event?"); PromotedDTCToken = _smiConnection.PromoteTransaction(_currentTransaction.TransactionId, _smiEventSink); break; case TransactionRequest.Rollback: case TransactionRequest.IfRollback: Debug.Assert(null != _currentTransaction, "rollback/ifrollback transaction without TransactionStarted event?"); _smiConnection.RollbackTransaction(_currentTransaction.TransactionId, transactionName, _smiEventSink); break; case TransactionRequest.Save: Debug.Assert(null != _currentTransaction, "save transaction without TransactionStarted event?"); _smiConnection.CreateTransactionSavePoint(_currentTransaction.TransactionId, transactionName, _smiEventSink); break; default: Debug.Assert(false, "unhandled case for TransactionRequest"); break; } _smiEventSink.ProcessMessagesAndThrow(); }
abstract internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest);
private void ZombieCheck() { if (this.IsZombied) { if (this.IsYukonPartialZombie) { this._internalTransaction = null; } throw ADP.TransactionZombied(this); } }
public void Initialize() { // if we get here, then we know for certain that we're the delegated // transaction. SqlInternalConnection connection = _connection; SqlConnection usersConnection = connection.Connection; Bid.Trace("<sc.SqlDelegatedTransaction.Initialize|RES|CPOOL> %d#, Connection %d#, delegating transaction.\n", ObjectID, connection.ObjectID); RuntimeHelpers.PrepareConstrainedRegions(); try { #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else { #endif //DEBUG if (connection.IsEnlistedInTransaction) { // defect first Bid.Trace("<sc.SqlDelegatedTransaction.Initialize|RES|CPOOL> %d#, Connection %d#, was enlisted, now defecting.\n", ObjectID, connection.ObjectID); connection.EnlistNull(); } _internalTransaction = new SqlInternalTransaction(connection, TransactionType.Delegated, null); connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Begin, null, _isolationLevel, _internalTransaction, true); // Handle case where ExecuteTran didn't produce a new transaction, but also didn't throw. if (null == connection.CurrentTransaction) { connection.DoomThisConnection(); throw ADP.InternalError(ADP.InternalErrorCode.UnknownTransactionFailure); } _active = true; } #if DEBUG finally { tdsReliabilitySection.Stop(); } #endif //DEBUG } catch (System.OutOfMemoryException e) { usersConnection.Abort(e); throw; } catch (System.StackOverflowException e) { usersConnection.Abort(e); throw; } catch (System.Threading.ThreadAbortException e) { usersConnection.Abort(e); throw; } }
internal void Zombie() { SqlInternalConnection innerConnection = this._connection.InnerConnection as SqlInternalConnection; if (((innerConnection != null) && innerConnection.IsYukonOrNewer) && !this._isFromAPI) { if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlTransaction.Zombie|ADV> %d# yukon deferred zombie\n", this.ObjectID); } } else { this._internalTransaction = null; } }
override public void Rollback() { Exception e = null; Guid operationId = s_diagnosticListener.WriteTransactionRollbackBefore(_isolationLevel, _connection, null); if (IsYukonPartialZombie) { // Put something in the trace in case a customer has an issue _internalTransaction = null; // yukon zombification } else { ZombieCheck(); SqlStatistics statistics = null; try { statistics = SqlStatistics.StartTimer(Statistics); _isFromAPI = true; _internalTransaction.Rollback(); } catch (Exception ex) { e = ex; throw; } finally { if (e != null) { s_diagnosticListener.WriteTransactionRollbackError(operationId, _isolationLevel, _connection, null, e); } else { s_diagnosticListener.WriteTransactionRollbackAfter(operationId, _isolationLevel, _connection, null); } _isFromAPI = false; SqlStatistics.StopTimer(statistics); } } }
public override void Rollback() { if (this.IsYukonPartialZombie) { if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlTransaction.Rollback|ADV> %d# partial zombie no rollback required\n", this.ObjectID); } this._internalTransaction = null; } else { IntPtr ptr; this.ZombieCheck(); SqlStatistics statistics = null; Bid.ScopeEnter(out ptr, "<sc.SqlTransaction.Rollback|API> %d#", this.ObjectID); SNIHandle target = null; RuntimeHelpers.PrepareConstrainedRegions(); try { target = SqlInternalConnection.GetBestEffortCleanupTarget(this._connection); statistics = SqlStatistics.StartTimer(this.Statistics); this._isFromAPI = true; this._internalTransaction.Rollback(); } catch (OutOfMemoryException exception3) { this._connection.Abort(exception3); throw; } catch (StackOverflowException exception2) { this._connection.Abort(exception2); throw; } catch (ThreadAbortException exception) { this._connection.Abort(exception); SqlInternalConnection.BestEffortCleanup(target); throw; } finally { this._isFromAPI = false; SqlStatistics.StopTimer(statistics); Bid.ScopeLeave(ref ptr); } } }
internal void DecrementOpenResultCount() { if (this._executedUnderTransaction == null) { this._parser.DecrementNonTransactedOpenResultCount(); } else { this._executedUnderTransaction.DecrementAndObtainOpenResultCount(); this._executedUnderTransaction = null; } this._hasOpenResult = false; }
override public void Rollback() { if (IsYukonPartialZombie) { // Put something in the trace in case a customer has an issue if (Bid.AdvancedOn) { Bid.Trace("<sc.SqlTransaction.Rollback|ADV> %d# partial zombie no rollback required\n", ObjectID); } _internalTransaction = null; // yukon zombification } else { ZombieCheck(); SqlStatistics statistics = null; IntPtr hscp; Bid.ScopeEnter(out hscp, "<sc.SqlTransaction.Rollback|API> %d#", ObjectID); Bid.CorrelationTrace("<sc.SqlTransaction.Rollback|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID); TdsParser bestEffortCleanupTarget = null; RuntimeHelpers.PrepareConstrainedRegions(); try { #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); statistics = SqlStatistics.StartTimer(Statistics); _isFromAPI = true; _internalTransaction.Rollback(); } #if DEBUG finally { tdsReliabilitySection.Stop(); } #endif //DEBUG } catch (System.OutOfMemoryException e) { _connection.Abort(e); throw; } catch (System.StackOverflowException e) { _connection.Abort(e); throw; } catch (System.Threading.ThreadAbortException e) { _connection.Abort(e); SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); throw; } finally { _isFromAPI = false; SqlStatistics.StopTimer(statistics); Bid.ScopeLeave(ref hscp); } } }
internal int IncrementAndObtainOpenResultCount(SqlInternalTransaction transaction) { this._hasOpenResult = true; if (transaction == null) { return this._parser.IncrementNonTransactedOpenResultCount(); } this._executedUnderTransaction = transaction; return transaction.IncrementAndObtainOpenResultCount(); }
//////////////////////////////////////////////////////////////////////////////////////// // LOCAL TRANSACTION METHODS //////////////////////////////////////////////////////////////////////////////////////// override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) { TdsParser parser = Parser; if (null != parser) { parser.DisconnectTransaction(internalTransaction); } }
internal void DecrementOpenResultCount() { if (_executedUnderTransaction == null) { // If we were not executed under a transaction - decrement the global count // on the parser. _parser.DecrementNonTransactedOpenResultCount(); } else { // If we were executed under a transaction - decrement the count on the transaction. _executedUnderTransaction.DecrementAndObtainOpenResultCount(); _executedUnderTransaction = null; } _hasOpenResult = false; }
// 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 METHODS //////////////////////////////////////////////////////////////////////////////////////// internal void Zombie() { // For Yukon, we have to defer "zombification" until // we get past the users' next rollback, else we'll // throw an exception there that is a breaking change. // Of course, if the connection is already closed, // then we're free to zombify... SqlInternalConnection internalConnection = (_connection.InnerConnection as SqlInternalConnection); if (internalConnection == null || _isFromAPI) { _internalTransaction = null; // pre-yukon zombification } }
internal abstract void ExecuteTransaction(TransactionRequest transactionRequest, string name, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest);
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(); } } }
override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) { if (IsConnectionDoomed) { // doomed means we can't do anything else... if (transactionRequest == TransactionRequest.Rollback || transactionRequest == TransactionRequest.IfRollback) { return; } throw SQL.ConnectionDoomed(); } if (transactionRequest == TransactionRequest.Commit || transactionRequest == TransactionRequest.Rollback || transactionRequest == TransactionRequest.IfRollback) { if (!Parser.MARSOn && Parser._physicalStateObj.BcpLock) { throw SQL.ConnectionLockedForBcpEvent(); } } string transactionName = (null == name) ? String.Empty : name; if (!_parser.IsYukonOrNewer) { ExecuteTransactionPreYukon(transactionRequest, transactionName, iso, internalTransaction); } else { ExecuteTransactionYukon(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest); } }
internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) { Debug.Assert(_currentTransaction != null && _currentTransaction == internalTransaction, "disconnecting different transaction"); if (_currentTransaction != null && _currentTransaction == internalTransaction) { _currentTransaction = null; } }
internal override void ExecuteTransaction(SqlInternalConnection.TransactionRequest transactionRequest, string name, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) { if (base.IsConnectionDoomed) { if ((transactionRequest != SqlInternalConnection.TransactionRequest.Rollback) && (transactionRequest != SqlInternalConnection.TransactionRequest.IfRollback)) { throw SQL.ConnectionDoomed(); } } else { if ((((transactionRequest == SqlInternalConnection.TransactionRequest.Commit) || (transactionRequest == SqlInternalConnection.TransactionRequest.Rollback)) || (transactionRequest == SqlInternalConnection.TransactionRequest.IfRollback)) && (!this.Parser.MARSOn && this.Parser._physicalStateObj.BcpLock)) { throw SQL.ConnectionLockedForBcpEvent(); } string transactionName = (name == null) ? string.Empty : name; if (!this._parser.IsYukonOrNewer) { this.ExecuteTransactionPreYukon(transactionRequest, transactionName, iso, internalTransaction); } else { this.ExecuteTransactionYukon(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest); } } }
internal SqlDataReader TdsExecuteTransactionManagerRequest( byte[] buffer, TdsEnums.TransactionManagerRequestType request, string transactionName, TdsEnums.TransactionManagerIsolationLevel isoLevel, int timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj ) { Debug.Assert(this == stateObj.Parser, "different parsers"); if (TdsParserState.Broken == State || TdsParserState.Closed == State) { return null; } // 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. Debug.Assert(!_connHandler.ThreadHasParserLockForClose || _connHandler._parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken"); bool callerHasConnectionLock = _connHandler.ThreadHasParserLockForClose; // If the thread already claims to have the parser lock, then we will let the caller handle releasing it if (!callerHasConnectionLock) { _connHandler._parserLock.Wait(canReleaseFromAnyThread: false); _connHandler.ThreadHasParserLockForClose = true; } // Capture _asyncWrite (after taking lock) to restore it afterwards bool hadAsyncWrites = _asyncWrite; try { // Temprarily disable async writes _asyncWrite = false; stateObj._outputMessageType = TdsEnums.MT_TRANS; // set message type stateObj.SetTimeoutSeconds(timeout); stateObj.SniContext = SniContext.Snix_Execute; const int marsHeaderSize = 18; // 4 + 2 + 8 + 4 const int totalHeaderLength = 22; // 4 + 4 + 2 + 8 + 4 Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length"); // Write total header length WriteInt(totalHeaderLength, stateObj); // Write mars header length WriteInt(marsHeaderSize, stateObj); WriteMarsHeaderData(stateObj, _currentTransaction); WriteShort((short)request, stateObj); // write TransactionManager Request type bool returnReader = false; switch (request) { case TdsEnums.TransactionManagerRequestType.Begin: Debug.Assert(null != transaction, "Should have specified an internalTransaction when doing a BeginTransaction request!"); // Only assign the passed in transaction if it is not equal to the current transaction. // And, if it is not equal, the current actually should be null. Anything else // is a unexpected state. The concern here is mainly for the mixed use of // T-SQL and API transactions. // Expected states: // 1) _pendingTransaction = null, _currentTransaction = null, non null transaction // passed in on BeginTransaction API call. // 2) _currentTransaction != null, _pendingTransaction = null, non null transaction // passed in but equivalent to _currentTransaction. // #1 will occur on standard BeginTransactionAPI call. #2 should only occur if // t-sql transaction started followed by a call to SqlConnection.BeginTransaction. // Any other state is unknown. if (_currentTransaction != transaction) { Debug.Assert(_currentTransaction == null || true == _fResetConnection, "We should not have a current Tx at this point"); PendingTransaction = transaction; } stateObj.WriteByte((byte)isoLevel); stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string). WriteString(transactionName, stateObj); break; case TdsEnums.TransactionManagerRequestType.Commit: Debug.Assert(transactionName.Length == 0, "Should not have a transaction name on Commit"); stateObj.WriteByte((byte)0); // No xact name stateObj.WriteByte(0); // No flags Debug.Assert(isoLevel == TdsEnums.TransactionManagerIsolationLevel.Unspecified, "Should not have isolation level other than unspecified on Commit!"); // WriteByte((byte) 0, stateObj); // IsolationLevel // WriteByte((byte) 0, stateObj); // No begin xact name break; case TdsEnums.TransactionManagerRequestType.Rollback: stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string). WriteString(transactionName, stateObj); stateObj.WriteByte(0); // No flags Debug.Assert(isoLevel == TdsEnums.TransactionManagerIsolationLevel.Unspecified, "Should not have isolation level other than unspecified on Commit!"); // WriteByte((byte) 0, stateObj); // IsolationLevel // WriteByte((byte) 0, stateObj); // No begin xact name break; case TdsEnums.TransactionManagerRequestType.Save: stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string). WriteString(transactionName, stateObj); break; default: Debug.Assert(false, "Unexpected TransactionManagerRequest"); break; } Task writeTask = stateObj.WritePacket(TdsEnums.HARDFLUSH); Debug.Assert(writeTask == null, "Writes should not pend when writing sync"); stateObj._pendingData = true; stateObj._messageStatus = 0; SqlDataReader dtcReader = null; stateObj.SniContext = SniContext.Snix_Read; if (returnReader) { dtcReader = new SqlDataReader(null, CommandBehavior.Default); Debug.Assert(this == stateObj.Parser, "different parser"); #if DEBUG // Remove the current owner of stateObj - otherwise we will hit asserts stateObj.Owner = null; #endif dtcReader.Bind(stateObj); // force consumption of metadata _SqlMetaDataSet metaData = dtcReader.MetaData; } else { Run(RunBehavior.UntilDone, null, null, null, stateObj); } return dtcReader; } catch (Exception e) { if (!ADP.IsCatchableExceptionType(e)) { throw; } FailureCleanup(stateObj, e); throw; } finally { // SQLHotfix 50000518 // make sure we don't leave temporary fields set when leaving this function _pendingTransaction = null; _asyncWrite = hadAsyncWrites; if (!callerHasConnectionLock) { _connHandler.ThreadHasParserLockForClose = false; _connHandler._parserLock.Release(); } } }
internal void ExecuteTransactionPreYukon(SqlInternalConnection.TransactionRequest transactionRequest, string transactionName, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction) { StringBuilder builder = new StringBuilder(); switch (iso) { case System.Data.IsolationLevel.Unspecified: break; case System.Data.IsolationLevel.Chaos: throw SQL.NotSupportedIsolationLevel(iso); case System.Data.IsolationLevel.ReadUncommitted: builder.Append("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"); builder.Append(";"); break; case System.Data.IsolationLevel.Serializable: builder.Append("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"); builder.Append(";"); break; case System.Data.IsolationLevel.Snapshot: throw SQL.SnapshotNotSupported(System.Data.IsolationLevel.Snapshot); case System.Data.IsolationLevel.ReadCommitted: builder.Append("SET TRANSACTION ISOLATION LEVEL READ COMMITTED"); builder.Append(";"); break; case System.Data.IsolationLevel.RepeatableRead: builder.Append("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"); builder.Append(";"); break; default: throw ADP.InvalidIsolationLevel(iso); } if (!ADP.IsEmpty(transactionName)) { transactionName = " " + SqlConnection.FixupDatabaseTransactionName(transactionName); } switch (transactionRequest) { case SqlInternalConnection.TransactionRequest.Begin: builder.Append("BEGIN TRANSACTION"); builder.Append(transactionName); break; case SqlInternalConnection.TransactionRequest.Commit: builder.Append("COMMIT TRANSACTION"); builder.Append(transactionName); break; case SqlInternalConnection.TransactionRequest.Rollback: builder.Append("ROLLBACK TRANSACTION"); builder.Append(transactionName); break; case SqlInternalConnection.TransactionRequest.IfRollback: builder.Append("IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"); builder.Append(transactionName); break; case SqlInternalConnection.TransactionRequest.Save: builder.Append("SAVE TRANSACTION"); builder.Append(transactionName); break; } this._parser.TdsExecuteSQLBatch(builder.ToString(), base.ConnectionOptions.ConnectTimeout, null, this._parser._physicalStateObj); this._parser.Run(RunBehavior.UntilDone, null, null, null, this._parser._physicalStateObj); if (transactionRequest == SqlInternalConnection.TransactionRequest.Begin) { this._parser.CurrentTransaction = internalTransaction; } }
abstract internal void DisconnectTransaction(SqlInternalTransaction internalTransaction);
override public void Rollback() { if (IsYukonPartialZombie) { // Put something in the trace in case a customer has an issue _internalTransaction = null; // yukon zombification } else { ZombieCheck(); SqlStatistics statistics = null; try { statistics = SqlStatistics.StartTimer(Statistics); _isFromAPI = true; _internalTransaction.Rollback(); } finally { _isFromAPI = false; SqlStatistics.StopTimer(statistics); } } }
//////////////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS //////////////////////////////////////////////////////////////////////////////////////// private void ZombieCheck() { // If this transaction has been completed, throw exception since it is unusable. if (IsZombied) { if (IsYukonPartialZombie) { _internalTransaction = null; // yukon zombification } throw ADP.TransactionZombied(this); } }
internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con, IsolationLevel iso, SqlInternalTransaction internalTransaction) => throw new PlatformNotSupportedException(EXCEPTION_MESSAGE);