Browse Source

fix: correctly handle nexttick scheduling in stream reads (#24082)

Co-authored-by: Paul Frazee <[email protected]>
trop[bot] 4 years ago
parent
commit
db7ac3587e

+ 11 - 1
shell/browser/net/node_stream_loader.cc

@@ -68,6 +68,8 @@ void NodeStreamLoader::Start(network::mojom::URLResponseHeadPtr head) {
 void NodeStreamLoader::NotifyReadable() {
   if (!readable_)
     ReadMore();
+  else if (is_reading_)
+    has_read_waiting_ = true;
   readable_ = true;
 }
 
@@ -100,8 +102,16 @@ void NodeStreamLoader::ReadMore() {
   // If there is no buffer read, wait until |readable| is emitted again.
   v8::Local<v8::Value> buffer;
   if (!ret.ToLocal(&buffer) || !node::Buffer::HasInstance(buffer)) {
-    readable_ = false;
     is_reading_ = false;
+
+    // If 'readable' was called after 'read()', try again
+    if (has_read_waiting_) {
+      has_read_waiting_ = false;
+      ReadMore();
+      return;
+    }
+
+    readable_ = false;
     if (ended_) {
       NotifyComplete(result_);
     }

+ 5 - 0
shell/browser/net/node_stream_loader.h

@@ -85,6 +85,11 @@ class NodeStreamLoader : public network::mojom::URLLoader {
   // flag.
   bool readable_ = false;
 
+  // It's possible for reads to be queued using nextTick() during read()
+  // which will cause 'readable' to emit during ReadMore, so we track if
+  // that occurred in a flag.
+  bool has_read_waiting_ = false;
+
   // Store the V8 callbacks to unsubscribe them later.
   std::map<std::string, v8::Global<v8::Value>> handlers_;
 

+ 31 - 0
spec-main/api-protocol-spec.ts

@@ -8,6 +8,7 @@ import * as http from 'http';
 import * as fs from 'fs';
 import * as qs from 'querystring';
 import * as stream from 'stream';
+import { EventEmitter } from 'events';
 import { closeWindow } from './window-helpers';
 import { emittedOnce } from './events-helpers';
 
@@ -412,6 +413,36 @@ describe('protocol module', () => {
       const r = await ajax(protocolName + '://fake-host');
       expect(r.data).to.have.lengthOf(1024 * 1024 * 2);
     });
+
+    it('can handle next-tick scheduling during read calls', async () => {
+      const events = new EventEmitter();
+      function createStream () {
+        const buffers = [
+          Buffer.alloc(65536),
+          Buffer.alloc(65537),
+          Buffer.alloc(39156)
+        ];
+        const e = new stream.Readable({ highWaterMark: 0 });
+        e.push(buffers.shift());
+        e._read = function () {
+          process.nextTick(() => this.push(buffers.shift() || null));
+        };
+        e.on('end', function () {
+          events.emit('end');
+        });
+        return e;
+      }
+      registerStreamProtocol(protocolName, (request, callback) => {
+        callback({
+          statusCode: 200,
+          headers: { 'Content-Type': 'text/plain' },
+          data: createStream()
+        });
+      });
+      const hasEndedPromise = emittedOnce(events, 'end');
+      ajax(protocolName + '://fake-host');
+      await hasEndedPromise;
+    });
   });
 
   describe('protocol.isProtocolHandled', () => {