Bug 1792675 - Prevent blocking main process for too long in LineReader.jsm. r=mkmelin
authorPing Chen <remotenonsense@gmail.com>
Fri, 30 Sep 2022 02:04:58 +0000
changeset 36838 b9e3fe9b347017afda349fbaad1678762fcce6b3
parent 36837 eeb087154a2634853a7ab059a92deccf60afc794
child 36839 5847feb671ebad139302665588bfa51362f51dd8
push id20341
push usergeoff@darktrojan.net
push dateFri, 30 Sep 2022 04:25:32 +0000
treeherdercomm-central@b9e3fe9b3470 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1792675
Bug 1792675 - Prevent blocking main process for too long in LineReader.jsm. r=mkmelin Differential Revision: https://phabricator.services.mozilla.com/D158387
mailnews/base/src/LineReader.jsm
--- a/mailnews/base/src/LineReader.jsm
+++ b/mailnews/base/src/LineReader.jsm
@@ -11,48 +11,57 @@ const EXPORTED_SYMBOLS = ["LineReader"];
  * This class helps dealing with multi-line responses by:
  * - Break up a response to lines
  * - Join incomplete line from a previous response with the current response
  * - Remove stuffed dot (.. at the beginning of a line)
  * - Detect the end of the response (\r\n.\r\n)
  */
 class LineReader {
   processingMultiLineResponse = false;
+  _data = "";
 
   /**
    * Read a multi-line response, emit each line through a callback.
    * @param {string} data - A multi-line response received from the server.
    * @param {Function} lineCallback - A line will be passed to the callback each
    *   time.
    * @param {Function} doneCallback - A function to be called when data is ended.
    */
   read(data, lineCallback, doneCallback) {
-    if (this._leftoverData) {
-      // For a single request, the response can span multiple responses.
-      // Concatenate the leftover from the last data to the current data.
-      data = this._leftoverData + data;
-      this._leftoverData = null;
+    this._data += data;
+    if (this._data == ".\r\n" || this._data.endsWith("\r\n.\r\n")) {
+      this.processingMultiLineResponse = false;
+      this._data = this._data.slice(0, -3);
+    } else {
+      this.processingMultiLineResponse = true;
     }
-    this.processingMultiLineResponse = true;
-    if (data == ".\r\n" || data.endsWith("\r\n.\r\n")) {
-      this.processingMultiLineResponse = false;
-      data = data.slice(0, -3);
+    if (this._running) {
+      // This function can be called multiple times, but this._data should only
+      // be consumed once.
+      return;
     }
-    while (data) {
-      let index = data.indexOf("\r\n");
+
+    let i = 0;
+    this._running = true;
+    while (this._data) {
+      let index = this._data.indexOf("\r\n");
       if (index == -1) {
         // Not enough data, save it for the next round.
-        this._leftoverData = data;
         break;
       }
-      let line = data.slice(0, index + 2);
+      let line = this._data.slice(0, index + 2);
       if (line.startsWith("..")) {
         // Remove stuffed dot.
         line = line.slice(1);
       }
       lineCallback(line);
-      data = data.slice(index + 2);
+      this._data = this._data.slice(index + 2);
+      if (++i % 100 == 0) {
+        // Prevent blocking main process for too long.
+        Services.tm.spinEventLoopUntilEmpty();
+      }
     }
-    if (!this.processingMultiLineResponse) {
+    this._running = false;
+    if (!this.processingMultiLineResponse && !this._data) {
       doneCallback();
     }
   }
 }