From 539938969d5ad13db58fe75f5d4a921e977689eb Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 13 Apr 2024 16:03:17 -0400 Subject: [PATCH] Ensure reading last data written Related discussion: https://github.com/uBlockOrigin/uBlock-discussions/discussions/888 --- src/js/cachestorage.js | 73 +++++++++++++++++++++--------------------- src/js/start.js | 8 ----- src/js/storage.js | 3 -- 3 files changed, 36 insertions(+), 48 deletions(-) diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index 38a6533be..d94be62e3 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -23,7 +23,6 @@ import * as s14e from './s14e-serializer.js'; -import lz4Codec from './lz4.js'; import { ubolog } from './console.js'; import webext from './webext.js'; import µb from './background.js'; @@ -32,6 +31,7 @@ import µb from './background.js'; const STORAGE_NAME = 'uBlock0CacheStorage'; const extensionStorage = webext.storage.local; +const pendingWrite = new Map(); const keysFromGetArg = arg => { if ( arg === null || arg === undefined ) { return []; } @@ -58,8 +58,18 @@ const hasOwnProperty = (o, p) => const cacheStorage = (( ) => { - const exGet = (api, wanted, outbin) => { - return api.get(wanted).then(inbin => { + const exGet = async (api, wanted, outbin) => { + ubolog('cacheStorage:', api.name || 'storage.local', wanted.join()); + const missing = []; + for ( const key of wanted ) { + if ( pendingWrite.has(key) ) { + outbin[key] = pendingWrite.get(key); + } else { + missing.push(key); + } + } + if ( missing.length === 0 ) { return; } + return api.get(missing).then(inbin => { inbin = inbin || {}; const found = Object.keys(inbin); Object.assign(outbin, inbin); @@ -139,16 +149,26 @@ const cacheStorage = (( ) => { async set(rawbin) { const keys = Object.keys(rawbin); if ( keys.length === 0 ) { return; } - const serializedbin = {}; - const promises = []; for ( const key of keys ) { - promises.push(compress(serializedbin, key, rawbin[key])); + pendingWrite.set(key, rawbin[key]); } - await Promise.all(promises); - cacheAPIs[fastCache].set(rawbin, serializedbin); - return extensionStorage.set(serializedbin).catch(reason => { + try { + const serializedbin = {}; + const promises = []; + for ( const key of keys ) { + promises.push(compress(serializedbin, key, rawbin[key])); + } + await Promise.all(promises); + await Promise.all([ + cacheAPIs[fastCache].set(rawbin, serializedbin), + extensionStorage.set(serializedbin), + ]); + } catch(reason) { ubolog(reason); - }); + } + for ( const key of keys ) { + pendingWrite.delete(key); + } }, remove(...args) { @@ -295,6 +315,7 @@ const cacheAPI = (( ) => { }; return { + name: 'cacheAPI', async get(arg) { const keys = keysFromGetArg(arg); if ( keys === undefined ) { return; } @@ -393,6 +414,7 @@ const memoryStorage = (( ) => { }; return { + name: 'memoryStorage', get(...args) { return sessionStorage.get(...args).then(bin => { return bin; @@ -491,25 +513,6 @@ const idbStorage = (( ) => { return /^cache\/(compiled|selfie)\//.test(key); }; - const fromBlob = data => { - if ( data instanceof Blob === false ) { - return Promise.resolve(data); - } - return new Promise(resolve => { - const blobReader = new FileReader(); - blobReader.onloadend = ev => { - resolve(new Uint8Array(ev.target.result)); - }; - blobReader.readAsArrayBuffer(data); - }); - }; - - const decompress = (key, value) => { - return lz4Codec.decode(value, fromBlob).then(value => { - return { key, value }; - }); - }; - const getAllEntries = async function() { const db = await getDb(); if ( db === null ) { return []; } @@ -527,9 +530,7 @@ const idbStorage = (( ) => { const cursor = ev.target && ev.target.result; if ( !cursor ) { return; } const { key, value } = cursor.value; - if ( value instanceof Blob ) { - entries.push(decompress(key, value)); - } else { + if ( value instanceof Blob === false ) { entries.push({ key, value }); } cursor.continue(); @@ -576,11 +577,8 @@ const idbStorage = (( ) => { if ( typeof result !== 'object' ) { return; } if ( result === null ) { return; } const { key, value } = result; - if ( value instanceof Blob ) { - entries.push(decompress(key, value)); - } else { - entries.push({ key, value }); - } + if ( value instanceof Blob ) { return; } + entries.push({ key, value }); }; const transaction = db.transaction(STORAGE_NAME, 'readonly'); transaction.oncomplete = @@ -656,6 +654,7 @@ const idbStorage = (( ) => { }; return { + name: 'idbStorage', async get(argbin) { const keys = keysFromGetArg(argbin); if ( keys === undefined ) { return; } diff --git a/src/js/start.js b/src/js/start.js index 46a052f4f..18e7dbdb4 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -45,7 +45,6 @@ import { filteringBehaviorChanged } from './broadcast.js'; import cacheStorage from './cachestorage.js'; import { ubolog } from './console.js'; import contextMenu from './contextmenu.js'; -import lz4Codec from './lz4.js'; import { redirectEngine } from './redirect-engine.js'; import staticFilteringReverseLookup from './reverselookup.js'; import staticExtFilteringEngine from './static-ext-filtering.js'; @@ -490,13 +489,6 @@ await initializeTabs(); // Start network observers. webRequest.start(); -// Ensure that the resources allocated for decompression purpose (likely -// large buffers) are garbage-collectable immediately after launch. -// Otherwise I have observed that it may take quite a while before the -// garbage collection of these resources kicks in. Relinquishing as soon -// as possible ensure minimal memory usage baseline. -lz4Codec.relinquish(); - // Force an update of the context menu according to the currently // active tab. contextMenu.update(); diff --git a/src/js/storage.js b/src/js/storage.js index 9ccd9e062..dfd9695c3 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -38,7 +38,6 @@ import cosmeticFilteringEngine from './cosmetic-filtering.js'; import { hostnameFromURI } from './uri-utils.js'; import io from './assets.js'; import logger from './logger.js'; -import lz4Codec from './lz4.js'; import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js'; import punycode from '../lib/punycode.js'; import { redirectEngine } from './redirect-engine.js'; @@ -928,7 +927,6 @@ onBroadcast(msg => { }); µb.selfieManager.destroy(); - lz4Codec.relinquish(); µb.compiledFormatChanged = false; loadingPromise = undefined; @@ -1324,7 +1322,6 @@ onBroadcast(msg => { staticNetFilteringEngine.toSelfie() ), ]); - lz4Codec.relinquish(); µb.selfieIsInvalid = false; ubolog('Filtering engine selfie created'); };