Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

930 lines
23 KiB

4 years ago
  1. /*++
  2. Copyright (c) 1990 Microsoft Corporation
  3. Module Name:
  4. blktrans.c
  5. Abstract:
  6. This module implements routines for managing transaction blocks.
  7. Author:
  8. Chuck Lenzmeier (chuckl) 23-Feb-1990
  9. Revision History:
  10. --*/
  11. #include "precomp.h"
  12. #pragma hdrstop
  13. #define BugCheckFileId SRV_FILE_BLKTRANS
  14. //
  15. // If a transaction block has no space for extra data, and its name is
  16. // the null string, it is eligible to be cached when it is free. This
  17. // means that instead of freeing the transaction block, a pointer to the
  18. // block is stored in the connection block.
  19. //
  20. // An eligible transaction will be four bytes longer than the base
  21. // transaction block size. This allows for a Unicode string terminator
  22. // padded to a longword.
  23. //
  24. #define CACHED_TRANSACTION_BLOCK_SIZE sizeof(TRANSACTION) + 4
  25. //
  26. // We allow up to four transactions to be cached.
  27. //
  28. // !!! This should be a configuration parameter.
  29. //
  30. #define CACHED_TRANSACTION_LIMIT 4
  31. #ifdef ALLOC_PRAGMA
  32. #pragma alloc_text( PAGE, SrvCloseTransaction )
  33. #pragma alloc_text( PAGE, SrvCloseTransactionsOnSession )
  34. #pragma alloc_text( PAGE, SrvCloseTransactionsOnTree )
  35. #pragma alloc_text( PAGE, SrvDereferenceTransaction )
  36. #pragma alloc_text( PAGE, SrvAllocateTransaction )
  37. #pragma alloc_text( PAGE, SrvFreeTransaction )
  38. #endif
  39. #if 0
  40. #endif
  41. VOID
  42. SrvAllocateTransaction (
  43. OUT PTRANSACTION *Transaction,
  44. OUT PVOID *TrailingBytes,
  45. IN PCONNECTION Connection,
  46. IN CLONG TrailingByteCount,
  47. IN PVOID TransactionName,
  48. IN PVOID EndOfSourceBuffer OPTIONAL,
  49. IN BOOLEAN SourceIsUnicode,
  50. IN BOOLEAN RemoteApiRequest
  51. )
  52. /*++
  53. Routine Description:
  54. This function allocates a Transaction block from the FSP heap.
  55. Arguments:
  56. Transaction - Returns a pointer to the transaction block, or NULL if
  57. no heap space was available.
  58. TrailingBytes - Returns a pointer to the trailing bytes allocated at
  59. the end of the transaction block. Invalid if *Transaction is
  60. NULL.
  61. TrailingByteCount - Supplies the count of bytes (not including the
  62. transaction name) to be allocated at the tail end of the
  63. transaction block.
  64. TransactionName - Supplies a pointer to the null-terminated
  65. transaction name string. Is SourceIsUnicode is TRUE, this must
  66. be an aligned pointer.
  67. EndOfSourceBuffer - A pointer to the end of the SMB buffer. Used to
  68. protect the server from accessing beyond the end of the SMB buffer,
  69. if the format is invalid. If NULL, indicates that checking is not
  70. necessary.
  71. SourceIsUnicode - Indicates whether the TransactionName buffer contains
  72. Unicode characters.
  73. RemoteApiRequest - TRUE if this is a remote API request and should
  74. hence be allocated from the shared memory that XACTSRV can see.
  75. Return Value:
  76. None.
  77. --*/
  78. {
  79. USHORT nameLength;
  80. CLONG extraLength;
  81. CLONG blockSize;
  82. PSINGLE_LIST_ENTRY listEntry;
  83. PNONPAGED_HEADER header;
  84. PTRANSACTION transaction;
  85. PAGED_CODE();
  86. //
  87. // Get the length of the name (in bytes) including the null terminator.
  88. //
  89. if ( EndOfSourceBuffer == NULL ) {
  90. //
  91. // No checking is required
  92. //
  93. if ( SourceIsUnicode ) {
  94. nameLength = (USHORT)(wcslen( (PWCH)TransactionName ) + 1);
  95. } else {
  96. nameLength = (USHORT)(strlen( (PCHAR)TransactionName ) + 1);
  97. }
  98. nameLength *= sizeof(WCHAR);
  99. } else {
  100. nameLength = SrvGetStringLength(
  101. TransactionName,
  102. EndOfSourceBuffer,
  103. SourceIsUnicode,
  104. TRUE // include null terminator
  105. );
  106. if ( nameLength == (USHORT)-1 ) {
  107. //
  108. // If the name is screwed up, assume L'\0'
  109. //
  110. nameLength = sizeof(WCHAR);
  111. } else if ( !SourceIsUnicode ) {
  112. nameLength *= sizeof(WCHAR);
  113. }
  114. }
  115. extraLength = ((nameLength + 3) & ~3) + TrailingByteCount;
  116. blockSize = sizeof(TRANSACTION) + extraLength;
  117. //
  118. // Attempt to allocate from the heap.
  119. //
  120. if ( !RemoteApiRequest ) {
  121. //
  122. // If the extra length required allows us to use a cached
  123. // transaction block, try to get one of those first.
  124. //
  125. if ( blockSize == CACHED_TRANSACTION_BLOCK_SIZE ) {
  126. listEntry = ExInterlockedPopEntrySList( &Connection->CachedTransactionList, &Connection->SpinLock );
  127. if ( listEntry != NULL ) {
  128. ASSERT( Connection->CachedTransactionCount > 0 );
  129. InterlockedDecrement( &Connection->CachedTransactionCount );
  130. header = CONTAINING_RECORD(
  131. listEntry,
  132. NONPAGED_HEADER,
  133. ListEntry
  134. );
  135. transaction = header->PagedBlock;
  136. IF_DEBUG(HEAP) {
  137. SrvPrint1( "SrvAllocateTransaction: Found cached "
  138. "transaction block at %lx\n", transaction );
  139. }
  140. *Transaction = transaction;
  141. goto got_cached_transaction;
  142. }
  143. }
  144. transaction = ALLOCATE_HEAP( blockSize, BlockTypeTransaction );
  145. } else {
  146. NTSTATUS status; // ignore this
  147. transaction = SrvXsAllocateHeap( blockSize, &status );
  148. }
  149. *Transaction = transaction;
  150. if ( transaction == NULL ) {
  151. INTERNAL_ERROR(
  152. ERROR_LEVEL_EXPECTED,
  153. "SrvAllocateTransaction: Unable to allocate %d bytes from heap.",
  154. blockSize,
  155. NULL
  156. );
  157. // An error will be logged by the caller
  158. return;
  159. }
  160. IF_DEBUG(HEAP) {
  161. SrvPrint1( "SrvAllocateTransaction: Allocated transaction block "
  162. "at %lx\n", transaction );
  163. }
  164. //
  165. // Allocate the nonpaged header.
  166. //
  167. header = ALLOCATE_NONPAGED_POOL(
  168. sizeof(NONPAGED_HEADER),
  169. BlockTypeNonpagedHeader
  170. );
  171. if ( header == NULL ) {
  172. INTERNAL_ERROR(
  173. ERROR_LEVEL_EXPECTED,
  174. "SrvAllocateTransaction: Unable to allocate %d bytes from pool.",
  175. sizeof( NONPAGED_HEADER ),
  176. NULL
  177. );
  178. if ( !RemoteApiRequest ) {
  179. FREE_HEAP( transaction );
  180. } else {
  181. SrvXsFreeHeap( transaction );
  182. }
  183. *Transaction = NULL;
  184. return;
  185. }
  186. header->Type = BlockTypeTransaction;
  187. header->PagedBlock = transaction;
  188. INCREMENT_DEBUG_STAT( SrvDbgStatistics.TransactionInfo.Allocations );
  189. #if SRVDBG2
  190. transaction->BlockHeader.ReferenceCount = 2; // for INITIALIZE_REFERENCE_HISTORY
  191. #endif
  192. INITIALIZE_REFERENCE_HISTORY( transaction );
  193. got_cached_transaction:
  194. RtlZeroMemory( transaction, sizeof(TRANSACTION) );
  195. transaction->NonpagedHeader = header;
  196. SET_BLOCK_TYPE_STATE_SIZE( transaction, BlockTypeTransaction, BlockStateActive, blockSize );
  197. header->ReferenceCount = 2; // allow for Active status and caller's pointer
  198. transaction->RemoteApiRequest = RemoteApiRequest;
  199. //
  200. // Put transaction name after main part of transaction block.
  201. //
  202. transaction->TransactionName.Buffer = (PWCH)( transaction + 1 );
  203. transaction->TransactionName.MaximumLength = (USHORT)nameLength;
  204. transaction->TransactionName.Length = (USHORT)(nameLength - sizeof(WCHAR));
  205. if ( nameLength == sizeof(WCHAR) ) {
  206. transaction->TransactionName.Buffer = L'\0';
  207. } else {
  208. if ( SourceIsUnicode ) {
  209. RtlCopyMemory(
  210. (PVOID)transaction->TransactionName.Buffer,
  211. TransactionName,
  212. nameLength
  213. );
  214. } else {
  215. ANSI_STRING ansiName;
  216. ansiName.Buffer = (PCHAR)TransactionName;
  217. ansiName.Length = (nameLength / sizeof(WCHAR)) - 1;
  218. RtlOemStringToUnicodeString(
  219. &transaction->TransactionName,
  220. &ansiName,
  221. FALSE
  222. );
  223. }
  224. }
  225. //
  226. // Set address of trailing bytes.
  227. //
  228. *TrailingBytes = (PCHAR)transaction + sizeof(TRANSACTION) +
  229. ((nameLength + 3) & ~3);
  230. return;
  231. } // SrvAllocateTransaction
  232. VOID
  233. SrvCloseTransaction (
  234. IN PTRANSACTION Transaction
  235. )
  236. /*++
  237. Routine Description:
  238. This routine closes a pending transaction. It sets the state of the
  239. transaction to Closing and dereferences the transaction block. The
  240. block will be destroyed as soon as all other references to it are
  241. eliminated.
  242. Arguments:
  243. Transaction - Supplies a pointer to the transaction block that is
  244. to be closed.
  245. Return Value:
  246. None.
  247. --*/
  248. {
  249. PAGED_CODE( );
  250. ACQUIRE_LOCK( &Transaction->Connection->Lock );
  251. if ( GET_BLOCK_STATE(Transaction) == BlockStateActive ) {
  252. IF_DEBUG(BLOCK1) {
  253. SrvPrint1( "Closing transaction at %lx\n", Transaction );
  254. }
  255. SET_BLOCK_STATE( Transaction, BlockStateClosing );
  256. RELEASE_LOCK( &Transaction->Connection->Lock );
  257. //
  258. // If the transaction request indicated that the tree connect
  259. // should be closed on completion, do so now.
  260. //
  261. if ( Transaction->Flags & SMB_TRANSACTION_DISCONNECT ) {
  262. SrvCloseTreeConnect( Transaction->TreeConnect );
  263. }
  264. //
  265. // Dereference the transaction (to indicate that it's no longer
  266. // open).
  267. //
  268. SrvDereferenceTransaction( Transaction );
  269. INCREMENT_DEBUG_STAT( SrvDbgStatistics.TransactionInfo.Closes );
  270. } else {
  271. RELEASE_LOCK( &Transaction->Connection->Lock );
  272. }
  273. return;
  274. } // SrvCloseTransaction
  275. VOID
  276. SrvCloseTransactionsOnSession (
  277. PSESSION Session
  278. )
  279. /*++
  280. Routine Description:
  281. This routine closes all pending transactions that are "owned" by the
  282. specified session. It walks the transaction list of the connection
  283. that owns the session. Each transaction in that list that is owned
  284. by the session is closed.
  285. Arguments:
  286. Session - Supplies a pointer to the session block for which
  287. transactions are to be closed.
  288. Return Value:
  289. None.
  290. --*/
  291. {
  292. PCONNECTION connection;
  293. PPAGED_CONNECTION pagedConnection;
  294. PLIST_ENTRY entry;
  295. PTRANSACTION previousTransaction;
  296. PTRANSACTION transaction;
  297. PAGED_CODE( );
  298. //
  299. // Get the address of the owning connection.
  300. //
  301. connection = Session->Connection;
  302. pagedConnection = connection->PagedConnection;
  303. //
  304. // Walk the transaction list, looking for transactions owned by the
  305. // specified session.
  306. //
  307. // *** This routine is complicated by the following requirements:
  308. //
  309. // 1) We must hold the transaction lock while looking at the
  310. // list, and we must ensure the integrity of the list as
  311. // we walk it.
  312. //
  313. // 2) The transaction lock must NOT be held when closing or
  314. // dereferencing a transaction, because its lock level is
  315. // higher than that of other locks that may need to be
  316. // taken out as a result of the close or dereference.
  317. //
  318. // We work around these problems in the following way:
  319. //
  320. // 1) We hold the transaction lock while we search for a
  321. // transaction to close.
  322. //
  323. // 2) We reference the transaction we're about to close, then
  324. // release the lock. This prevents someone else from
  325. // invalidating the transaction after we release the lock
  326. // but before we close it ourselves.
  327. //
  328. // 3) We close the transaction. Our extra reference to the
  329. // transaction prevents it from being deleted. This also
  330. // keeps it on the transaction list.
  331. //
  332. // 4) We retake the lock, find another transaction (using the
  333. // previous transaction as a starting point), reference it,
  334. // then release the lock.
  335. //
  336. // 5) We dereference the original transaction and go to step 3.
  337. //
  338. // Note that the loop below is NOT structured in exactly the same
  339. // way as the steps above are listed.
  340. //
  341. entry = &pagedConnection->TransactionList;
  342. previousTransaction = NULL;
  343. while ( TRUE ) {
  344. ACQUIRE_LOCK( &connection->Lock );
  345. //
  346. // Find a transaction that is owned by the specified session.
  347. //
  348. while ( TRUE ) {
  349. //
  350. // Get the address of the next list entry. If we hit the
  351. // end of the list, exit the inner loop.
  352. //
  353. entry = entry->Flink;
  354. if ( entry == &pagedConnection->TransactionList ) break;
  355. //
  356. // Get the address of the transaction. If it's owned by
  357. // the specified session and currently active, exit the
  358. // inner loop. If it is closing don't touch it.
  359. //
  360. transaction = CONTAINING_RECORD(
  361. entry,
  362. TRANSACTION,
  363. ConnectionListEntry
  364. );
  365. if ( transaction->Session == Session &&
  366. GET_BLOCK_STATE(transaction) == BlockStateActive) {
  367. break;
  368. }
  369. }
  370. //
  371. // If we hit the end of the list without finding a transaction
  372. // that was owned by the specified session, exit the main loop.
  373. //
  374. if ( entry == &pagedConnection->TransactionList ) break;
  375. //
  376. // Reference the transaction to ensure that it isn't deleted
  377. // when we close it.
  378. //
  379. SrvReferenceTransaction( transaction );
  380. //
  381. // Unlock the transaction list, so that we can dereference the
  382. // previous transaction and close the current one.
  383. //
  384. RELEASE_LOCK( &connection->Lock );
  385. //
  386. // If this is not the first matching transaction that we've
  387. // found, dereference the previous one now.
  388. //
  389. if ( previousTransaction != NULL ) {
  390. SrvDereferenceTransaction( previousTransaction );
  391. }
  392. //
  393. // Close the current transaction and mark that we need to
  394. // dereference it.
  395. //
  396. SrvCloseTransaction( transaction );
  397. previousTransaction = transaction;
  398. //
  399. // Go find another matching transaction.
  400. //
  401. } // while ( TRUE )
  402. //
  403. // We have hit the end of the transaction list. Release the
  404. // transaction lock. If we have a transaction that needs to be
  405. // dereferenced, do so. Then return to the caller.
  406. //
  407. RELEASE_LOCK( &connection->Lock );
  408. if ( previousTransaction != NULL ) {
  409. SrvDereferenceTransaction( previousTransaction );
  410. }
  411. return;
  412. } // SrvCloseTransactionsOnSession
  413. VOID
  414. SrvCloseTransactionsOnTree (
  415. IN PTREE_CONNECT TreeConnect
  416. )
  417. /*++
  418. Routine Description:
  419. This routine closes all pending transactions that are "owned" by the
  420. specified tree connect. It walks the transaction list of the
  421. connection that owns the tree connect. Each transaction in that
  422. list that is owned by the tree connect is closed.
  423. Arguments:
  424. TreeConnect - Supplies a pointer to the tree connect block for which
  425. transactions are to be closed.
  426. Return Value:
  427. None.
  428. --*/
  429. {
  430. PCONNECTION connection;
  431. PPAGED_CONNECTION pagedConnection;
  432. PLIST_ENTRY entry;
  433. PTRANSACTION previousTransaction;
  434. PTRANSACTION transaction;
  435. PAGED_CODE( );
  436. //
  437. // Get the address of the owning connection.
  438. //
  439. connection = TreeConnect->Connection;
  440. pagedConnection = connection->PagedConnection;
  441. //
  442. // Walk the transaction list, looking for transactions owned by the
  443. // specified tree connect.
  444. //
  445. // *** See the description of SrvCloseTransactionsOnSession, which
  446. // explains why this loop is so complicated.
  447. //
  448. entry = &pagedConnection->TransactionList;
  449. previousTransaction = NULL;
  450. while ( TRUE ) {
  451. ACQUIRE_LOCK( &connection->Lock );
  452. //
  453. // Find a transaction that is owned by the specified tree
  454. // connect.
  455. //
  456. while ( TRUE ) {
  457. //
  458. // Get the address of the next list entry. If we hit the
  459. // end of the list, exit the inner loop.
  460. //
  461. entry = entry->Flink;
  462. if ( entry == &pagedConnection->TransactionList ) break;
  463. //
  464. // Get the address of the transaction. If it's owned by
  465. // the specified tree connect and currently active, exit
  466. // the inner loop.
  467. //
  468. transaction = CONTAINING_RECORD(
  469. entry,
  470. TRANSACTION,
  471. ConnectionListEntry
  472. );
  473. if ( transaction->TreeConnect == TreeConnect &&
  474. GET_BLOCK_STATE(transaction) == BlockStateActive) {
  475. break;
  476. }
  477. }
  478. //
  479. // If we hit the end of the list without finding a transaction
  480. // that was owned by the specified tree connect, exit the main
  481. // loop.
  482. //
  483. if ( entry == &pagedConnection->TransactionList ) break;
  484. //
  485. // Reference the transaction to ensure that it isn't deleted
  486. // when we close it.
  487. //
  488. SrvReferenceTransaction( transaction );
  489. //
  490. // Unlock the transaction list, so that we can dereference the
  491. // previous transaction and close the current one.
  492. //
  493. RELEASE_LOCK( &connection->Lock );
  494. //
  495. // If this is not the first matching transaction that we've
  496. // found, dereference the previous one now.
  497. //
  498. if ( previousTransaction != NULL ) {
  499. SrvDereferenceTransaction( previousTransaction );
  500. }
  501. //
  502. // Close the current transaction and mark that we need to
  503. // dereference it.
  504. //
  505. SrvCloseTransaction( transaction );
  506. previousTransaction = transaction;
  507. //
  508. // Go find another matching transaction.
  509. //
  510. } // while ( TRUE )
  511. //
  512. // We have hit the end of the transaction list. Release the
  513. // transaction lock. If we have a transaction that needs to be
  514. // dereferenced, do so. Then return to the caller.
  515. //
  516. RELEASE_LOCK( &connection->Lock );
  517. if ( previousTransaction != NULL ) {
  518. SrvDereferenceTransaction( previousTransaction );
  519. }
  520. return;
  521. } // SrvCloseTransactionsOnTree
  522. VOID
  523. SrvDereferenceTransaction (
  524. IN PTRANSACTION Transaction
  525. )
  526. /*++
  527. Routine Description:
  528. This function decrements the reference count on a transaction. If
  529. the reference count goes to zero, the transaction block is deleted.
  530. Arguments:
  531. Transaction - Address of transaction
  532. Return Value:
  533. None.
  534. --*/
  535. {
  536. PCONNECTION connection;
  537. LONG result;
  538. PAGED_CODE( );
  539. //
  540. // Decrement the reference count on the block.
  541. //
  542. connection = Transaction->Connection;
  543. IF_DEBUG(REFCNT) {
  544. SrvPrint2( "Dereferencing transaction %lx; old refcnt %lx\n",
  545. Transaction, Transaction->NonpagedHeader->ReferenceCount );
  546. }
  547. ASSERT( GET_BLOCK_TYPE( Transaction ) == BlockTypeTransaction );
  548. ASSERT( Transaction->NonpagedHeader->ReferenceCount > 0 );
  549. UPDATE_REFERENCE_HISTORY( Transaction, TRUE );
  550. result = InterlockedDecrement(
  551. &Transaction->NonpagedHeader->ReferenceCount
  552. );
  553. if ( result == 0 ) {
  554. //
  555. // The new reference count is 0, meaning that it's time to
  556. // delete this block.
  557. //
  558. // If the transaction is on the connection's pending transaction
  559. // list, remove it and dereference the connection, session, and
  560. // tree connect. If the transaction isn't on the list, then the
  561. // session and tree connect pointers are not referenced
  562. // pointers, but just copies from the (single) work context
  563. // block associated with the transaction.
  564. //
  565. if ( Transaction->Inserted ) {
  566. ACQUIRE_LOCK( &connection->Lock );
  567. SrvRemoveEntryList(
  568. &connection->PagedConnection->TransactionList,
  569. &Transaction->ConnectionListEntry
  570. );
  571. RELEASE_LOCK( &connection->Lock );
  572. if ( Transaction->Session != NULL ) {
  573. SrvDereferenceSession( Transaction->Session );
  574. DEBUG Transaction->Session = NULL;
  575. }
  576. if ( Transaction->TreeConnect != NULL ) {
  577. SrvDereferenceTreeConnect( Transaction->TreeConnect );
  578. DEBUG Transaction->TreeConnect = NULL;
  579. }
  580. } else {
  581. DEBUG Transaction->Session = NULL;
  582. DEBUG Transaction->TreeConnect = NULL;
  583. }
  584. //
  585. // Free the transaction block, then release the transaction's
  586. // reference to the connection. Note that we have to do the
  587. // dereference after calling SrvFreeConnection because that
  588. // routine may try to put the transaction on the connection's
  589. // cached transaction list.
  590. //
  591. SrvFreeTransaction( Transaction );
  592. SrvDereferenceConnection( connection );
  593. }
  594. return;
  595. } // SrvDereferenceTransaction
  596. VOID
  597. SrvFreeTransaction (
  598. IN PTRANSACTION Transaction
  599. )
  600. /*++
  601. Routine Description:
  602. This function returns a Transaction block to the server heap.
  603. Arguments:
  604. Transaction - Address of Transaction block
  605. Return Value:
  606. None.
  607. --*/
  608. {
  609. ULONG blockSize;
  610. PCONNECTION connection;
  611. PNONPAGED_HEADER header;
  612. PAGED_CODE();
  613. blockSize = GET_BLOCK_SIZE( Transaction );
  614. DEBUG SET_BLOCK_TYPE_STATE_SIZE( Transaction, BlockTypeGarbage, BlockStateDead, -1 );
  615. DEBUG Transaction->NonpagedHeader->ReferenceCount = -1;
  616. TERMINATE_REFERENCE_HISTORY( Transaction );
  617. connection = Transaction->Connection;
  618. //
  619. // If the transaction was not allocated from the XACTSRV heap and
  620. // its block size is correct, cache this transaction instead of
  621. // freeing it back to pool.
  622. //
  623. header = Transaction->NonpagedHeader;
  624. if ( !Transaction->RemoteApiRequest ) {
  625. if ( blockSize == CACHED_TRANSACTION_BLOCK_SIZE ) {
  626. //
  627. // Check the count of cached transactions on the connection.
  628. // If there aren't already enough transactions cached, link
  629. // this transaction to the list. Otherwise, free the
  630. // transaction block.
  631. //
  632. if ( connection->CachedTransactionCount < CACHED_TRANSACTION_LIMIT ) {
  633. if ( connection->CachedTransactionCount < CACHED_TRANSACTION_LIMIT ) {
  634. ExInterlockedPushEntrySList(
  635. &connection->CachedTransactionList,
  636. &header->ListEntry,
  637. &connection->SpinLock
  638. );
  639. InterlockedIncrement( &connection->CachedTransactionCount );
  640. return;
  641. }
  642. }
  643. }
  644. FREE_HEAP( Transaction );
  645. } else {
  646. SrvXsFreeHeap( Transaction );
  647. }
  648. DEALLOCATE_NONPAGED_POOL( header );
  649. IF_DEBUG(HEAP) {
  650. SrvPrint1( "SrvFreeTransaction: Freed transaction block at %lx\n",
  651. Transaction );
  652. }
  653. INCREMENT_DEBUG_STAT( SrvDbgStatistics.TransactionInfo.Frees );
  654. return;
  655. } // SrvFreeTransaction