tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_cache2-30c-pinning-deferred-doom.js (6143B)


      1 /*
      2 
      3 This is a complex test checking the internal "deferred doom" functionality in both CacheEntry and CacheFileHandle.
      4 
      5 - We create a batch of 10 non-pinned and 10 pinned entries, write something to them.
      6 - Then we purge them from memory, so they have to reload from disk.
      7 - After that the IO thread is suspended not to process events on the READ (3) level.  This forces opening operation and eviction
      8  sync operations happen before we know actual pinning status of already cached entries.
      9 - We async-open the same batch of the 10+10 entries again, all should open as existing with the expected, previously stored
     10  content
     11 - After all these entries are made to open, we clear the cache.  This does some synchronous operations on the entries
     12  being open and also on the handles being in an already open state (but before the entry metadata has started to be read.)
     13  Expected is to leave the pinned entries only.
     14 - Now, we resume the IO thread, so it start reading.  One could say this is a hack, but this can very well happen in reality
     15  on slow disk or when a large number of entries is about to be open at once.  Suspending the IO thread is just doing this
     16  simulation is a fully deterministic way and actually very easily and elegantly.
     17 - After the resume we want to open all those 10+10 entries once again (no purgin involved this time.).  It is expected
     18  to open all the pinning entries intact and loose all the non-pinned entries (get them as new and empty again.)
     19 
     20 */
     21 
     22 "use strict";
     23 
     24 const kENTRYCOUNT = 10;
     25 
     26 function log_(msg) {
     27  if (true) {
     28    dump(">>>>>>>>>>>>> " + msg + "\n");
     29  }
     30 }
     31 
     32 function run_test() {
     33  do_get_profile();
     34 
     35  var lci = Services.loadContextInfo.default;
     36  var testingInterface = Services.cache2.QueryInterface(Ci.nsICacheTesting);
     37  Assert.ok(testingInterface);
     38 
     39  var mc = new MultipleCallbacks(
     40    1,
     41    function () {
     42      // (2)
     43 
     44      mc = new MultipleCallbacks(1, finish_cache2_test);
     45      // Release all references to cache entries so that they can be purged
     46      // Calling gc() four times is needed to force it to actually release
     47      // entries that are obviously unreferenced.  Yeah, I know, this is wacky...
     48      gc();
     49      gc();
     50      executeSoon(() => {
     51        gc();
     52        gc();
     53        log_("purging");
     54 
     55        // Invokes cacheservice:purge-memory-pools when done.
     56        Services.cache2.purgeFromMemory(
     57          Ci.nsICacheStorageService.PURGE_EVERYTHING
     58        ); // goes to (3)
     59      });
     60    },
     61    true
     62  );
     63 
     64  // (1), here we start
     65 
     66  var i;
     67  for (i = 0; i < kENTRYCOUNT; ++i) {
     68    log_("first set of opens");
     69 
     70    // Callbacks 1-20
     71    mc.add();
     72    asyncOpenCacheEntry(
     73      "http://pinned" + i + "/",
     74      "pin",
     75      Ci.nsICacheStorage.OPEN_TRUNCATE,
     76      lci,
     77      new OpenCallback(NEW | WAITFORWRITE, "m" + i, "p" + i, function () {
     78        mc.fired();
     79      })
     80    );
     81 
     82    mc.add();
     83    asyncOpenCacheEntry(
     84      "http://common" + i + "/",
     85      "disk",
     86      Ci.nsICacheStorage.OPEN_TRUNCATE,
     87      lci,
     88      new OpenCallback(NEW | WAITFORWRITE, "m" + i, "d" + i, function () {
     89        mc.fired();
     90      })
     91    );
     92  }
     93 
     94  mc.fired(); // Goes to (2)
     95 
     96  Services.obs.addObserver(
     97    {
     98      observe() {
     99        // (3)
    100 
    101        log_("after purge, second set of opens");
    102        // Prevent the I/O thread from reading the data.  We first want to schedule clear of the cache.
    103        // This deterministically emulates a slow hard drive.
    104        testingInterface.suspendCacheIOThread(3);
    105 
    106        // All entries should load
    107        // Callbacks 21-40
    108        for (i = 0; i < kENTRYCOUNT; ++i) {
    109          mc.add();
    110          asyncOpenCacheEntry(
    111            "http://pinned" + i + "/",
    112            "disk",
    113            Ci.nsICacheStorage.OPEN_NORMALLY,
    114            lci,
    115            new OpenCallback(NORMAL, "m" + i, "p" + i, function () {
    116              mc.fired();
    117            })
    118          );
    119 
    120          // Unfortunately we cannot ensure that entries existing in the cache will be delivered to the consumer
    121          // when soon after are evicted by some cache API call.  It's better to not ensure getting an entry
    122          // than allowing to get an entry that was just evicted from the cache.  Entries may be delievered
    123          // as new, but are already doomed.  Output stream cannot be openned, or the file handle is already
    124          // writing to a doomed file.
    125          //
    126          // The API now just ensures that entries removed by any of the cache eviction APIs are never more
    127          // available to consumers.
    128          mc.add();
    129          asyncOpenCacheEntry(
    130            "http://common" + i + "/",
    131            "disk",
    132            Ci.nsICacheStorage.OPEN_NORMALLY,
    133            lci,
    134            new OpenCallback(MAYBE_NEW | DOOMED, "m" + i, "d" + i, function () {
    135              mc.fired();
    136            })
    137          );
    138        }
    139 
    140        log_("clearing");
    141        // Now clear everything except pinned, all entries are in state of reading
    142        Services.cache2.clear();
    143        log_("cleared");
    144 
    145        // Resume reading the cache data, only now the pinning status on entries will be discovered,
    146        // the deferred dooming code will trigger.
    147        testingInterface.resumeCacheIOThread();
    148 
    149        log_("third set of opens");
    150        // Now open again.  Pinned entries should be there, disk entries should be the renewed entries.
    151        // Callbacks 41-60
    152        for (i = 0; i < kENTRYCOUNT; ++i) {
    153          mc.add();
    154          asyncOpenCacheEntry(
    155            "http://pinned" + i + "/",
    156            "disk",
    157            Ci.nsICacheStorage.OPEN_NORMALLY,
    158            lci,
    159            new OpenCallback(NORMAL, "m" + i, "p" + i, function () {
    160              mc.fired();
    161            })
    162          );
    163 
    164          mc.add();
    165          asyncOpenCacheEntry(
    166            "http://common" + i + "/",
    167            "disk",
    168            Ci.nsICacheStorage.OPEN_NORMALLY,
    169            lci,
    170            new OpenCallback(NEW, "m2" + i, "d2" + i, function () {
    171              mc.fired();
    172            })
    173          );
    174        }
    175 
    176        mc.fired(); // Finishes this test
    177      },
    178    },
    179    "cacheservice:purge-memory-pools"
    180  );
    181 
    182  do_test_pending();
    183 }