Bug 1742578 - Avoid repeatedly obtaining OpenPGP key listings using a LRU cache. r=mkmelin
authorKai Engert <kaie@kuix.de>
Mon, 29 Nov 2021 20:43:10 +0200
changeset 34417 c3d468fc8947be91dbdd1585375d4c22fe6bcd9b
parent 34416 ef3c9fd0bc35c437ad9fa3414892d1f002444987
child 34418 86650caf004480a503bf44eafd3615675dc55c85
push id19418
push usermkmelin@iki.fi
push dateMon, 29 Nov 2021 18:43:44 +0000
treeherdercomm-central@c3d468fc8947 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1742578
Bug 1742578 - Avoid repeatedly obtaining OpenPGP key listings using a LRU cache. r=mkmelin Differential Revision: https://phabricator.services.mozilla.com/D132196
mail/extensions/openpgp/content/modules/key.jsm
--- a/mail/extensions/openpgp/content/modules/key.jsm
+++ b/mail/extensions/openpgp/content/modules/key.jsm
@@ -112,16 +112,20 @@ var EnigmailKey = {
           keyId,
         })
         .then(value => {
           EnigmailDialog.alert(null, value);
         });
     }
   },
 
+  _keyListCache: new Map(),
+  _keyListCacheMaxEntries: 50,
+  _keyListCacheMaxKeySize: 30720,
+
   /**
    * Get details (key ID, UID) of the data contained in a OpenPGP key block
    *
    * @param keyBlockStr  String: the contents of one or more public keys
    * @param errorMsgObj  Object: obj.value will contain an error message in case of failures
    * @param interactive  Boolean: if in interactive mode, may display dialogs (default: true)
    *
    * @return Array of objects with the following structure:
@@ -134,44 +138,86 @@ var EnigmailKey = {
     keyBlockStr,
     errorMsgObj,
     interactive = true,
     pubkey,
     seckey
   ) {
     EnigmailLog.DEBUG("key.jsm: getKeyListFromKeyBlock\n");
 
+    let cacheEntry = this._keyListCache.get(keyBlockStr);
+    if (cacheEntry) {
+      // Remove and re-insert to move entry to the end of insertion order,
+      // so we know which entry was used least recently.
+      this._keyListCache.delete(keyBlockStr);
+      this._keyListCache.set(keyBlockStr, cacheEntry);
+
+      if (cacheEntry.error) {
+        errorMsgObj.value = cacheEntry.error;
+        return null;
+      }
+      return cacheEntry.data;
+    }
+
+    // We primarily want to cache single keys that are found in email
+    // attachments. We shouldn't attempt to cache larger key blocks
+    // that are likely arriving from explicit import attempts.
+    let updateCache = keyBlockStr.length < this._keyListCacheMaxKeySize;
+
+    if (
+      updateCache &&
+      this._keyListCache.size >= this._keyListCacheMaxEntries
+    ) {
+      // Remove oldest entry, make room for new entry.
+      this._keyListCache.delete(this._keyListCache.keys().next().value);
+    }
+
     const cApi = EnigmailCryptoAPI();
     let keyList;
     let key = {};
     let blocks;
     errorMsgObj.value = "";
 
     try {
       keyList = cApi.sync(
         cApi.getKeyListFromKeyBlockAPI(keyBlockStr, pubkey, seckey, true)
       );
     } catch (ex) {
       errorMsgObj.value = ex.toString();
+      if (updateCache) {
+        this._keyListCache.set(keyBlockStr, {
+          error: errorMsgObj.value,
+          data: null,
+        });
+      }
       return null;
     }
 
     if (!keyList) {
+      if (updateCache) {
+        this._keyListCache.set(keyBlockStr, { error: undefined, data: null });
+      }
       return null;
     }
 
     if (interactive && keyList.length === 1) {
       // TODO: not yet tested
       key = keyList[0];
       if ("revoke" in key && !("name" in key)) {
+        if (updateCache) {
+          this._keyListCache.set(keyBlockStr, { error: undefined, data: [] });
+        }
         this.importRevocationCert(key.id, blocks.join("\n"));
         return [];
       }
     }
 
+    if (updateCache) {
+      this._keyListCache.set(keyBlockStr, { error: undefined, data: keyList });
+    }
     return keyList;
   },
 
   /**
    * Get details of a key block to import. Works identically as getKeyListFromKeyBlock();
    * except that the input is a file instead of a string
    *
    * @param file         nsIFile object - file to read