Source code of Windows XP (NT5)
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.

854 lines
21 KiB

  1. /*++
  2. Copyright (c) 1999-1999 Microsoft Corporation
  3. Module Name:
  4. objtable.c
  5. Abstract:
  6. This module implements the object ID table. The ID table is
  7. implemented as a two-level array.
  8. The first level is an array of pointers to the second-level arrays.
  9. This first-level array is growable, but this should happen very
  10. infrequently.
  11. The second level is an array of OBJECT_TABLE_ENTRY structures.
  12. These structures contain a cyclic (to detect stale IDs) and a caller-
  13. supplied context value.
  14. The data structures may be diagrammed as follows:
  15. g_FirstLevelTable
  16. |
  17. | +-----+
  18. | | | +-----+-----+-----+-----+-----+-----+
  19. | | | | OBJECT_ | OBJECT_ | OBJECT_ |
  20. +-->| *-------->| TABLE_ | TABLE_ | TABLE_ |
  21. | | | ENTRY | ENTRY | ENTRY |
  22. | | +-----+-----+-----+-----+-----+-----+
  23. +-----+
  24. | | +-----+-----+-----+-----+-----+-----+
  25. | | | OBJECT_ | OBJECT_ | OBJECT_ |
  26. | *-------->| TABLE_ | TABLE_ | TABLE_ |
  27. | | | ENTRY | ENTRY | ENTRY |
  28. | | +-----+-----+-----+-----+-----+-----+
  29. +-----+
  30. | .
  31. | .
  32. . .
  33. . |
  34. . |
  35. +-----+
  36. | |
  37. | |
  38. | / |
  39. | |
  40. | |
  41. +-----+
  42. | |
  43. | |
  44. | / |
  45. | |
  46. | |
  47. +-----+
  48. Note that all free OBJECT_TABLE_ENTRY structures are kept on a single
  49. (global) free list. Whenever a new ID needs to be allocated, the free
  50. list is consulted. If it's not empty, an item is popped from the list
  51. and used. If the list is empty, then new space must be allocated. Best
  52. case, this will involve the allocation of a new second-level array.
  53. Worst case, this will also involve a reallocation of the first-level
  54. array. Reallocation of the first-level array should be relatively rare.
  55. A UL_OPAQUE_ID is opaque at user-mode. Internally, it consists of three
  56. fields:
  57. 1) An index into the first-level array.
  58. 2) An index into the second-level array referenced by the
  59. first-level index.
  60. 3) A cyclic, used to detect stale IDs.
  61. See the OPAQUE_ID_INTERNAL structure definition (below) for details.
  62. Author:
  63. Keith Moore (keithmo) 20-Apr-1999
  64. Revision History:
  65. --*/
  66. #include "precomp.h"
  67. //
  68. // Private constants.
  69. //
  70. #define FIRST_LEVEL_TABLE_GROWTH 32 // entries
  71. #define SECOND_LEVEL_TABLE_SIZE 256 // entries
  72. //
  73. // Private prototypes.
  74. //
  75. POPAQUE_ID_TABLE_ENTRY
  76. UlpMapOpaqueIdToTableEntry(
  77. IN UL_OPAQUE_ID OpaqueId
  78. );
  79. NTSTATUS
  80. UlpReallocOpaqueIdTables(
  81. IN ULONG CapturedFirstTableSize,
  82. IN ULONG CapturedFirstTableInUse
  83. );
  84. __inline
  85. CYCLIC
  86. UlpGetNextCyclic(
  87. VOID
  88. )
  89. {
  90. OPAQUE_ID_TABLE_ENTRY entry;
  91. entry.Cyclic = (CYCLIC)InterlockedIncrement( &g_OpaqueIdCyclic );
  92. entry.EntryType = ENTRY_TYPE_IN_USE;
  93. return entry.Cyclic;
  94. } // UlpGetNextCyclic
  95. //
  96. // Private globals.
  97. //
  98. LIST_ENTRY g_UlObjectTypeListHead;
  99. UL_SPIN_LOCK g_UlObjectLock;
  100. #ifdef ALLOC_PRAGMA
  101. #pragma alloc_text( INIT, UlInitializeObjectTablePackage )
  102. #pragma alloc_text( PAGE, UlTerminateObjectTablePackage )
  103. #pragma alloc_text( INIT, UlInitializeObjectType )
  104. #pragma alloc_text( PAGE, UlCreateObjectTable )
  105. #pragma alloc_text( PAGE, UlDestroyObjectTable )
  106. #pragma alloc_text( PAGE, UlCreateObject )
  107. #pragma alloc_text( PAGE, UlCloseObject )
  108. #pragma alloc_text( PAGE, UlReferenceObjectByOpaqueId )
  109. #pragma alloc_text( PAGE, UlReferenceObjectByPointer )
  110. #pragma alloc_text( PAGE, UlDereferenceObject )
  111. #endif // ALLOC_PRAGMA
  112. #if 0
  113. NOT PAGEABLE -- UlpMapOpaqueIdToTableEntry
  114. NOT PAGEABLE -- UlpReallocOpaqueIdTables
  115. #endif
  116. //
  117. // Public functions.
  118. //
  119. /***************************************************************************++
  120. Routine Description:
  121. Performs global initialization of the object table package.
  122. Return Value:
  123. NTSTATUS - Completion status.
  124. --***************************************************************************/
  125. NTSTATUS
  126. UlInitializeObjectTablePackage(
  127. VOID
  128. )
  129. {
  130. LARGE_INTEGER timeNow;
  131. INITIALIZE_LOCK( &g_OpaqueIdTableLock );
  132. KeInitializeSpinLock( &g_FreeOpaqueIdSListLock );
  133. ExInitializeSListHead( &g_FreeOpaqueIdSListHead );
  134. KeQuerySystemTime( &timeNow );
  135. g_OpaqueIdCyclic = (LONG)( timeNow.HighPart + timeNow.LowPart );
  136. g_FirstLevelTableInUse = 0;
  137. g_FirstLevelTableSize = FIRST_LEVEL_TABLE_GROWTH;
  138. //
  139. // Go ahead and allocate the first-level table now. This makes the
  140. // normal runtime path a little cleaner because it doesn't have to
  141. // deal with the "first time" case.
  142. //
  143. g_FirstLevelTable = UL_ALLOCATE_ARRAY(
  144. NonPagedPool,
  145. POPAQUE_ID_TABLE_ENTRY,
  146. g_FirstLevelTableSize,
  147. UL_OPAQUE_ID_TABLE_POOL_TAG
  148. );
  149. if (g_FirstLevelTable == NULL)
  150. {
  151. return STATUS_INSUFFICIENT_RESOURCES;
  152. }
  153. RtlZeroMemory(
  154. g_FirstLevelTable,
  155. g_FirstLevelTableSize * sizeof(POPAQUE_ID_TABLE_ENTRY)
  156. );
  157. return STATUS_SUCCESS;
  158. } // InitializeOpaqueIdTable
  159. /***************************************************************************++
  160. Routine Description:
  161. Performs global termination of the object table package.
  162. --***************************************************************************/
  163. VOID
  164. UlTerminateObjectTablePackage(
  165. VOID
  166. )
  167. {
  168. ULONG i;
  169. if (g_FirstLevelTable != NULL)
  170. {
  171. //
  172. // Free the second-level tables.
  173. //
  174. for (i = 0 ; i < g_FirstLevelTableInUse ; i++)
  175. {
  176. if (g_FirstLevelTable[i] != NULL)
  177. {
  178. UL_FREE_POOL(
  179. g_FirstLevelTable[i],
  180. UL_OPAQUE_ID_TABLE_POOL_TAG
  181. );
  182. }
  183. g_FirstLevelTable[i] = NULL;
  184. }
  185. //
  186. // Free the first-level table.
  187. //
  188. UL_FREE_POOL(
  189. g_FirstLevelTable,
  190. UL_OPAQUE_ID_TABLE_POOL_TAG
  191. );
  192. g_FirstLevelTable = NULL;
  193. }
  194. ExInitializeSListHead( &g_FreeOpaqueIdSListHead );
  195. g_OpaqueIdCyclic = 0;
  196. g_FirstLevelTableSize = 0;
  197. g_FirstLevelTableInUse = 0;
  198. } // TerminateOpaqueIdTable
  199. /***************************************************************************++
  200. Routine Description:
  201. Allocates a new opaque ID and associates it with the specified context.
  202. Arguments:
  203. pOpaqueId - Receives the newly allocated opaque ID if successful.
  204. OpaqueIdType - Supplies an uninterpreted
  205. pContext - Supplies the context to associate with the new opaque ID.
  206. Return Value:
  207. NTSTATUS - Completion status.
  208. --***************************************************************************/
  209. NTSTATUS
  210. UlAllocateOpaqueId(
  211. OUT PUL_OPAQUE_ID pOpaqueId,
  212. IN UL_OPAQUE_ID_TYPE OpaqueIdType,
  213. IN PVOID pContext
  214. )
  215. {
  216. NTSTATUS status;
  217. CYCLIC cyclic;
  218. ULONG firstIndex;
  219. ULONG secondIndex;
  220. PSINGLE_LIST_ENTRY listEntry;
  221. OPAQUE_ID_INTERNAL internal;
  222. POPAQUE_ID_TABLE_ENTRY tableEntry;
  223. ULONG capturedFirstTableSize;
  224. ULONG capturedFirstTableInUse;
  225. #if HACK_OPAQUE_ID
  226. KIRQL oldIrql;
  227. oldIrql = KeRaiseIrqlToDpcLevel();
  228. #endif
  229. //
  230. // Sanity check.
  231. //
  232. ASSERT( KeGetCurrentIrql() == DISPATCH_LEVEL );
  233. //
  234. // Allocate a new cyclic while we're outside the lock.
  235. //
  236. cyclic = UlpGetNextCyclic();
  237. //
  238. // Loop, trying to allocate an item from the table.
  239. //
  240. do
  241. {
  242. ACQUIRE_READ_LOCK( &g_OpaqueIdTableLock );
  243. listEntry = ExInterlockedPopEntrySList(
  244. &g_FreeOpaqueIdSListHead,
  245. &g_FreeOpaqueIdSListLock
  246. );
  247. if (listEntry != NULL)
  248. {
  249. //
  250. // The free list isn't empty, so we can just use this
  251. // entry. We'll calculate the indices for this entry,
  252. // initialize the entry, then release the table lock.
  253. //
  254. tableEntry = CONTAINING_RECORD(
  255. listEntry,
  256. OPAQUE_ID_TABLE_ENTRY,
  257. FreeListEntry
  258. );
  259. firstIndex = tableEntry->FirstLevelIndex;
  260. secondIndex = (ULONG)(tableEntry - g_FirstLevelTable[firstIndex]);
  261. tableEntry->pContext = pContext;
  262. tableEntry->Cyclic = cyclic;
  263. RELEASE_READ_LOCK( &g_OpaqueIdTableLock );
  264. //
  265. // Pack the cyclic & indices into the opaque ID and return it.
  266. //
  267. internal.Cyclic = cyclic;
  268. internal.FirstIndex = firstIndex;
  269. internal.SecondIndex = secondIndex;
  270. *pOpaqueId = internal.OpaqueId;
  271. #if HACK_OPAQUE_ID
  272. if (oldIrql != DISPATCH_LEVEL)
  273. {
  274. KeLowerIrql( oldIrql );
  275. }
  276. #endif
  277. return STATUS_SUCCESS;
  278. }
  279. //
  280. // We only make it to this point if the free list is empty,
  281. // meaning we need to do some memory allocations before
  282. // we can continue. We'll put this off into a separate routine
  283. // to keep this one small (to avoid cache thrash). The realloc
  284. // routine returns STATUS_SUCCESS if it (or another thread)
  285. // managed to successfully reallocate the tables. Otherwise, it
  286. // returns a failure code.
  287. //
  288. capturedFirstTableSize = g_FirstLevelTableSize;
  289. capturedFirstTableInUse = g_FirstLevelTableInUse;
  290. RELEASE_READ_LOCK( &g_OpaqueIdTableLock );
  291. status = UlpReallocOpaqueIdTables(
  292. capturedFirstTableSize,
  293. capturedFirstTableInUse
  294. );
  295. } while( status == STATUS_SUCCESS );
  296. return status;
  297. } // UlAllocateOpaqueId
  298. /***************************************************************************++
  299. Routine Description:
  300. Frees the specified opaque ID.
  301. Arguments:
  302. OpaqueId - Supplies the connection ID to free.
  303. Return Value:
  304. PVOID - Returns the HTTP_CONNECTION associated with the
  305. connection ID if successful, NULL otherwise.
  306. --***************************************************************************/
  307. PVOID
  308. UlFreeOpaqueId(
  309. IN UL_OPAQUE_ID OpaqueId,
  310. IN UL_OPAQUE_ID_TYPE OpaqueIdType
  311. )
  312. {
  313. POPAQUE_ID_TABLE_ENTRY tableEntry;
  314. PVOID pContext;
  315. #if HACK_OPAQUE_ID
  316. KIRQL oldIrql;
  317. oldIrql = KeRaiseIrqlToDpcLevel();
  318. #endif
  319. //
  320. // Sanity check.
  321. //
  322. ASSERT( KeGetCurrentIrql() == DISPATCH_LEVEL );
  323. //
  324. // Try to map the opaque ID to a table entry.
  325. //
  326. tableEntry = UlpMapOpaqueIdToTableEntry( OpaqueId );
  327. if (tableEntry != NULL)
  328. {
  329. OPAQUE_ID_INTERNAL internal;
  330. //
  331. // Got a match. Snag the context, free the table entry, then
  332. // unlock the table.
  333. //
  334. pContext = tableEntry->pContext;
  335. internal.OpaqueId = OpaqueId;
  336. tableEntry->FirstLevelIndex = internal.FirstIndex;
  337. tableEntry->EntryType = ENTRY_TYPE_FREE;
  338. ExInterlockedPushEntrySList(
  339. &g_FreeOpaqueIdSListHead,
  340. &tableEntry->FreeListEntry,
  341. &g_FreeOpaqueIdSListLock
  342. );
  343. RELEASE_READ_LOCK( &g_OpaqueIdTableLock );
  344. #if HACK_OPAQUE_ID
  345. if (oldIrql != DISPATCH_LEVEL)
  346. {
  347. KeLowerIrql( oldIrql );
  348. }
  349. #endif
  350. return pContext;
  351. }
  352. #if HACK_OPAQUE_ID
  353. if (oldIrql != DISPATCH_LEVEL)
  354. {
  355. KeLowerIrql( oldIrql );
  356. }
  357. #endif
  358. return NULL;
  359. } // UlFreeOpaqueId
  360. /***************************************************************************++
  361. Routine Description:
  362. Maps the specified opaque ID to the corresponding context value.
  363. Arguments:
  364. OpaqueId - Supplies the opaque ID to map.
  365. Return Value:
  366. PVOID - Returns the context value associated with the opaque ID
  367. if successful, NULL otherwise.
  368. --***************************************************************************/
  369. PVOID
  370. UlGetObjectFromOpaqueId(
  371. IN UL_OPAQUE_ID OpaqueId,
  372. IN UL_OPAQUE_ID_TYPE OpaqueIdType
  373. )
  374. {
  375. POPAQUE_ID_TABLE_ENTRY tableEntry;
  376. PVOID pContext;
  377. #if HACK_OPAQUE_ID
  378. KIRQL oldIrql;
  379. oldIrql = KeRaiseIrqlToDpcLevel();
  380. #endif
  381. //
  382. // Sanity check.
  383. //
  384. ASSERT( KeGetCurrentIrql() == DISPATCH_LEVEL );
  385. //
  386. // Try to map the opaque ID to a table entry.
  387. //
  388. tableEntry = UlpMapOpaqueIdToTableEntry( OpaqueId );
  389. if (tableEntry != NULL)
  390. {
  391. //
  392. // Got a match. Retrieve the context value, then unlock the
  393. // table. Note that we cannot touch the table entry once we
  394. // unlock the table.
  395. //
  396. pContext = tableEntry->pContext;
  397. RELEASE_READ_LOCK( &g_OpaqueIdTableLock );
  398. #if HACK_OPAQUE_ID
  399. if (oldIrql != DISPATCH_LEVEL)
  400. {
  401. KeLowerIrql( oldIrql );
  402. }
  403. #endif
  404. return pContext;
  405. }
  406. #if HACK_OPAQUE_ID
  407. if (oldIrql != DISPATCH_LEVEL)
  408. {
  409. KeLowerIrql( oldIrql );
  410. }
  411. #endif
  412. return NULL;
  413. } // UlGetObjectFromOpaqueId
  414. //
  415. // Private functions.
  416. //
  417. /***************************************************************************++
  418. Routine Description:
  419. Maps the specified UL_OPAQUE_ID to the corresponding
  420. PVOID pointer.
  421. Arguments:
  422. OpaqueId - Supplies the opaque ID to map.
  423. Return Value:
  424. POPAQUE_ID_TABLE_ENTRY - Pointer to the table entry corresponding to the
  425. opaque ID if successful, NULL otherwise.
  426. N.B. If this function is successful, it returns with the table lock
  427. held for read access!
  428. --***************************************************************************/
  429. POPAQUE_ID_TABLE_ENTRY
  430. UlpMapOpaqueIdToTableEntry(
  431. IN UL_OPAQUE_ID OpaqueId
  432. )
  433. {
  434. POPAQUE_ID_TABLE_ENTRY secondTable;
  435. POPAQUE_ID_TABLE_ENTRY tableEntry;
  436. OPAQUE_ID_INTERNAL internal;
  437. //
  438. // Sanity check.
  439. //
  440. ASSERT( KeGetCurrentIrql() == DISPATCH_LEVEL );
  441. //
  442. // Unpack the ID.
  443. //
  444. internal.OpaqueId = OpaqueId;
  445. //
  446. // Lock the table.
  447. //
  448. ACQUIRE_READ_LOCK( &g_OpaqueIdTableLock );
  449. //
  450. // Validate the index.
  451. //
  452. if (internal.FirstIndex >= 0 &&
  453. internal.FirstIndex < g_FirstLevelTableInUse)
  454. {
  455. secondTable = g_FirstLevelTable[internal.FirstIndex];
  456. ASSERT( secondTable != NULL );
  457. ASSERT( internal.SecondIndex >= 0 );
  458. ASSERT( internal.SecondIndex <= SECOND_LEVEL_TABLE_SIZE );
  459. tableEntry = secondTable + internal.SecondIndex;
  460. //
  461. // The index is within legal range. Ensure it's in use and
  462. // the cyclic matches.
  463. //
  464. if (tableEntry->Cyclic == internal.Cyclic &&
  465. tableEntry->EntryType == ENTRY_TYPE_IN_USE)
  466. {
  467. return tableEntry;
  468. }
  469. }
  470. //
  471. // Invalid opaque ID. Fail it.
  472. //
  473. RELEASE_READ_LOCK( &g_OpaqueIdTableLock );
  474. return NULL;
  475. } // UlpMapOpaqueIdToTableEntry
  476. /***************************************************************************++
  477. Routine Description:
  478. Allocates a new second-level table, and (optionally) reallocates the
  479. first-level table if necessary.
  480. Arguments:
  481. CapturedFirstTableSize - The size of the first-level table as captured
  482. with the table lock held.
  483. CapturedFirstTableInUse - The number of entries currently used in the
  484. first-level table as captured with the table lock held.
  485. Return Value:
  486. NTSTATUS - Completion status.
  487. --***************************************************************************/
  488. NTSTATUS
  489. UlpReallocOpaqueIdTables(
  490. IN ULONG CapturedFirstTableSize,
  491. IN ULONG CapturedFirstTableInUse
  492. )
  493. {
  494. ULONG firstIndex;
  495. ULONG secondIndex;
  496. POPAQUE_ID_TABLE_ENTRY tableEntry;
  497. POPAQUE_ID_TABLE_ENTRY newSecondTable;
  498. POPAQUE_ID_TABLE_ENTRY *newFirstTable;
  499. ULONG newFirstTableSize;
  500. ULONG i;
  501. PLIST_ENTRY listEntry;
  502. //
  503. // Sanity check.
  504. //
  505. ASSERT( KeGetCurrentIrql() == DISPATCH_LEVEL );
  506. ASSERT( ExQueryDepthSList( &g_FreeOpaqueIdSListHead ) == 0 );
  507. //
  508. // Assume we won't actually allocate anything.
  509. //
  510. newFirstTable = NULL;
  511. newSecondTable = NULL;
  512. //
  513. // Allocate a new second-level table.
  514. //
  515. newSecondTable = UL_ALLOCATE_ARRAY(
  516. NonPagedPool,
  517. OPAQUE_ID_TABLE_ENTRY,
  518. SECOND_LEVEL_TABLE_SIZE,
  519. UL_OPAQUE_ID_TABLE_POOL_TAG
  520. );
  521. if (newSecondTable == NULL)
  522. {
  523. return STATUS_INSUFFICIENT_RESOURCES;
  524. }
  525. if (CapturedFirstTableInUse == CapturedFirstTableSize)
  526. {
  527. //
  528. // We need a new first-level table.
  529. //
  530. newFirstTableSize =
  531. CapturedFirstTableSize + FIRST_LEVEL_TABLE_GROWTH;
  532. newFirstTable = UL_ALLOCATE_ARRAY(
  533. NonPagedPool,
  534. POPAQUE_ID_TABLE_ENTRY,
  535. newFirstTableSize,
  536. UL_OPAQUE_ID_TABLE_POOL_TAG
  537. );
  538. if (newFirstTable == NULL)
  539. {
  540. UL_FREE_POOL( newSecondTable, UL_OPAQUE_ID_TABLE_POOL_TAG );
  541. return STATUS_INSUFFICIENT_RESOURCES;
  542. }
  543. RtlZeroMemory(
  544. newFirstTable + CapturedFirstTableSize,
  545. FIRST_LEVEL_TABLE_GROWTH * sizeof(POPAQUE_ID_TABLE_ENTRY)
  546. );
  547. }
  548. //
  549. // OK, we've got the new table(s). Acquire the lock for write access
  550. // and see if another thread has already done the work for us.
  551. //
  552. ACQUIRE_WRITE_LOCK( &g_OpaqueIdTableLock );
  553. if (ExQueryDepthSList( &g_FreeOpaqueIdSListHead ) == 0)
  554. {
  555. //
  556. // The free list is still empty. This could potentially
  557. // mean that another thread has already performed the
  558. // realloc and all of *those* new entries are now in use.
  559. // We can detect this by comparing the current table size
  560. // with the size we captured previously with the lock held.
  561. //
  562. if (CapturedFirstTableSize != g_FirstLevelTableSize)
  563. {
  564. goto cleanup;
  565. }
  566. //
  567. // OK, we're the one performing the realloc.
  568. //
  569. if (newFirstTable != NULL)
  570. {
  571. POPAQUE_ID_TABLE_ENTRY *tmp;
  572. //
  573. // Copy the old table into the new table.
  574. //
  575. RtlCopyMemory(
  576. newFirstTable,
  577. g_FirstLevelTable,
  578. g_FirstLevelTableSize * sizeof(POPAQUE_ID_TABLE_ENTRY)
  579. );
  580. //
  581. // Swap the new & old table pointers so we can keep the
  582. // original pointer & free it later (after we've released
  583. // the table lock).
  584. //
  585. tmp = g_FirstLevelTable;
  586. g_FirstLevelTable = newFirstTable;
  587. newFirstTable = tmp;
  588. g_FirstLevelTableSize = newFirstTableSize;
  589. }
  590. //
  591. // Attach the second-level table to the first-level table,
  592. //
  593. ASSERT( g_FirstLevelTable[g_FirstLevelTableInUse] == NULL );
  594. g_FirstLevelTable[g_FirstLevelTableInUse++] = newSecondTable;
  595. ASSERT( g_FirstLevelTableInUse <= g_FirstLevelTableSize );
  596. //
  597. // Link it onto the global free list.
  598. //
  599. for (i = 0 ; i < SECOND_LEVEL_TABLE_SIZE ; i++)
  600. {
  601. newSecondTable[i].FirstLevelIndex = CapturedFirstTableInUse;
  602. newSecondTable[i].EntryType = ENTRY_TYPE_FREE;
  603. ExInterlockedPushEntrySList(
  604. &g_FreeOpaqueIdSListHead,
  605. &newSecondTable[i].FreeListEntry,
  606. &g_FreeOpaqueIdSListLock
  607. );
  608. }
  609. //
  610. // Remember that we don't need to free the second-level table.
  611. //
  612. newSecondTable = NULL;
  613. }
  614. else
  615. {
  616. //
  617. // The free list was not empty after we reacquired the lock,
  618. // indicating that another thread has already performed the
  619. // realloc. This is cool; we'll just free the pool we allocated
  620. // and return.
  621. //
  622. }
  623. //
  624. // Cleanup.
  625. //
  626. cleanup:
  627. RELEASE_WRITE_LOCK( &g_OpaqueIdTableLock );
  628. if (newSecondTable != NULL)
  629. {
  630. UL_FREE_POOL( newSecondTable, UL_OPAQUE_ID_TABLE_POOL_TAG );
  631. }
  632. if (newFirstTable != NULL)
  633. {
  634. UL_FREE_POOL( newFirstTable, UL_OPAQUE_ID_TABLE_POOL_TAG );
  635. }
  636. return STATUS_SUCCESS;
  637. } // UlpReallocOpaqueIdTables