Leaked source code of windows server 2003
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.

928 lines
24 KiB

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