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.

2055 lines
52 KiB

  1. /*++
  2. Copyright (c) 1998-2001 Microsoft Corporation
  3. Module Name:
  4. cache.cxx
  5. Abstract:
  6. Contains the HTTP response cache logic.
  7. Author:
  8. Michael Courage (mcourage) 17-May-1999
  9. Revision History:
  10. --*/
  11. #include "precomp.h"
  12. #include "cachep.h"
  13. PTRACE_LOG g_UriTraceLog;
  14. BOOLEAN g_InitUriCacheCalled;
  15. //
  16. // Global hash table
  17. //
  18. HASHTABLE g_UriCacheTable;
  19. LIST_ENTRY g_ZombieListHead;
  20. UL_URI_CACHE_CONFIG g_UriCacheConfig;
  21. UL_URI_CACHE_STATS g_UriCacheStats;
  22. UL_SPIN_LOCK g_UriCacheSpinLock;
  23. //
  24. // Scavenger stuff.
  25. //
  26. UL_SPIN_LOCK g_UriScavengerSpinLock;
  27. BOOLEAN g_UriScavengerInitialized;
  28. KDPC g_UriScavengerDpc;
  29. KTIMER g_UriScavengerTimer;
  30. KEVENT g_UriScavengerTerminationEvent;
  31. UL_WORK_ITEM g_UriScavengerWorkItem;
  32. LONG g_UriScavengerRunning;
  33. #ifdef ALLOC_PRAGMA
  34. #pragma alloc_text( INIT, UlInitializeUriCache )
  35. #pragma alloc_text( PAGE, UlTerminateUriCache )
  36. #pragma alloc_text( INIT, UlpInitializeScavenger )
  37. #pragma alloc_text( PAGE, UlCheckCachePreconditions )
  38. #pragma alloc_text( PAGE, UlCheckCacheResponseConditions )
  39. #pragma alloc_text( PAGE, UlCheckoutUriCacheEntry )
  40. #pragma alloc_text( PAGE, UlCheckinUriCacheEntry )
  41. #pragma alloc_text( PAGE, UlFlushCache )
  42. #pragma alloc_text( PAGE, UlpFlushFilterAll )
  43. #pragma alloc_text( PAGE, UlFlushCacheByProcess )
  44. #pragma alloc_text( PAGE, UlpFlushFilterProcess )
  45. #pragma alloc_text( PAGE, UlFlushCacheByUri )
  46. #pragma alloc_text( PAGE, UlpFlushUri )
  47. #pragma alloc_text( PAGE, UlAddCacheEntry )
  48. #pragma alloc_text( PAGE, UlpFilteredFlushUriCache )
  49. #pragma alloc_text( PAGE, UlpAddZombie )
  50. #pragma alloc_text( PAGE, UlpClearZombieList )
  51. #pragma alloc_text( PAGE, UlpDestroyUriCacheEntry )
  52. #pragma alloc_text( PAGE, UlpFlushFilterScavenger )
  53. #pragma alloc_text( PAGE, UlpQueryTranslateHeader )
  54. #endif // ALLOC_PRAGMA
  55. #if 0
  56. NOT PAGEABLE -- UlpCheckTableSpace
  57. NOT PAGEABLE -- UlpCheckSpaceAndAddEntryStats
  58. NOT PAGEABLE -- UlpRemoveEntryStats
  59. NOT PAGEABLE -- UlpTerminateScavenger
  60. NOT PAGEABLE -- UlpScavengerDpcRoutine
  61. NOT PAGEABLE -- UlpSetScavengerTimer
  62. NOT PAGEABLE -- UlpScavenger
  63. #endif
  64. /***************************************************************************++
  65. Routine Description:
  66. Performs global initialization of the URI cache.
  67. Return Value:
  68. NTSTATUS - Completion status.
  69. --***************************************************************************/
  70. NTSTATUS
  71. UlInitializeUriCache(
  72. PUL_CONFIG pConfig
  73. )
  74. {
  75. NTSTATUS Status = STATUS_SUCCESS;
  76. //
  77. // Sanity check.
  78. //
  79. PAGED_CODE();
  80. ASSERT( !g_InitUriCacheCalled );
  81. UlTrace(URI_CACHE, ("Http!UlInitializeUriCache\n"));
  82. if ( !g_InitUriCacheCalled )
  83. {
  84. PUL_URI_CACHE_CONFIG pUriConfig = &pConfig->UriConfig;
  85. g_UriCacheConfig.EnableCache = pUriConfig->EnableCache;
  86. g_UriCacheConfig.MaxCacheUriCount = pUriConfig->MaxCacheUriCount;
  87. g_UriCacheConfig.MaxCacheMegabyteCount =
  88. min((ULONG)pConfig->LargeMemMegabytes, pUriConfig->MaxCacheMegabyteCount);
  89. //
  90. // Don't want to scavenge more than once every ten seconds.
  91. // In particular, do not want to scavenge every 0 seconds, as the
  92. // machine will become completely unresponsive.
  93. //
  94. g_UriCacheConfig.ScavengerPeriod =
  95. max(pUriConfig->ScavengerPeriod, 10);
  96. g_UriCacheConfig.MaxUriBytes = pUriConfig->MaxUriBytes;
  97. g_UriCacheConfig.HashTableBits = pUriConfig->HashTableBits;
  98. RtlZeroMemory(&g_UriCacheStats, sizeof(g_UriCacheStats));
  99. InitializeListHead(&g_ZombieListHead);
  100. UlInitializeSpinLock( &g_UriCacheSpinLock, "g_UriCacheSpinLock" );
  101. if (g_UriCacheConfig.EnableCache)
  102. {
  103. Status = UlInitializeResource(
  104. &g_pUlNonpagedData->UriZombieResource,
  105. "UriZombieResource",
  106. 0,
  107. UL_ZOMBIE_RESOURCE_TAG
  108. );
  109. if (NT_SUCCESS(Status))
  110. {
  111. Status = UlInitializeHashTable(
  112. &g_UriCacheTable,
  113. PagedPool,
  114. g_UriCacheConfig.HashTableBits
  115. );
  116. if (NT_SUCCESS(Status))
  117. {
  118. ASSERT(IS_VALID_HASHTABLE(&g_UriCacheTable));
  119. CREATE_REF_TRACE_LOG( g_UriTraceLog,
  120. 2048 - REF_TRACE_OVERHEAD, 0 );
  121. UlpInitializeScavenger();
  122. g_InitUriCacheCalled = TRUE;
  123. }
  124. }
  125. else
  126. {
  127. UlDeleteResource(&g_pUlNonpagedData->UriZombieResource);
  128. }
  129. }
  130. else
  131. {
  132. UlTrace(URI_CACHE, ("URI Cache disabled.\n"));
  133. g_InitUriCacheCalled = TRUE;
  134. }
  135. }
  136. else
  137. {
  138. UlTrace(URI_CACHE, ("URI CACHE INITIALIZED TWICE!\n"));
  139. }
  140. return Status;
  141. } // UlInitializeUriCache
  142. /***************************************************************************++
  143. Routine Description:
  144. Performs global termination of the URI cache.
  145. --***************************************************************************/
  146. VOID
  147. UlTerminateUriCache(
  148. VOID
  149. )
  150. {
  151. NTSTATUS Status;
  152. //
  153. // Sanity check.
  154. //
  155. PAGED_CODE();
  156. UlTrace(URI_CACHE, ("Http!UlTerminateUriCache\n"));
  157. if (g_InitUriCacheCalled && g_UriCacheConfig.EnableCache)
  158. {
  159. // Must terminate the scavenger before destroying the hash table
  160. UlpTerminateScavenger();
  161. UlTerminateHashTable(&g_UriCacheTable);
  162. Status = UlDeleteResource(&g_pUlNonpagedData->UriZombieResource);
  163. ASSERT(NT_SUCCESS(Status));
  164. DESTROY_REF_TRACE_LOG( g_UriTraceLog );
  165. g_UriTraceLog = NULL;
  166. }
  167. g_InitUriCacheCalled = FALSE;
  168. } // UlTerminateUriCache
  169. /***************************************************************************++
  170. Routine Description:
  171. This routine checks a request (and its connection) to see if it's
  172. ok to serve this request from the cache. Basically we only accept
  173. simple GET requests with no conditional headers.
  174. Arguments:
  175. pHttpConn - The connection to be checked
  176. Return Value:
  177. BOOLEAN - True if it's ok to serve from cache
  178. --***************************************************************************/
  179. BOOLEAN
  180. UlCheckCachePreconditions(
  181. PUL_INTERNAL_REQUEST pRequest,
  182. PUL_HTTP_CONNECTION pHttpConn
  183. )
  184. {
  185. URI_PRECONDITION Precondition = URI_PRE_OK;
  186. //
  187. // Sanity check
  188. //
  189. PAGED_CODE();
  190. ASSERT( UL_IS_VALID_HTTP_CONNECTION(pHttpConn) );
  191. ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
  192. if (!g_UriCacheConfig.EnableCache)
  193. {
  194. Precondition = URI_PRE_DISABLED;
  195. }
  196. else if (pRequest->ParseState != ParseDoneState)
  197. {
  198. Precondition = URI_PRE_ENTITY_BODY;
  199. }
  200. else if (pRequest->Verb != HttpVerbGET)
  201. {
  202. Precondition = URI_PRE_VERB;
  203. }
  204. else if (HTTP_NOT_EQUAL_VERSION(pRequest->Version, 1, 1)
  205. && HTTP_NOT_EQUAL_VERSION(pRequest->Version, 1, 0))
  206. {
  207. Precondition = URI_PRE_PROTOCOL;
  208. }
  209. // check for Translate: f (DAV)
  210. else if ( UlpQueryTranslateHeader(pRequest) )
  211. {
  212. Precondition = URI_PRE_TRANSLATE;
  213. }
  214. // check for Authorization header
  215. else if (pRequest->HeaderValid[HttpHeaderAuthorization])
  216. {
  217. Precondition = URI_PRE_AUTHORIZATION;
  218. }
  219. //
  220. // check for some of the If-* headers
  221. // NOTE: See UlpCheckCacheControlHeaders for handling of other If-* headers
  222. //
  223. else if (pRequest->HeaderValid[HttpHeaderIfRange])
  224. {
  225. Precondition = URI_PRE_CONDITIONAL;
  226. }
  227. // CODEWORK: check for other evil headers
  228. else if (pRequest->HeaderValid[HttpHeaderRange])
  229. {
  230. Precondition = URI_PRE_OTHER_HEADER;
  231. }
  232. UlTrace(URI_CACHE, (
  233. "Http!UlCheckCachePreconditions(req = %p, httpconn = %p)\n"
  234. " OkToServeFromCache = %d, Precondition = %d\n",
  235. pRequest,
  236. pHttpConn,
  237. (URI_PRE_OK == Precondition) ? 1 : 0,
  238. Precondition
  239. ));
  240. //
  241. // update stats
  242. //
  243. if (URI_PRE_OK != Precondition) {
  244. InterlockedIncrement((PLONG) &g_UriCacheStats.MissPreconditionCount);
  245. }
  246. return (URI_PRE_OK == Precondition);
  247. } // UlCheckCachePreconditions
  248. /***************************************************************************++
  249. Routine Description:
  250. This routine checks a response to see if it's cacheable. Basically
  251. we'll take it if:
  252. * the cache policy is right
  253. * the size is small enough
  254. * there is room in the cache
  255. * we get the response all at once
  256. Arguments:
  257. pHttpConn - The connection to be checked
  258. Return Value:
  259. BOOLEAN - True if it's ok to serve from cache
  260. --***************************************************************************/
  261. BOOLEAN
  262. UlCheckCacheResponseConditions(
  263. PUL_INTERNAL_REQUEST pRequest,
  264. PUL_INTERNAL_RESPONSE pResponse,
  265. ULONG Flags,
  266. HTTP_CACHE_POLICY CachePolicy
  267. )
  268. {
  269. URI_PRECONDITION Precondition = URI_PRE_OK;
  270. //
  271. // Sanity check
  272. //
  273. PAGED_CODE();
  274. ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
  275. ASSERT( UL_IS_VALID_INTERNAL_RESPONSE(pResponse) );
  276. if (pRequest->CachePreconditions == FALSE) {
  277. Precondition = URI_PRE_REQUEST;
  278. }
  279. // check policy
  280. else if (CachePolicy.Policy == HttpCachePolicyNocache) {
  281. Precondition = URI_PRE_POLICY;
  282. }
  283. // check size of response
  284. else if ((pResponse->ResponseLength - pResponse->HeaderLength) >
  285. g_UriCacheConfig.MaxUriBytes) {
  286. Precondition = URI_PRE_SIZE;
  287. }
  288. // check for full response
  289. else if (Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) {
  290. Precondition = URI_PRE_FRAGMENT;
  291. }
  292. // check available cache table space
  293. else if (!UlpCheckTableSpace(pResponse->ResponseLength)) {
  294. Precondition = URI_PRE_NOMEMORY;
  295. }
  296. // Check for bogus responses
  297. // BUGBUG: this check should be in ioctl.cxx
  298. else if ((pResponse->ResponseLength < 1) || (pResponse->ChunkCount < 2)) {
  299. Precondition = URI_PRE_BOGUS;
  300. }
  301. UlTrace(URI_CACHE, (
  302. "Http!UlCheckCacheResponseConditions(pRequest = %p, pResponse = %p)\n"
  303. " OkToCache = %d, Precondition = %d\n",
  304. pRequest,
  305. pResponse,
  306. (URI_PRE_OK == Precondition),
  307. Precondition
  308. ));
  309. return (URI_PRE_OK == Precondition);
  310. } // UlCheckCacheResponseConditions
  311. /***************************************************************************++
  312. Routine Description:
  313. This routine does a cache lookup to see if there is a valid entry
  314. corresponding to the request URI.
  315. Arguments:
  316. pRequest - The request
  317. Return Value:
  318. PUL_URI_CACHE_ENTRY - Pointer to the entry, if found. NULL otherwise.
  319. --***************************************************************************/
  320. PUL_URI_CACHE_ENTRY
  321. UlCheckoutUriCacheEntry(
  322. PUL_INTERNAL_REQUEST pRequest
  323. )
  324. {
  325. PUL_URI_CACHE_ENTRY pUriCacheEntry = NULL;
  326. ULONG BucketNumber;
  327. URI_KEY Key;
  328. //
  329. // Sanity check
  330. //
  331. PAGED_CODE();
  332. //
  333. // find bucket
  334. //
  335. Key.Hash = pRequest->CookedUrl.Hash;
  336. Key.Length = pRequest->CookedUrl.Length;
  337. Key.pUri = pRequest->CookedUrl.pUrl;
  338. ASSERT(!g_UriCacheConfig.EnableCache
  339. || IS_VALID_HASHTABLE(&g_UriCacheTable));
  340. pUriCacheEntry = UlGetFromHashTable(&g_UriCacheTable, &Key);
  341. if (pUriCacheEntry != NULL)
  342. {
  343. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  344. ASSERT(! pUriCacheEntry->Zombie);
  345. InterlockedIncrement((PLONG) &pUriCacheEntry->HitCount);
  346. // reset scavenger counter
  347. pUriCacheEntry->ScavengerTicks = 0;
  348. UlTrace(URI_CACHE, (
  349. "Http!UlCheckoutUriCacheEntry(pUriCacheEntry %p, '%ls') "
  350. "refcount = %d\n",
  351. pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
  352. pUriCacheEntry->ReferenceCount
  353. ));
  354. //
  355. // update stats
  356. //
  357. InterlockedIncrement((PLONG) &g_UriCacheStats.HitCount);
  358. UlIncCounter(HttpGlobalCounterUriCacheHits);
  359. }
  360. else
  361. {
  362. InterlockedIncrement((PLONG) &g_UriCacheStats.MissTableCount);
  363. UlIncCounter(HttpGlobalCounterUriCacheMisses);
  364. }
  365. return pUriCacheEntry;
  366. } // UlCheckoutUriCacheEntry
  367. /***************************************************************************++
  368. Routine Description:
  369. Decrements the refcount on a cache entry. Cleans up non-cached
  370. entries.
  371. Arguments:
  372. pUriCacheEntry - the entry to deref
  373. --***************************************************************************/
  374. VOID
  375. UlCheckinUriCacheEntry(
  376. PUL_URI_CACHE_ENTRY pUriCacheEntry
  377. )
  378. {
  379. LONG ReferenceCount;
  380. BOOLEAN Cached;
  381. //
  382. // Sanity check
  383. //
  384. PAGED_CODE();
  385. ASSERT(!g_UriCacheConfig.EnableCache
  386. || IS_VALID_HASHTABLE(&g_UriCacheTable));
  387. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  388. //
  389. // check to see if it was cached while we have a reference
  390. //
  391. Cached = pUriCacheEntry->Cached;
  392. //
  393. // decrement count
  394. //
  395. ReferenceCount = DEREFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, CHECKIN);
  396. ASSERT(ReferenceCount >= 0);
  397. if (ReferenceCount == 0)
  398. {
  399. // CODEWORK: add this entry to the zombie list, instead of
  400. // deleting synchronously
  401. // Make sure the zombie list gets periodically purged, even
  402. // if the ScavengerPeriod is huge.
  403. // The refcount of a cached entry can be zero if the entry
  404. // was flushed from the cache while the entry was being
  405. // sent back to a client.
  406. }
  407. else
  408. {
  409. // If the reference was not marked as cached, its refcount
  410. // must now be zero
  411. ASSERT(Cached);
  412. }
  413. } // UlCheckinUriCacheEntry
  414. /***************************************************************************++
  415. Routine Description:
  416. Removes all cache entries
  417. --***************************************************************************/
  418. VOID
  419. UlFlushCache(
  420. VOID
  421. )
  422. {
  423. //
  424. // sanity check
  425. //
  426. PAGED_CODE();
  427. ASSERT(!g_UriCacheConfig.EnableCache
  428. || IS_VALID_HASHTABLE(&g_UriCacheTable));
  429. if (g_UriCacheConfig.EnableCache) {
  430. UlTrace(URI_CACHE, (
  431. "Http!UlFlushCache()\n"
  432. ));
  433. UlpFilteredFlushUriCache(UlpFlushFilterAll, NULL);
  434. }
  435. } // UlFlushCache
  436. /***************************************************************************++
  437. Routine Description:
  438. A filter for UlFlushCache. Called by UlpFilteredFlushUriCache.
  439. Arguments:
  440. pUriCacheEntry - the entry to check
  441. pContext - ignored
  442. --***************************************************************************/
  443. UL_CACHE_PREDICATE
  444. UlpFlushFilterAll(
  445. IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
  446. IN PVOID pContext
  447. )
  448. {
  449. PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
  450. //
  451. // Sanity check
  452. //
  453. PAGED_CODE();
  454. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  455. ASSERT( pUriFilterContext != NULL
  456. && URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature
  457. && pUriFilterContext->pCallerContext == NULL );
  458. UlTrace(URI_CACHE, (
  459. "Http!UlpFlushFilterAll(pUriCacheEntry %p '%ls') refcount = %d\n",
  460. pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
  461. pUriCacheEntry->ReferenceCount));
  462. return UlpZombifyEntry(
  463. TRUE,
  464. pUriCacheEntry,
  465. pUriFilterContext
  466. );
  467. } // UlpFlushFilterAll
  468. /***************************************************************************++
  469. Routine Description:
  470. Removes any cache entries that were created by the given process.
  471. Arguments:
  472. pProcess - a process that is going away
  473. --***************************************************************************/
  474. VOID
  475. UlFlushCacheByProcess(
  476. PUL_APP_POOL_PROCESS pProcess
  477. )
  478. {
  479. //
  480. // sanity check
  481. //
  482. PAGED_CODE();
  483. ASSERT( IS_VALID_AP_PROCESS(pProcess) );
  484. ASSERT(!g_UriCacheConfig.EnableCache
  485. || IS_VALID_HASHTABLE(&g_UriCacheTable));
  486. if (g_UriCacheConfig.EnableCache) {
  487. UlTrace(URI_CACHE, (
  488. "Http!UlFlushCacheByProcess(proc = %p)\n",
  489. pProcess
  490. ));
  491. UlpFilteredFlushUriCache(UlpFlushFilterProcess, pProcess);
  492. }
  493. } // UlFlushCacheByProcess
  494. /***************************************************************************++
  495. Routine Description:
  496. Removes any cache entries that were created by the given process
  497. whose url has a given prefix.
  498. Arguments:
  499. pUri - the uri prefix to match against
  500. Length - length of the prefix
  501. Flags - HTTP_FLUSH_RESPONSE_FLAG_RECURSIVE indicates a tree flush
  502. pProcess - the process that made the call
  503. --***************************************************************************/
  504. VOID
  505. UlFlushCacheByUri(
  506. IN PWSTR pUri,
  507. IN ULONG Length,
  508. IN ULONG Flags,
  509. PUL_APP_POOL_PROCESS pProcess
  510. )
  511. {
  512. //
  513. // sanity check
  514. //
  515. PAGED_CODE();
  516. ASSERT( IS_VALID_AP_PROCESS(pProcess) );
  517. ASSERT(!g_UriCacheConfig.EnableCache
  518. || IS_VALID_HASHTABLE(&g_UriCacheTable));
  519. if (g_UriCacheConfig.EnableCache)
  520. {
  521. UlTrace(URI_CACHE, (
  522. "Http!UlFlushCacheByUri(\n"
  523. " uri = '%S'\n"
  524. " len = %d\n"
  525. " flags = %08x\n"
  526. " proc = %p\n",
  527. pUri,
  528. Length,
  529. Flags,
  530. pProcess
  531. ));
  532. if (Flags & HTTP_FLUSH_RESPONSE_FLAG_RECURSIVE) {
  533. //
  534. // CODEWORK: restrict the flush to the ones they actually
  535. // asked for!
  536. //
  537. UlpFilteredFlushUriCache(UlpFlushFilterProcess, pProcess);
  538. } else {
  539. UlpFlushUri(
  540. pUri,
  541. Length,
  542. pProcess
  543. );
  544. UlpClearZombieList();
  545. }
  546. }
  547. } // UlFlushCacheByUri
  548. /***************************************************************************++
  549. Routine Description:
  550. Removes a single URI from the table if the name and process match an
  551. entry.
  552. Arguments:
  553. --***************************************************************************/
  554. VOID
  555. UlpFlushUri(
  556. IN PWSTR pUri,
  557. IN ULONG Length,
  558. PUL_APP_POOL_PROCESS pProcess
  559. )
  560. {
  561. PUL_URI_CACHE_ENTRY pUriCacheEntry = NULL;
  562. URI_KEY Key;
  563. LONG ReferenceCount;
  564. //
  565. // Sanity check
  566. //
  567. PAGED_CODE();
  568. //
  569. // find bucket
  570. //
  571. Key.Hash = HashRandomizeBits(HashStringNoCaseW(pUri, 0));
  572. Key.Length = Length;
  573. Key.pUri = pUri;
  574. pUriCacheEntry = UlDeleteFromHashTable(&g_UriCacheTable, &Key);
  575. if (NULL != pUriCacheEntry)
  576. {
  577. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  578. UlTrace(URI_CACHE, (
  579. "Http!UlpFlushUri(pUriCacheEntry %p '%ls') refcount = %d\n",
  580. pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
  581. pUriCacheEntry->ReferenceCount));
  582. DEREFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, FLUSH);
  583. //
  584. // Perfmon counters
  585. //
  586. UlIncCounter(HttpGlobalCounterTotalFlushedUris);
  587. }
  588. } // UlpFlushUri
  589. /***************************************************************************++
  590. Routine Description:
  591. A filter for UlFlushCacheByProcess. Called by UlpFilteredFlushUriCache.
  592. Arguments:
  593. pUriCacheEntry - the entry to check
  594. pContext - pointer to the UL_APP_POOL_PROCESS that's going away
  595. --***************************************************************************/
  596. UL_CACHE_PREDICATE
  597. UlpFlushFilterProcess(
  598. IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
  599. IN PVOID pContext
  600. )
  601. {
  602. PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
  603. PUL_APP_POOL_PROCESS pProcess;
  604. //
  605. // Sanity check
  606. //
  607. PAGED_CODE();
  608. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  609. ASSERT( pUriFilterContext != NULL
  610. && URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature
  611. && pUriFilterContext->pCallerContext != NULL );
  612. pProcess = (PUL_APP_POOL_PROCESS) pUriFilterContext->pCallerContext;
  613. ASSERT( IS_VALID_AP_PROCESS(pProcess) );
  614. return UlpZombifyEntry(
  615. (pProcess == pUriCacheEntry->pProcess),
  616. pUriCacheEntry,
  617. pUriFilterContext
  618. );
  619. } // UlpFlushFilterProcess
  620. /***************************************************************************++
  621. Routine Description:
  622. Checks the hash table to make sure there is room for one more
  623. entry of a given size.
  624. This routine must be called with the table resource held.
  625. Arguments:
  626. EntrySize - the size in bytes of the entry to be added
  627. --***************************************************************************/
  628. BOOLEAN
  629. UlpCheckTableSpace(
  630. IN ULONGLONG EntrySize
  631. )
  632. {
  633. ULONG UriCount;
  634. ULONGLONG ByteCount;
  635. UriCount = g_UriCacheStats.UriCount + 1;
  636. ByteCount = g_UriCacheStats.ByteCount + EntrySize;
  637. //
  638. // CODEWORK: MaxCacheMegabyteCount of zero should mean adaptive limit,
  639. // but for now I'll take it to mean "no limit".
  640. //
  641. if (g_UriCacheConfig.MaxCacheMegabyteCount == 0) {
  642. ByteCount = 0;
  643. }
  644. //
  645. // MaxCacheUriCount of zero means no limit on number of URIs cached
  646. //
  647. if (g_UriCacheConfig.MaxCacheUriCount == 0) {
  648. UriCount = 0;
  649. }
  650. if (
  651. UriCount <= g_UriCacheConfig.MaxCacheUriCount &&
  652. ByteCount <= (g_UriCacheConfig.MaxCacheMegabyteCount << MEGABYTE_SHIFT)
  653. )
  654. {
  655. return TRUE;
  656. } else {
  657. UlTrace(URI_CACHE, (
  658. "Http!UlpCheckTableSpace(%d) FALSE\n"
  659. " UriCount = %d\n"
  660. " ByteCount = %I64d (%dMB)\n"
  661. " MaxCacheUriCount = %d\n"
  662. " MaxCacheMegabyteCount = %dMB\n",
  663. EntrySize,
  664. g_UriCacheStats.UriCount,
  665. g_UriCacheStats.ByteCount,
  666. g_UriCacheStats.ByteCount >> MEGABYTE_SHIFT,
  667. g_UriCacheConfig.MaxCacheUriCount,
  668. g_UriCacheConfig.MaxCacheMegabyteCount
  669. ));
  670. return FALSE;
  671. }
  672. } // UlpCheckTableSpace
  673. /***************************************************************************++
  674. Routine Description:
  675. Tries to add a cache entry to the hash table.
  676. Arguments:
  677. pUriCacheEntry - the entry to be added
  678. --***************************************************************************/
  679. VOID
  680. UlAddCacheEntry(
  681. PUL_URI_CACHE_ENTRY pUriCacheEntry
  682. )
  683. {
  684. ULC_RETCODE rc = ULC_SUCCESS;
  685. //
  686. // Sanity check
  687. //
  688. PAGED_CODE();
  689. ASSERT(!g_UriCacheConfig.EnableCache
  690. || IS_VALID_HASHTABLE(&g_UriCacheTable));
  691. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  692. ASSERT(! pUriCacheEntry->Zombie);
  693. pUriCacheEntry->BucketEntry.Next = NULL;
  694. pUriCacheEntry->Cached = FALSE;
  695. // First, check if still has space for storing the cache entry
  696. if (UlpCheckSpaceAndAddEntryStats(pUriCacheEntry))
  697. {
  698. pUriCacheEntry->Cached = TRUE;
  699. //
  700. // Insert this record into the hash table
  701. // Check first to see if the key already presents
  702. //
  703. rc = UlAddToHashTable(&g_UriCacheTable, pUriCacheEntry);
  704. }
  705. UlTrace(URI_CACHE, (
  706. "Http!UlAddCacheEntry(urientry %p '%ls') %s added to table. "
  707. "RefCount=%d, lkrc=%d.\n",
  708. pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
  709. pUriCacheEntry->Cached ? "was" : "was not",
  710. pUriCacheEntry->ReferenceCount,
  711. rc
  712. ));
  713. } // UlAddCacheEntry
  714. /***************************************************************************++
  715. Routine Description:
  716. Check to see if we have space to add this cache entry and if so update
  717. cache statistics to reflect the addition of an entry. This has to be
  718. done together inside a lock.
  719. Arguments:
  720. pUriCacheEntry - entry being added
  721. --***************************************************************************/
  722. BOOLEAN
  723. UlpCheckSpaceAndAddEntryStats(
  724. PUL_URI_CACHE_ENTRY pUriCacheEntry
  725. )
  726. {
  727. KIRQL OldIrql;
  728. ULONG EntrySize;
  729. //
  730. // Sanity check
  731. //
  732. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  733. EntrySize = pUriCacheEntry->HeaderLength + pUriCacheEntry->ContentLength;
  734. if (!pUriCacheEntry->LongTermCacheable)
  735. {
  736. //
  737. // Schedule the scavenger immediately, but only do it once
  738. //
  739. if (FALSE == InterlockedExchange(&g_UriScavengerRunning, TRUE))
  740. {
  741. UL_QUEUE_WORK_ITEM(
  742. &g_UriScavengerWorkItem,
  743. &UlpScavenger
  744. );
  745. }
  746. return FALSE;
  747. }
  748. UlAcquireSpinLock( &g_UriCacheSpinLock, &OldIrql );
  749. if (UlpCheckTableSpace(EntrySize))
  750. {
  751. g_UriCacheStats.UriCount++;
  752. g_UriCacheStats.UriAddedTotal++;
  753. g_UriCacheStats.UriCountMax = MAX(
  754. g_UriCacheStats.UriCountMax,
  755. g_UriCacheStats.UriCount
  756. );
  757. g_UriCacheStats.ByteCount += EntrySize;
  758. g_UriCacheStats.ByteCountMax = MAX(
  759. g_UriCacheStats.ByteCountMax,
  760. g_UriCacheStats.ByteCount
  761. );
  762. UlReleaseSpinLock( &g_UriCacheSpinLock, OldIrql );
  763. UlTrace(URI_CACHE, (
  764. "Http!UlpCheckSpaceAndAddEntryStats (urientry %p '%ls')\n",
  765. pUriCacheEntry, pUriCacheEntry->UriKey.pUri
  766. ));
  767. //
  768. // Perfmon counters
  769. //
  770. UlIncCounter(HttpGlobalCounterCurrentUrisCached);
  771. UlIncCounter(HttpGlobalCounterTotalUrisCached);
  772. return TRUE;
  773. }
  774. UlReleaseSpinLock( &g_UriCacheSpinLock, OldIrql );
  775. return FALSE;
  776. } // UlpCheckSpaceAndAddEntryStats
  777. /***************************************************************************++
  778. Routine Description:
  779. Updates cache statistics to reflect the removal of an entry
  780. Arguments:
  781. pUriCacheEntry - entry being added
  782. --***************************************************************************/
  783. VOID
  784. UlpRemoveEntryStats(
  785. PUL_URI_CACHE_ENTRY pUriCacheEntry
  786. )
  787. {
  788. KIRQL OldIrql;
  789. ULONG EntrySize;
  790. //
  791. // Sanity check
  792. //
  793. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  794. ASSERT( pUriCacheEntry->Cached );
  795. EntrySize = pUriCacheEntry->HeaderLength + pUriCacheEntry->ContentLength;
  796. UlAcquireSpinLock( &g_UriCacheSpinLock, &OldIrql );
  797. g_UriCacheStats.UriCount--;
  798. g_UriCacheStats.ByteCount -= EntrySize;
  799. UlReleaseSpinLock( &g_UriCacheSpinLock, OldIrql );
  800. UlTrace(URI_CACHE, (
  801. "Http!UlpRemoveEntryStats (urientry %p '%ls')\n",
  802. pUriCacheEntry, pUriCacheEntry->UriKey.pUri
  803. ));
  804. //
  805. // Perfmon counters
  806. //
  807. UlDecCounter(HttpGlobalCounterCurrentUrisCached);
  808. } // UlpRemoveEntryStats
  809. /***************************************************************************++
  810. Routine Description:
  811. Helper function for the filter callbacks indirectly invoked by
  812. UlpFilteredFlushUriCache. Adds deleteable entries to a temporary
  813. list.
  814. Arguments:
  815. MustZombify - if TRUE, add entry to the private zombie list
  816. pUriCacheEntry - entry to zombify
  817. pUriFilterContext - contains private list
  818. --***************************************************************************/
  819. UL_CACHE_PREDICATE
  820. UlpZombifyEntry(
  821. BOOLEAN MustZombify,
  822. IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
  823. IN PURI_FILTER_CONTEXT pUriFilterContext
  824. )
  825. {
  826. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  827. ASSERT(URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature);
  828. ASSERT(! pUriCacheEntry->Zombie);
  829. ASSERT(NULL == pUriCacheEntry->ZombieListEntry.Flink);
  830. if (MustZombify)
  831. {
  832. //
  833. // Temporarily bump the refcount up so that it won't go down
  834. // to zero when it's removed from the hash table, automatically
  835. // invoking UlpDestroyUriCacheEntry, which we are trying to defer.
  836. //
  837. pUriCacheEntry->ZombieAddReffed = TRUE;
  838. REFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, ZOMBIFY);
  839. InsertTailList(
  840. &pUriFilterContext->ZombieListHead,
  841. &pUriCacheEntry->ZombieListEntry);
  842. pUriCacheEntry->Zombie = TRUE;
  843. //
  844. // reset timer so we can track how long an entry is on the list
  845. //
  846. pUriCacheEntry->ScavengerTicks = 0;
  847. ++ pUriFilterContext->ZombieCount;
  848. // now remove it from the hash table
  849. return ULC_DELETE;
  850. }
  851. // do not remove pUriCacheEntry from table
  852. return ULC_NO_ACTION;
  853. } // UlpZombifyEntry
  854. /***************************************************************************++
  855. Routine Description:
  856. Adds a list of entries to the global zombie list, then calls
  857. UlpClearZombieList. This cleans up the list of deferred deletions
  858. built up by UlpFilteredFlushUriCache.
  859. Runs at passive level.
  860. Arguments:
  861. pWorkItem - workitem within a URI_FILTER_CONTEXT containing private list
  862. --***************************************************************************/
  863. VOID
  864. UlpZombifyList(
  865. IN PUL_WORK_ITEM pWorkItem
  866. )
  867. {
  868. PAGED_CODE();
  869. ASSERT(NULL != pWorkItem);
  870. PURI_FILTER_CONTEXT pUriFilterContext
  871. = CONTAINING_RECORD(pWorkItem, URI_FILTER_CONTEXT, WorkItem);
  872. ASSERT(URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature);
  873. UlTrace(URI_CACHE, (
  874. "http!UlpZombifyList, ctxt = %p\n",
  875. pUriFilterContext
  876. ));
  877. UlAcquireResourceExclusive(&g_pUlNonpagedData->UriZombieResource, TRUE);
  878. //
  879. // Splice the entire private list into the head of the Zombie list
  880. //
  881. ASSERT(! IsListEmpty(&pUriFilterContext->ZombieListHead));
  882. PLIST_ENTRY pContextHead = pUriFilterContext->ZombieListHead.Flink;
  883. PLIST_ENTRY pContextTail = pUriFilterContext->ZombieListHead.Blink;
  884. PLIST_ENTRY pZombieHead = g_ZombieListHead.Flink;
  885. pContextTail->Flink = pZombieHead;
  886. pZombieHead->Blink = pContextTail;
  887. g_ZombieListHead.Flink = pContextHead;
  888. pContextHead->Blink = &g_ZombieListHead;
  889. // Update stats
  890. g_UriCacheStats.ZombieCount += pUriFilterContext->ZombieCount;
  891. g_UriCacheStats.ZombieCountMax = MAX(g_UriCacheStats.ZombieCount,
  892. g_UriCacheStats.ZombieCountMax);
  893. #if DBG
  894. PLIST_ENTRY pEntry;
  895. ULONG ZombieCount;
  896. // Walk forwards through the zombie list and check that it contains
  897. // exactly as many valid zombied UriCacheEntries as we expect.
  898. for (pEntry = g_ZombieListHead.Flink, ZombieCount = 0;
  899. pEntry != &g_ZombieListHead;
  900. pEntry = pEntry->Flink, ++ZombieCount)
  901. {
  902. PUL_URI_CACHE_ENTRY pUriCacheEntry
  903. = CONTAINING_RECORD(pEntry, UL_URI_CACHE_ENTRY, ZombieListEntry);
  904. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  905. ASSERT(pUriCacheEntry->Zombie);
  906. ASSERT(pUriCacheEntry->ZombieAddReffed
  907. ? pUriCacheEntry->ScavengerTicks == 0
  908. : pUriCacheEntry->ScavengerTicks > 0);
  909. ASSERT(ZombieCount < g_UriCacheStats.ZombieCount);
  910. }
  911. ASSERT(ZombieCount == g_UriCacheStats.ZombieCount);
  912. // And backwards too, like Ginger Rogers
  913. for (pEntry = g_ZombieListHead.Blink, ZombieCount = 0;
  914. pEntry != &g_ZombieListHead;
  915. pEntry = pEntry->Blink, ++ZombieCount)
  916. {
  917. PUL_URI_CACHE_ENTRY pUriCacheEntry
  918. = CONTAINING_RECORD(pEntry, UL_URI_CACHE_ENTRY, ZombieListEntry);
  919. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  920. ASSERT(pUriCacheEntry->Zombie);
  921. ASSERT(pUriCacheEntry->ZombieAddReffed
  922. ? pUriCacheEntry->ScavengerTicks == 0
  923. : pUriCacheEntry->ScavengerTicks > 0);
  924. ASSERT(ZombieCount < g_UriCacheStats.ZombieCount);
  925. }
  926. ASSERT(ZombieCount == g_UriCacheStats.ZombieCount);
  927. #endif // DBG
  928. UlReleaseResource(&g_pUlNonpagedData->UriZombieResource);
  929. UL_FREE_POOL_WITH_SIG(pUriFilterContext, URI_FILTER_CONTEXT_POOL_TAG);
  930. // Now purge those entries, if there are no outstanding references
  931. UlpClearZombieList();
  932. } // UlpZombifyList
  933. /***************************************************************************++
  934. Routine Description:
  935. Removes entries based on a caller specified filter. The caller
  936. provides a boolean function which takes a cache entry as a
  937. parameter. The function will be called with each item in the cache.
  938. The function should conclude with a call to UlpZombifyEntry, passing
  939. in whether or not the item should be deleted. See sample usage
  940. elsewhere in this file.
  941. Arguments:
  942. pFilterRoutine - A pointer to the filter function
  943. pCallerContext - a parameter to the filter function
  944. --***************************************************************************/
  945. VOID
  946. UlpFilteredFlushUriCache(
  947. IN PUL_URI_FILTER pFilterRoutine,
  948. IN PVOID pCallerContext
  949. )
  950. {
  951. PURI_FILTER_CONTEXT pUriFilterContext;
  952. LONG ZombieCount = 0;
  953. //
  954. // sanity check
  955. //
  956. PAGED_CODE();
  957. ASSERT( NULL != pFilterRoutine );
  958. //
  959. // Perfmon counters
  960. //
  961. UlIncCounter(HttpGlobalCounterUriCacheFlushes);
  962. pUriFilterContext = UL_ALLOCATE_STRUCT(
  963. NonPagedPool,
  964. URI_FILTER_CONTEXT,
  965. URI_FILTER_CONTEXT_POOL_TAG);
  966. if (pUriFilterContext == NULL)
  967. return;
  968. pUriFilterContext->Signature = URI_FILTER_CONTEXT_POOL_TAG;
  969. InitializeListHead(&pUriFilterContext->ZombieListHead);
  970. pUriFilterContext->pCallerContext = pCallerContext;
  971. pUriFilterContext->ZombieCount = 0;
  972. UlTrace(URI_CACHE, (
  973. "Http!UlFilteredFlushUriCache(filt = %p, ctxt = %p)\n",
  974. pFilterRoutine, pUriFilterContext
  975. ));
  976. if (IS_VALID_HASHTABLE(&g_UriCacheTable))
  977. {
  978. ZombieCount = UlFilterFlushHashTable(
  979. &g_UriCacheTable,
  980. pFilterRoutine,
  981. pUriFilterContext
  982. );
  983. ASSERT(ZombieCount == pUriFilterContext->ZombieCount);
  984. if (0 != ZombieCount)
  985. {
  986. UlAddCounter(HttpGlobalCounterTotalFlushedUris, ZombieCount);
  987. UL_QUEUE_WORK_ITEM(
  988. &pUriFilterContext->WorkItem,
  989. UlpZombifyList
  990. );
  991. }
  992. else
  993. {
  994. UL_FREE_POOL_WITH_SIG(pUriFilterContext,
  995. URI_FILTER_CONTEXT_POOL_TAG);
  996. }
  997. UlTrace(URI_CACHE, (
  998. "Http!UlFilteredFlushUriCache(filt = %p, caller ctxt = %p)"
  999. " Zombified: %d\n",
  1000. pFilterRoutine,
  1001. pCallerContext,
  1002. ZombieCount
  1003. ));
  1004. }
  1005. } // UlpFilteredFlushUriCache
  1006. /***************************************************************************++
  1007. Routine Description:
  1008. Scans the zombie list for entries whose refcount has dropped to "zero".
  1009. (The calling routine is generally expected to have added a reference
  1010. (and set the ZombieAddReffed field within the entries), so that
  1011. otherwise unreferenced entries will actually have a refcount of one. It
  1012. works this way because we don't want the scavenger directly triggering
  1013. calls to UlpDestroyUriCacheEntry)
  1014. --***************************************************************************/
  1015. VOID
  1016. UlpClearZombieList(
  1017. VOID
  1018. )
  1019. {
  1020. ULONG ZombiesFreed = 0;
  1021. ULONG ZombiesSpared = 0;
  1022. PLIST_ENTRY pCurrent;
  1023. LONG ReferenceCount;
  1024. //
  1025. // sanity check
  1026. //
  1027. PAGED_CODE();
  1028. UlAcquireResourceExclusive(&g_pUlNonpagedData->UriZombieResource, TRUE);
  1029. pCurrent = g_ZombieListHead.Flink;
  1030. while (pCurrent != &g_ZombieListHead)
  1031. {
  1032. PUL_URI_CACHE_ENTRY pUriCacheEntry
  1033. = CONTAINING_RECORD(pCurrent, UL_URI_CACHE_ENTRY, ZombieListEntry);
  1034. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  1035. ASSERT(pUriCacheEntry->Zombie);
  1036. //
  1037. // get next entry now, because we might destroy this one
  1038. //
  1039. pCurrent = pCurrent->Flink;
  1040. //
  1041. // ReferenceCount is modified with interlocked ops, but in
  1042. // this case we know the ReferenceCount can't go up on
  1043. // a Zombie, and if an entry hits one just after we look
  1044. // at it, we'll just get it on the next pass
  1045. //
  1046. if (pUriCacheEntry->ZombieAddReffed)
  1047. {
  1048. bool LastRef = (pUriCacheEntry->ReferenceCount == 1);
  1049. if (LastRef)
  1050. {
  1051. RemoveEntryList(&pUriCacheEntry->ZombieListEntry);
  1052. pUriCacheEntry->ZombieListEntry.Flink = NULL;
  1053. ++ ZombiesFreed;
  1054. ASSERT(g_UriCacheStats.ZombieCount > 0);
  1055. -- g_UriCacheStats.ZombieCount;
  1056. }
  1057. else
  1058. {
  1059. // track age of zombie
  1060. ++ pUriCacheEntry->ScavengerTicks;
  1061. ++ ZombiesSpared;
  1062. }
  1063. pUriCacheEntry->ZombieAddReffed = FALSE;
  1064. DEREFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, UNZOMBIFY);
  1065. }
  1066. else
  1067. {
  1068. ASSERT(pUriCacheEntry->ScavengerTicks > 0);
  1069. // If we've released the zombie reference on it and it's still
  1070. // on the zombie list, somebody'd better have a reference.
  1071. ASSERT(pUriCacheEntry->ReferenceCount > 0);
  1072. // track age of zombie
  1073. ++ pUriCacheEntry->ScavengerTicks;
  1074. ++ ZombiesSpared;
  1075. if (pUriCacheEntry->ScavengerTicks > ZOMBIE_AGE_THRESHOLD)
  1076. {
  1077. UlTrace(URI_CACHE, (
  1078. "Http!UlpClearZombieList()\n"
  1079. " WARNING: %p '%ls' (refs = %d) "
  1080. "has been a zombie for %d ticks!\n",
  1081. pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
  1082. pUriCacheEntry->ReferenceCount,
  1083. pUriCacheEntry->ScavengerTicks
  1084. ));
  1085. }
  1086. }
  1087. }
  1088. ASSERT(ZombiesSpared == g_UriCacheStats.ZombieCount);
  1089. ASSERT((g_UriCacheStats.ZombieCount == 0)
  1090. == IsListEmpty(&g_ZombieListHead));
  1091. UlReleaseResource(&g_pUlNonpagedData->UriZombieResource);
  1092. UlTrace(URI_CACHE, (
  1093. "Http!UlpClearZombieList(): Freed = %d, Remaining = %d.\n",
  1094. ZombiesFreed,
  1095. ZombiesSpared
  1096. ));
  1097. } // UlpClearZombieList
  1098. /***************************************************************************++
  1099. Routine Description:
  1100. Frees a URI entry to the pool. Removes references to other objects.
  1101. Arguments:
  1102. pTracker - Supplies the UL_READ_TRACKER to manipulate.
  1103. --***************************************************************************/
  1104. VOID
  1105. UlpDestroyUriCacheEntry(
  1106. PUL_URI_CACHE_ENTRY pUriCacheEntry
  1107. )
  1108. {
  1109. //
  1110. // Sanity check
  1111. //
  1112. PAGED_CODE();
  1113. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  1114. // CODEWORK: real cleanup will need to release
  1115. // config & process references.
  1116. ASSERT(0 == pUriCacheEntry->ReferenceCount);
  1117. UlTrace(URI_CACHE, (
  1118. "Http!UlpDestroyUriCacheEntry: Entry %p, '%ls', Refs=%d\n",
  1119. pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
  1120. pUriCacheEntry->ReferenceCount
  1121. ));
  1122. //
  1123. // Release the UL_URL_CONFIG_GROUP_INFO block
  1124. //
  1125. UlpConfigGroupInfoRelease(&pUriCacheEntry->ConfigInfo);
  1126. UlLargeMemFree(pUriCacheEntry->pResponseMdl);
  1127. //
  1128. // Remove from g_ZombieListHead if neccessary
  1129. //
  1130. if (pUriCacheEntry->ZombieListEntry.Flink != NULL)
  1131. {
  1132. ASSERT(pUriCacheEntry->Zombie);
  1133. ASSERT(! pUriCacheEntry->ZombieAddReffed);
  1134. UlAcquireResourceExclusive(
  1135. &g_pUlNonpagedData->UriZombieResource,
  1136. TRUE);
  1137. RemoveEntryList(&pUriCacheEntry->ZombieListEntry);
  1138. ASSERT(g_UriCacheStats.ZombieCount > 0);
  1139. -- g_UriCacheStats.ZombieCount;
  1140. UlReleaseResource(&g_pUlNonpagedData->UriZombieResource);
  1141. }
  1142. UL_FREE_POOL_WITH_SIG(
  1143. pUriCacheEntry,
  1144. UL_URI_CACHE_ENTRY_POOL_TAG
  1145. );
  1146. } // UlpDestroyUriCacheEntry
  1147. /***************************************************************************++
  1148. Routine Description:
  1149. Initializes the cache scavenger.
  1150. --***************************************************************************/
  1151. VOID
  1152. UlpInitializeScavenger(
  1153. VOID
  1154. )
  1155. {
  1156. UlTrace(URI_CACHE, (
  1157. "Http!UlpInitializeScavenger\n"
  1158. ));
  1159. g_UriScavengerInitialized = TRUE;
  1160. g_UriScavengerRunning = FALSE;
  1161. UlInitializeSpinLock(
  1162. &g_UriScavengerSpinLock,
  1163. "g_UriScavengerSpinLock"
  1164. );
  1165. KeInitializeDpc(
  1166. &g_UriScavengerDpc, // DPC object
  1167. &UlpScavengerDpcRoutine, // DPC routine
  1168. NULL // context
  1169. );
  1170. KeInitializeTimer(
  1171. &g_UriScavengerTimer
  1172. );
  1173. KeInitializeEvent(
  1174. &g_UriScavengerTerminationEvent,
  1175. NotificationEvent,
  1176. FALSE
  1177. );
  1178. UlpSetScavengerTimer();
  1179. } // UlpInitializeScavenger
  1180. /***************************************************************************++
  1181. Routine Description:
  1182. Increments the current chunk pointer in the tracker and initializes
  1183. some of the "from file" related tracker fields if necessary.
  1184. Arguments:
  1185. pTracker - Supplies the UL_READ_TRACKER to manipulate.
  1186. --***************************************************************************/
  1187. VOID
  1188. UlpTerminateScavenger(
  1189. VOID
  1190. )
  1191. {
  1192. KIRQL oldIrql;
  1193. UlTrace(URI_CACHE, (
  1194. "Http!UlpTerminateScavenger\n"
  1195. ));
  1196. if (g_UriScavengerInitialized)
  1197. {
  1198. //
  1199. // Clear the "initialized" flag. If the scavenger runs soon,
  1200. // it will see this flag, set the termination event, and exit
  1201. // quickly.
  1202. //
  1203. UlAcquireSpinLock(
  1204. &g_UriScavengerSpinLock,
  1205. &oldIrql
  1206. );
  1207. g_UriScavengerInitialized = FALSE;
  1208. UlReleaseSpinLock(
  1209. &g_UriScavengerSpinLock,
  1210. oldIrql
  1211. );
  1212. //
  1213. // Cancel the scavenger timer. If the cancel fails, then the
  1214. // scavenger is either running or scheduled to run soon. In
  1215. // either case, wait for it to terminate.
  1216. //
  1217. if ( !KeCancelTimer( &g_UriScavengerTimer ) )
  1218. {
  1219. KeWaitForSingleObject(
  1220. (PVOID)&g_UriScavengerTerminationEvent,
  1221. UserRequest,
  1222. KernelMode,
  1223. FALSE,
  1224. NULL
  1225. );
  1226. }
  1227. }
  1228. //
  1229. // clear out anything remaining
  1230. //
  1231. UlpClearZombieList();
  1232. //
  1233. // The EndpointDrain should have closed all connections and released
  1234. // all references to cache entries
  1235. //
  1236. ASSERT( g_UriCacheStats.ZombieCount == 0 );
  1237. ASSERT( IsListEmpty(&g_ZombieListHead) );
  1238. } // UlpTerminateScavenger
  1239. /***************************************************************************++
  1240. Routine Description:
  1241. Figures out the scavenger interval in 100 ns ticks, and sets the timer.
  1242. --***************************************************************************/
  1243. VOID
  1244. UlpSetScavengerTimer(
  1245. VOID
  1246. )
  1247. {
  1248. LARGE_INTEGER ScavengerInterval;
  1249. //
  1250. // convert seconds to 100 nanosecond intervals (x * 10^7)
  1251. // negative numbers mean relative time
  1252. //
  1253. ScavengerInterval.QuadPart= g_UriCacheConfig.ScavengerPeriod
  1254. * -C_NS_TICKS_PER_SEC;
  1255. UlTrace(URI_CACHE, (
  1256. "Http!UlpSetScavengerTimer: %d seconds = %I64d 100ns ticks\n",
  1257. g_UriCacheConfig.ScavengerPeriod,
  1258. ScavengerInterval.QuadPart
  1259. ));
  1260. KeSetTimer(
  1261. &g_UriScavengerTimer,
  1262. ScavengerInterval,
  1263. &g_UriScavengerDpc
  1264. );
  1265. } // UlpSetScavengerTimer
  1266. /***************************************************************************++
  1267. Routine Description:
  1268. Executes every UriScavengerPeriodSeconds, or when our timer gets
  1269. cancelled (on shutdown). If we're not shutting down, we run the
  1270. UlpScavenger (at passive level).
  1271. Arguments:
  1272. I ignore all of these.
  1273. --***************************************************************************/
  1274. VOID
  1275. UlpScavengerDpcRoutine(
  1276. IN PKDPC Dpc,
  1277. IN PVOID DeferredContext,
  1278. IN PVOID SystemArgument1,
  1279. IN PVOID SystemArgument2
  1280. )
  1281. {
  1282. // UlTrace(URI_CACHE, (
  1283. // "Http!UlpScavengerDpcRoutine(...)\n"
  1284. // ));
  1285. UlAcquireSpinLockAtDpcLevel(
  1286. &g_UriScavengerSpinLock
  1287. );
  1288. if( !g_UriScavengerInitialized ) {
  1289. //
  1290. // We're shutting down, so signal the termination event.
  1291. //
  1292. KeSetEvent(
  1293. &g_UriScavengerTerminationEvent,
  1294. 0,
  1295. FALSE
  1296. );
  1297. } else {
  1298. //
  1299. // Do that scavenger thang.
  1300. //
  1301. if (FALSE == InterlockedExchange(&g_UriScavengerRunning, TRUE)) {
  1302. UL_QUEUE_WORK_ITEM(
  1303. &g_UriScavengerWorkItem,
  1304. &UlpScavenger
  1305. );
  1306. }
  1307. }
  1308. UlReleaseSpinLockFromDpcLevel(
  1309. &g_UriScavengerSpinLock
  1310. );
  1311. } // UlpScavengerDpcRoutine
  1312. /***************************************************************************++
  1313. Routine Description:
  1314. Looks through the cache for expired entries to put on the zombie list,
  1315. and then empties out the list.
  1316. Arguments:
  1317. pWorkItem - ignored
  1318. --***************************************************************************/
  1319. VOID
  1320. UlpScavenger(
  1321. IN PUL_WORK_ITEM pWorkItem
  1322. )
  1323. {
  1324. KIRQL oldIrql;
  1325. UlTrace(URI_CACHE, (
  1326. "Http!UlpScavenger()\n"
  1327. ));
  1328. ASSERT( TRUE == g_UriScavengerRunning );
  1329. ASSERT( &g_UriScavengerWorkItem == pWorkItem );
  1330. if (g_UriScavengerInitialized)
  1331. {
  1332. UlpFilteredFlushUriCache(UlpFlushFilterScavenger, NULL);
  1333. //
  1334. // allow other instances of the scavenger to run
  1335. //
  1336. InterlockedExchange(&g_UriScavengerRunning, FALSE);
  1337. }
  1338. UlAcquireSpinLock(&g_UriScavengerSpinLock, &oldIrql);
  1339. if (g_UriScavengerInitialized)
  1340. {
  1341. //
  1342. // restart the timer
  1343. //
  1344. UlpSetScavengerTimer();
  1345. }
  1346. else
  1347. {
  1348. KeSetEvent(
  1349. &g_UriScavengerTerminationEvent,
  1350. 0,
  1351. FALSE
  1352. );
  1353. }
  1354. UlReleaseSpinLock(&g_UriScavengerSpinLock, oldIrql);
  1355. } // UlpScavenger
  1356. /***************************************************************************++
  1357. Routine Description:
  1358. A filter for UlpScavenger. Called by UlpFilteredFlushUriCache.
  1359. Arguments:
  1360. pUriCacheEntry - the entry to check
  1361. pContext - ignored
  1362. --***************************************************************************/
  1363. UL_CACHE_PREDICATE
  1364. UlpFlushFilterScavenger(
  1365. IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
  1366. IN PVOID pContext
  1367. )
  1368. {
  1369. PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
  1370. //
  1371. // Sanity check
  1372. //
  1373. PAGED_CODE();
  1374. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  1375. ASSERT( pUriFilterContext != NULL
  1376. && URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature
  1377. && pUriFilterContext->pCallerContext == NULL );
  1378. pUriCacheEntry->ScavengerTicks++;
  1379. //
  1380. // CODEWORK: need to check for expiration time as well
  1381. //
  1382. return UlpZombifyEntry(
  1383. (pUriCacheEntry->ScavengerTicks > CACHE_ENTRY_AGE_THRESHOLD),
  1384. pUriCacheEntry,
  1385. pUriFilterContext
  1386. );
  1387. } // UlpFlushFilterScavenger
  1388. /***************************************************************************++
  1389. Routine Description:
  1390. Determine if the Translate header is present AND has a value of 'F' or 'f'.
  1391. Arguments:
  1392. pRequest - Supplies the request to query.
  1393. Return Value:
  1394. BOOLEAN - TRUE if "Translate: F", FALSE otherwise
  1395. --***************************************************************************/
  1396. BOOLEAN
  1397. UlpQueryTranslateHeader(
  1398. IN PUL_INTERNAL_REQUEST pRequest
  1399. )
  1400. {
  1401. BOOLEAN ret = FALSE;
  1402. if ( pRequest->HeaderValid[HttpHeaderTranslate] )
  1403. {
  1404. PUCHAR pValue = pRequest->Headers[HttpHeaderTranslate].pHeader;
  1405. ASSERT(NULL != pValue);
  1406. if ('f' == pValue[0] || 'F' == pValue[0])
  1407. {
  1408. ret = TRUE;
  1409. }
  1410. }
  1411. return ret;
  1412. } // UlpQueryTranslateHeader
  1413. /***************************************************************************++
  1414. Routine Description:
  1415. Add a reference on a cache entry
  1416. Arguments:
  1417. pUriCacheEntry - the entry to addref
  1418. --***************************************************************************/
  1419. LONG
  1420. UlAddRefUriCacheEntry(
  1421. IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
  1422. IN REFTRACE_ACTION Action
  1423. REFERENCE_DEBUG_FORMAL_PARAMS
  1424. )
  1425. {
  1426. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  1427. LONG RefCount = InterlockedIncrement(&pUriCacheEntry->ReferenceCount);
  1428. WRITE_REF_TRACE_LOG(
  1429. g_UriTraceLog,
  1430. Action,
  1431. RefCount,
  1432. pUriCacheEntry,
  1433. pFileName,
  1434. LineNumber
  1435. );
  1436. UlTrace(URI_CACHE, (
  1437. "Http!UlAddRefUriCacheEntry: Entry %p, refcount=%d.\n",
  1438. pUriCacheEntry, RefCount
  1439. ));
  1440. ASSERT(RefCount > 0);
  1441. return RefCount;
  1442. } // UlAddRefUriCacheEntry
  1443. /***************************************************************************++
  1444. Routine Description:
  1445. Release a reference on a cache entry
  1446. Arguments:
  1447. pUriCacheEntry - the entry to release
  1448. --***************************************************************************/
  1449. LONG
  1450. UlReleaseUriCacheEntry(
  1451. IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
  1452. IN REFTRACE_ACTION Action
  1453. REFERENCE_DEBUG_FORMAL_PARAMS
  1454. )
  1455. {
  1456. ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
  1457. LONG RefCount = InterlockedDecrement(&pUriCacheEntry->ReferenceCount);
  1458. WRITE_REF_TRACE_LOG(
  1459. g_UriTraceLog,
  1460. Action,
  1461. RefCount,
  1462. pUriCacheEntry,
  1463. pFileName,
  1464. LineNumber
  1465. );
  1466. UlTrace(URI_CACHE, (
  1467. "Http!UlReleaseUriCacheEntry: (pUriCacheEntry %p '%ls')"
  1468. "refcount = %d\n",
  1469. pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
  1470. RefCount
  1471. ));
  1472. ASSERT(RefCount >= 0);
  1473. if (RefCount == 0)
  1474. {
  1475. if (pUriCacheEntry->Cached)
  1476. UlpRemoveEntryStats(pUriCacheEntry);
  1477. UlpDestroyUriCacheEntry(pUriCacheEntry);
  1478. }
  1479. return RefCount;
  1480. } // UlReleaseUriCacheEntry
  1481. /***************************************************************************++
  1482. Routine Description:
  1483. UL_URI_CACHE_ENTRY pseudo-constructor. Primarily used for
  1484. AddRef and tracelogging.
  1485. Arguments:
  1486. pUriCacheEntry - the entry to initialize
  1487. Hash - Hash code of pUrl
  1488. Length - Length (in bytes) of pUrl
  1489. pUrl - Unicode URL to copy
  1490. --***************************************************************************/
  1491. VOID
  1492. UlInitCacheEntry(
  1493. PUL_URI_CACHE_ENTRY pUriCacheEntry,
  1494. ULONG Hash,
  1495. ULONG Length,
  1496. PCWSTR pUrl
  1497. )
  1498. {
  1499. pUriCacheEntry->Signature = UL_URI_CACHE_ENTRY_POOL_TAG;
  1500. pUriCacheEntry->ReferenceCount = 0;
  1501. pUriCacheEntry->HitCount = 1;
  1502. pUriCacheEntry->Zombie = FALSE;
  1503. pUriCacheEntry->ZombieAddReffed = FALSE;
  1504. pUriCacheEntry->ZombieListEntry.Flink = NULL;
  1505. pUriCacheEntry->ZombieListEntry.Blink = NULL;
  1506. pUriCacheEntry->Cached = FALSE;
  1507. pUriCacheEntry->ScavengerTicks = 0;
  1508. pUriCacheEntry->UriKey.Hash = Hash;
  1509. pUriCacheEntry->UriKey.Length = Length;
  1510. pUriCacheEntry->UriKey.pUri = (PWSTR) ((PCHAR)pUriCacheEntry +
  1511. ALIGN_UP(sizeof(UL_URI_CACHE_ENTRY), PVOID));
  1512. RtlCopyMemory(
  1513. pUriCacheEntry->UriKey.pUri,
  1514. pUrl,
  1515. pUriCacheEntry->UriKey.Length + sizeof(WCHAR)
  1516. );
  1517. REFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, CREATE);
  1518. UlTrace(URI_CACHE, (
  1519. "Http!UlInitCacheEntry (%p = '%ls')\n",
  1520. pUriCacheEntry, pUriCacheEntry->UriKey.pUri
  1521. ));
  1522. } // UlInitCacheEntry