Skip to content

Commit 81d09be

Browse files
authored
fix: correctly handle nexttick scheduling in stream reads (electron#24022)
1 parent 130b176 commit 81d09be

File tree

3 files changed

+47
-1
lines changed

3 files changed

+47
-1
lines changed

shell/browser/net/node_stream_loader.cc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ void NodeStreamLoader::Start(network::mojom::URLResponseHeadPtr head) {
6868
void NodeStreamLoader::NotifyReadable() {
6969
if (!readable_)
7070
ReadMore();
71+
else if (is_reading_)
72+
has_read_waiting_ = true;
7173
readable_ = true;
7274
}
7375

@@ -101,8 +103,16 @@ void NodeStreamLoader::ReadMore() {
101103
// If there is no buffer read, wait until |readable| is emitted again.
102104
v8::Local<v8::Value> buffer;
103105
if (!ret.ToLocal(&buffer) || !node::Buffer::HasInstance(buffer)) {
104-
readable_ = false;
105106
is_reading_ = false;
107+
108+
// If 'readable' was called after 'read()', try again
109+
if (has_read_waiting_) {
110+
has_read_waiting_ = false;
111+
ReadMore();
112+
return;
113+
}
114+
115+
readable_ = false;
106116
if (ended_) {
107117
NotifyComplete(result_);
108118
}

shell/browser/net/node_stream_loader.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ class NodeStreamLoader : public network::mojom::URLLoader {
8787
// flag.
8888
bool readable_ = false;
8989

90+
// It's possible for reads to be queued using nextTick() during read()
91+
// which will cause 'readable' to emit during ReadMore, so we track if
92+
// that occurred in a flag.
93+
bool has_read_waiting_ = false;
94+
9095
// Store the V8 callbacks to unsubscribe them later.
9196
std::map<std::string, v8::Global<v8::Value>> handlers_;
9297

spec-main/api-protocol-spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as http from 'http';
77
import * as fs from 'fs';
88
import * as qs from 'querystring';
99
import * as stream from 'stream';
10+
import { EventEmitter } from 'events';
1011
import { closeWindow } from './window-helpers';
1112
import { emittedOnce } from './events-helpers';
1213
import { WebmGenerator } from './video-helpers';
@@ -410,6 +411,36 @@ describe('protocol module', () => {
410411
const r = await ajax(protocolName + '://fake-host');
411412
expect(r.data).to.have.lengthOf(1024 * 1024 * 2);
412413
});
414+
415+
it('can handle next-tick scheduling during read calls', async () => {
416+
const events = new EventEmitter();
417+
function createStream () {
418+
const buffers = [
419+
Buffer.alloc(65536),
420+
Buffer.alloc(65537),
421+
Buffer.alloc(39156)
422+
];
423+
const e = new stream.Readable({ highWaterMark: 0 });
424+
e.push(buffers.shift());
425+
e._read = function () {
426+
process.nextTick(() => this.push(buffers.shift() || null));
427+
};
428+
e.on('end', function () {
429+
events.emit('end');
430+
});
431+
return e;
432+
}
433+
registerStreamProtocol(protocolName, (request, callback) => {
434+
callback({
435+
statusCode: 200,
436+
headers: { 'Content-Type': 'text/plain' },
437+
data: createStream()
438+
});
439+
});
440+
const hasEndedPromise = emittedOnce(events, 'end');
441+
ajax(protocolName + '://fake-host');
442+
await hasEndedPromise;
443+
});
413444
});
414445

415446
describe('protocol.isProtocolRegistered', () => {

0 commit comments

Comments
 (0)