nsMemoryCacheDevice.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "nsCache.h"
  6. #include "nsMemoryCacheDevice.h"
  7. #include "nsCacheService.h"
  8. #include "nsICacheService.h"
  9. #include "nsICacheVisitor.h"
  10. #include "nsIStorageStream.h"
  11. #include "nsCRT.h"
  12. #include "nsReadableUtils.h"
  13. #include "mozilla/MathAlgorithms.h"
  14. #include "mozilla/Telemetry.h"
  15. #include <algorithm>
  16. // The memory cache implements the "LRU-SP" caching algorithm
  17. // described in "LRU-SP: A Size-Adjusted and Popularity-Aware LRU Replacement
  18. // Algorithm for Web Caching" by Kai Cheng and Yahiko Kambayashi.
  19. // We keep kQueueCount LRU queues, which should be about ceil(log2(mHardLimit))
  20. // The queues hold exponentially increasing ranges of floor(log2((size/nref)))
  21. // values for entries.
  22. // Entries larger than 2^(kQueueCount-1) go in the last queue.
  23. // Entries with no expiration go in the first queue.
  24. const char *gMemoryDeviceID = "memory";
  25. using namespace mozilla;
  26. nsMemoryCacheDevice::nsMemoryCacheDevice()
  27. : mInitialized(false),
  28. mHardLimit(4 * 1024 * 1024), // default, if no pref
  29. mSoftLimit((mHardLimit * 9) / 10), // default, if no pref
  30. mTotalSize(0),
  31. mInactiveSize(0),
  32. mEntryCount(0),
  33. mMaxEntryCount(0),
  34. mMaxEntrySize(-1) // -1 means "no limit"
  35. {
  36. for (int i=0; i<kQueueCount; ++i)
  37. PR_INIT_CLIST(&mEvictionList[i]);
  38. }
  39. nsMemoryCacheDevice::~nsMemoryCacheDevice()
  40. {
  41. Shutdown();
  42. }
  43. nsresult
  44. nsMemoryCacheDevice::Init()
  45. {
  46. if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
  47. mMemCacheEntries.Init();
  48. mInitialized = true;
  49. return NS_OK;
  50. }
  51. nsresult
  52. nsMemoryCacheDevice::Shutdown()
  53. {
  54. NS_ASSERTION(mInitialized, "### attempting shutdown while not initialized");
  55. NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
  56. mMemCacheEntries.Shutdown();
  57. // evict all entries
  58. nsCacheEntry * entry, * next;
  59. for (int i = kQueueCount - 1; i >= 0; --i) {
  60. entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
  61. while (entry != &mEvictionList[i]) {
  62. NS_ASSERTION(!entry->IsInUse(), "### shutting down with active entries");
  63. next = (nsCacheEntry *)PR_NEXT_LINK(entry);
  64. PR_REMOVE_AND_INIT_LINK(entry);
  65. // update statistics
  66. int32_t memoryRecovered = (int32_t)entry->DataSize();
  67. mTotalSize -= memoryRecovered;
  68. mInactiveSize -= memoryRecovered;
  69. --mEntryCount;
  70. delete entry;
  71. entry = next;
  72. }
  73. }
  74. /*
  75. * we're not factoring in changes to meta data yet...
  76. * NS_ASSERTION(mTotalSize == 0, "### mem cache leaking entries?");
  77. */
  78. NS_ASSERTION(mInactiveSize == 0, "### mem cache leaking entries?");
  79. NS_ASSERTION(mEntryCount == 0, "### mem cache leaking entries?");
  80. mInitialized = false;
  81. return NS_OK;
  82. }
  83. const char *
  84. nsMemoryCacheDevice::GetDeviceID()
  85. {
  86. return gMemoryDeviceID;
  87. }
  88. nsCacheEntry *
  89. nsMemoryCacheDevice::FindEntry(nsCString * key, bool *collision)
  90. {
  91. nsCacheEntry * entry = mMemCacheEntries.GetEntry(key);
  92. if (!entry) return nullptr;
  93. // move entry to the tail of an eviction list
  94. PR_REMOVE_AND_INIT_LINK(entry);
  95. PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
  96. mInactiveSize -= entry->DataSize();
  97. return entry;
  98. }
  99. nsresult
  100. nsMemoryCacheDevice::DeactivateEntry(nsCacheEntry * entry)
  101. {
  102. CACHE_LOG_DEBUG(("nsMemoryCacheDevice::DeactivateEntry for entry 0x%p\n",
  103. entry));
  104. if (entry->IsDoomed()) {
  105. #ifdef DEBUG
  106. // XXX verify we've removed it from mMemCacheEntries & eviction list
  107. #endif
  108. delete entry;
  109. CACHE_LOG_DEBUG(("deleted doomed entry 0x%p\n", entry));
  110. return NS_OK;
  111. }
  112. #ifdef DEBUG
  113. nsCacheEntry * ourEntry = mMemCacheEntries.GetEntry(entry->Key());
  114. NS_ASSERTION(ourEntry, "DeactivateEntry called for an entry we don't have!");
  115. NS_ASSERTION(entry == ourEntry, "entry doesn't match ourEntry");
  116. if (ourEntry != entry)
  117. return NS_ERROR_INVALID_POINTER;
  118. #endif
  119. mInactiveSize += entry->DataSize();
  120. EvictEntriesIfNecessary();
  121. return NS_OK;
  122. }
  123. nsresult
  124. nsMemoryCacheDevice::BindEntry(nsCacheEntry * entry)
  125. {
  126. if (!entry->IsDoomed()) {
  127. NS_ASSERTION(PR_CLIST_IS_EMPTY(entry),"entry is already on a list!");
  128. // append entry to the eviction list
  129. PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
  130. // add entry to hashtable of mem cache entries
  131. nsresult rv = mMemCacheEntries.AddEntry(entry);
  132. if (NS_FAILED(rv)) {
  133. PR_REMOVE_AND_INIT_LINK(entry);
  134. return rv;
  135. }
  136. // add size of entry to memory totals
  137. ++mEntryCount;
  138. if (mMaxEntryCount < mEntryCount) mMaxEntryCount = mEntryCount;
  139. mTotalSize += entry->DataSize();
  140. EvictEntriesIfNecessary();
  141. }
  142. return NS_OK;
  143. }
  144. void
  145. nsMemoryCacheDevice::DoomEntry(nsCacheEntry * entry)
  146. {
  147. #ifdef DEBUG
  148. // debug code to verify we have entry
  149. nsCacheEntry * hashEntry = mMemCacheEntries.GetEntry(entry->Key());
  150. if (!hashEntry) NS_WARNING("no entry for key");
  151. else if (entry != hashEntry) NS_WARNING("entry != hashEntry");
  152. #endif
  153. CACHE_LOG_DEBUG(("Dooming entry 0x%p in memory cache\n", entry));
  154. EvictEntry(entry, DO_NOT_DELETE_ENTRY);
  155. }
  156. nsresult
  157. nsMemoryCacheDevice::OpenInputStreamForEntry( nsCacheEntry * entry,
  158. nsCacheAccessMode mode,
  159. uint32_t offset,
  160. nsIInputStream ** result)
  161. {
  162. NS_ENSURE_ARG_POINTER(entry);
  163. NS_ENSURE_ARG_POINTER(result);
  164. nsCOMPtr<nsIStorageStream> storage;
  165. nsresult rv;
  166. nsISupports *data = entry->Data();
  167. if (data) {
  168. storage = do_QueryInterface(data, &rv);
  169. if (NS_FAILED(rv))
  170. return rv;
  171. }
  172. else {
  173. rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage));
  174. if (NS_FAILED(rv))
  175. return rv;
  176. entry->SetData(storage);
  177. }
  178. return storage->NewInputStream(offset, result);
  179. }
  180. nsresult
  181. nsMemoryCacheDevice::OpenOutputStreamForEntry( nsCacheEntry * entry,
  182. nsCacheAccessMode mode,
  183. uint32_t offset,
  184. nsIOutputStream ** result)
  185. {
  186. NS_ENSURE_ARG_POINTER(entry);
  187. NS_ENSURE_ARG_POINTER(result);
  188. nsCOMPtr<nsIStorageStream> storage;
  189. nsresult rv;
  190. nsISupports *data = entry->Data();
  191. if (data) {
  192. storage = do_QueryInterface(data, &rv);
  193. if (NS_FAILED(rv))
  194. return rv;
  195. }
  196. else {
  197. rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage));
  198. if (NS_FAILED(rv))
  199. return rv;
  200. entry->SetData(storage);
  201. }
  202. return storage->GetOutputStream(offset, result);
  203. }
  204. nsresult
  205. nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry * entry,
  206. nsIFile ** result )
  207. {
  208. return NS_ERROR_NOT_IMPLEMENTED;
  209. }
  210. bool
  211. nsMemoryCacheDevice::EntryIsTooBig(int64_t entrySize)
  212. {
  213. CACHE_LOG_DEBUG(("nsMemoryCacheDevice::EntryIsTooBig "
  214. "[size=%d max=%d soft=%d]\n",
  215. entrySize, mMaxEntrySize, mSoftLimit));
  216. if (mMaxEntrySize == -1)
  217. return entrySize > mSoftLimit;
  218. else
  219. return (entrySize > mSoftLimit || entrySize > mMaxEntrySize);
  220. }
  221. size_t
  222. nsMemoryCacheDevice::TotalSize()
  223. {
  224. return mTotalSize;
  225. }
  226. nsresult
  227. nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, int32_t deltaSize)
  228. {
  229. if (entry->IsStreamData()) {
  230. // we have the right to refuse or pre-evict
  231. uint32_t newSize = entry->DataSize() + deltaSize;
  232. if (EntryIsTooBig(newSize)) {
  233. #ifdef DEBUG
  234. nsresult rv =
  235. #endif
  236. nsCacheService::DoomEntry(entry);
  237. NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
  238. return NS_ERROR_ABORT;
  239. }
  240. }
  241. // adjust our totals
  242. mTotalSize += deltaSize;
  243. if (!entry->IsDoomed()) {
  244. // move entry to the tail of the appropriate eviction list
  245. PR_REMOVE_AND_INIT_LINK(entry);
  246. PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, deltaSize)]);
  247. }
  248. EvictEntriesIfNecessary();
  249. return NS_OK;
  250. }
  251. void
  252. nsMemoryCacheDevice::AdjustMemoryLimits(int32_t softLimit, int32_t hardLimit)
  253. {
  254. mSoftLimit = softLimit;
  255. mHardLimit = hardLimit;
  256. // First, evict entries that won't fit into the new cache size.
  257. EvictEntriesIfNecessary();
  258. }
  259. void
  260. nsMemoryCacheDevice::EvictEntry(nsCacheEntry * entry, bool deleteEntry)
  261. {
  262. CACHE_LOG_DEBUG(("Evicting entry 0x%p from memory cache, deleting: %d\n",
  263. entry, deleteEntry));
  264. // remove entry from our hashtable
  265. mMemCacheEntries.RemoveEntry(entry);
  266. // remove entry from the eviction list
  267. PR_REMOVE_AND_INIT_LINK(entry);
  268. // update statistics
  269. int32_t memoryRecovered = (int32_t)entry->DataSize();
  270. mTotalSize -= memoryRecovered;
  271. if (!entry->IsDoomed())
  272. mInactiveSize -= memoryRecovered;
  273. --mEntryCount;
  274. if (deleteEntry) delete entry;
  275. }
  276. void
  277. nsMemoryCacheDevice::EvictEntriesIfNecessary(void)
  278. {
  279. nsCacheEntry * entry;
  280. nsCacheEntry * maxEntry;
  281. CACHE_LOG_DEBUG(("EvictEntriesIfNecessary. mTotalSize: %d, mHardLimit: %d,"
  282. "mInactiveSize: %d, mSoftLimit: %d\n",
  283. mTotalSize, mHardLimit, mInactiveSize, mSoftLimit));
  284. if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit))
  285. return;
  286. uint32_t now = SecondsFromPRTime(PR_Now());
  287. uint64_t entryCost = 0;
  288. uint64_t maxCost = 0;
  289. do {
  290. // LRU-SP eviction selection: Check the head of each segment (each
  291. // eviction list, kept in LRU order) and select the maximal-cost
  292. // entry for eviction. Cost is time-since-accessed * size / nref.
  293. maxEntry = 0;
  294. for (int i = kQueueCount - 1; i >= 0; --i) {
  295. entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
  296. // If the head of a list is in use, check the next available entry
  297. while ((entry != &mEvictionList[i]) &&
  298. (entry->IsInUse())) {
  299. entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
  300. }
  301. if (entry != &mEvictionList[i]) {
  302. entryCost = (uint64_t)
  303. (now - entry->LastFetched()) * entry->DataSize() /
  304. std::max(1, entry->FetchCount());
  305. if (!maxEntry || (entryCost > maxCost)) {
  306. maxEntry = entry;
  307. maxCost = entryCost;
  308. }
  309. }
  310. }
  311. if (maxEntry) {
  312. EvictEntry(maxEntry, DELETE_ENTRY);
  313. } else {
  314. break;
  315. }
  316. }
  317. while ((mTotalSize >= mHardLimit) || (mInactiveSize >= mSoftLimit));
  318. }
  319. int
  320. nsMemoryCacheDevice::EvictionList(nsCacheEntry * entry, int32_t deltaSize)
  321. {
  322. // favor items which never expire by putting them in the lowest-index queue
  323. if (entry->ExpirationTime() == nsICache::NO_EXPIRATION_TIME)
  324. return 0;
  325. // compute which eviction queue this entry should go into,
  326. // based on floor(log2(size/nref))
  327. int32_t size = deltaSize + (int32_t)entry->DataSize();
  328. int32_t fetchCount = std::max(1, entry->FetchCount());
  329. return std::min((int)mozilla::FloorLog2(size / fetchCount), kQueueCount - 1);
  330. }
  331. nsresult
  332. nsMemoryCacheDevice::Visit(nsICacheVisitor * visitor)
  333. {
  334. nsMemoryCacheDeviceInfo * deviceInfo = new nsMemoryCacheDeviceInfo(this);
  335. nsCOMPtr<nsICacheDeviceInfo> deviceRef(deviceInfo);
  336. if (!deviceInfo) return NS_ERROR_OUT_OF_MEMORY;
  337. bool keepGoing;
  338. nsresult rv = visitor->VisitDevice(gMemoryDeviceID, deviceInfo, &keepGoing);
  339. if (NS_FAILED(rv)) return rv;
  340. if (!keepGoing)
  341. return NS_OK;
  342. nsCacheEntry * entry;
  343. nsCOMPtr<nsICacheEntryInfo> entryRef;
  344. for (int i = kQueueCount - 1; i >= 0; --i) {
  345. entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
  346. while (entry != &mEvictionList[i]) {
  347. nsCacheEntryInfo * entryInfo = new nsCacheEntryInfo(entry);
  348. if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY;
  349. entryRef = entryInfo;
  350. rv = visitor->VisitEntry(gMemoryDeviceID, entryInfo, &keepGoing);
  351. entryInfo->DetachEntry();
  352. if (NS_FAILED(rv)) return rv;
  353. if (!keepGoing) break;
  354. entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
  355. }
  356. }
  357. return NS_OK;
  358. }
  359. static bool
  360. IsEntryPrivate(nsCacheEntry* entry, void* args)
  361. {
  362. return entry->IsPrivate();
  363. }
  364. struct ClientIDArgs {
  365. const char* clientID;
  366. uint32_t prefixLength;
  367. };
  368. static bool
  369. EntryMatchesClientID(nsCacheEntry* entry, void* args)
  370. {
  371. const char * clientID = static_cast<ClientIDArgs*>(args)->clientID;
  372. uint32_t prefixLength = static_cast<ClientIDArgs*>(args)->prefixLength;
  373. const char * key = entry->Key()->get();
  374. return !clientID || nsCRT::strncmp(clientID, key, prefixLength) == 0;
  375. }
  376. nsresult
  377. nsMemoryCacheDevice::DoEvictEntries(bool (*matchFn)(nsCacheEntry* entry, void* args), void* args)
  378. {
  379. nsCacheEntry * entry;
  380. for (int i = kQueueCount - 1; i >= 0; --i) {
  381. PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
  382. while (elem != &mEvictionList[i]) {
  383. entry = (nsCacheEntry *)elem;
  384. elem = PR_NEXT_LINK(elem);
  385. if (!matchFn(entry, args))
  386. continue;
  387. if (entry->IsInUse()) {
  388. nsresult rv = nsCacheService::DoomEntry(entry);
  389. if (NS_FAILED(rv)) {
  390. CACHE_LOG_WARNING(("memCache->DoEvictEntries() aborted: rv =%x", rv));
  391. return rv;
  392. }
  393. } else {
  394. EvictEntry(entry, DELETE_ENTRY);
  395. }
  396. }
  397. }
  398. return NS_OK;
  399. }
  400. nsresult
  401. nsMemoryCacheDevice::EvictEntries(const char * clientID)
  402. {
  403. ClientIDArgs args = {clientID, clientID ? uint32_t(strlen(clientID)) : 0};
  404. return DoEvictEntries(&EntryMatchesClientID, &args);
  405. }
  406. nsresult
  407. nsMemoryCacheDevice::EvictPrivateEntries()
  408. {
  409. return DoEvictEntries(&IsEntryPrivate, nullptr);
  410. }
  411. // WARNING: SetCapacity can get called before Init()
  412. void
  413. nsMemoryCacheDevice::SetCapacity(int32_t capacity)
  414. {
  415. int32_t hardLimit = capacity * 1024; // convert k into bytes
  416. int32_t softLimit = (hardLimit * 9) / 10;
  417. AdjustMemoryLimits(softLimit, hardLimit);
  418. }
  419. void
  420. nsMemoryCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
  421. {
  422. // Internal unit is bytes. Changing this only takes effect *after* the
  423. // change and has no consequences for existing cache-entries
  424. if (maxSizeInKilobytes >= 0)
  425. mMaxEntrySize = maxSizeInKilobytes * 1024;
  426. else
  427. mMaxEntrySize = -1;
  428. }
  429. #ifdef DEBUG
  430. void
  431. nsMemoryCacheDevice::CheckEntryCount()
  432. {
  433. if (!mInitialized) return;
  434. int32_t evictionListCount = 0;
  435. for (int i=0; i<kQueueCount; ++i) {
  436. PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
  437. while (elem != &mEvictionList[i]) {
  438. elem = PR_NEXT_LINK(elem);
  439. ++evictionListCount;
  440. }
  441. }
  442. NS_ASSERTION(mEntryCount == evictionListCount, "### mem cache badness");
  443. int32_t entryCount = 0;
  444. for (auto iter = mMemCacheEntries.Iter(); !iter.Done(); iter.Next()) {
  445. ++entryCount;
  446. }
  447. NS_ASSERTION(mEntryCount == entryCount, "### mem cache badness");
  448. }
  449. #endif
  450. /******************************************************************************
  451. * nsMemoryCacheDeviceInfo - for implementing about:cache
  452. *****************************************************************************/
  453. NS_IMPL_ISUPPORTS(nsMemoryCacheDeviceInfo, nsICacheDeviceInfo)
  454. NS_IMETHODIMP
  455. nsMemoryCacheDeviceInfo::GetDescription(char ** result)
  456. {
  457. NS_ENSURE_ARG_POINTER(result);
  458. *result = NS_strdup("Memory cache device");
  459. if (!*result) return NS_ERROR_OUT_OF_MEMORY;
  460. return NS_OK;
  461. }
  462. NS_IMETHODIMP
  463. nsMemoryCacheDeviceInfo::GetUsageReport(char ** result)
  464. {
  465. NS_ENSURE_ARG_POINTER(result);
  466. nsCString buffer;
  467. buffer.AssignLiteral(" <tr>\n"
  468. " <th>Inactive storage:</th>\n"
  469. " <td>");
  470. buffer.AppendInt(mDevice->mInactiveSize / 1024);
  471. buffer.AppendLiteral(" KiB</td>\n"
  472. " </tr>\n");
  473. *result = ToNewCString(buffer);
  474. if (!*result) return NS_ERROR_OUT_OF_MEMORY;
  475. return NS_OK;
  476. }
  477. NS_IMETHODIMP
  478. nsMemoryCacheDeviceInfo::GetEntryCount(uint32_t * result)
  479. {
  480. NS_ENSURE_ARG_POINTER(result);
  481. // XXX compare calculated count vs. mEntryCount
  482. *result = (uint32_t)mDevice->mEntryCount;
  483. return NS_OK;
  484. }
  485. NS_IMETHODIMP
  486. nsMemoryCacheDeviceInfo::GetTotalSize(uint32_t * result)
  487. {
  488. NS_ENSURE_ARG_POINTER(result);
  489. *result = (uint32_t)mDevice->mTotalSize;
  490. return NS_OK;
  491. }
  492. NS_IMETHODIMP
  493. nsMemoryCacheDeviceInfo::GetMaximumSize(uint32_t * result)
  494. {
  495. NS_ENSURE_ARG_POINTER(result);
  496. *result = (uint32_t)mDevice->mHardLimit;
  497. return NS_OK;
  498. }