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 synchronize 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
{
// Temporarily 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
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;
stateObj.SniContext = SniContext.Snix_Read;
Run(RunBehavior.UntilDone, null, null, null, stateObj);
return null;
}
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();
}
}
}