Skip to content

Commit d5f92d9

Browse files
Reject pending reads when releasing a reader
Currently, reader.releaseLock() throws an error if there are still pending read requests. However, in #1103 we realized that it would be more useful if we allowed releasing a reader while there are still pending reads, which would reject those reads instead. The user can then acquire a new reader to receive those chunks. For readable byte streams, we also discard pull-into descriptors corresponding to (now rejected) read-into requests. However, we must retain the first pull-into descriptor, since the underlying byte source may still be using it through controller.byobRequest. Instead, we mark this descriptor as "detached", such that when we invalidate this BYOB request (as a result of controller.enqueue() or byobRequest.respond()), the bytes from this descriptor are pushed into the stream's queue and used to fulfill read requests from a future reader. This also allows expressing pipeTo()'s behavior more accurately. Currently, it "cheats" by calling ReadableStreamReaderGenericRelease directly without checking if [[readRequests]] is empty. With the proposed changes, we can safely release the reader when the pipe finishes (even if there's a pending read), and be sure that any unread chunks can be read by a future reader or pipe.
1 parent 53282dd commit d5f92d9

9 files changed

+320
-112
lines changed

index.bs

Lines changed: 177 additions & 58 deletions
Large diffs are not rendered by default.

reference-implementation/lib/ReadableByteStreamController-impl.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22
const assert = require('assert');
33

4-
const { CancelSteps, PullSteps } = require('./abstract-ops/internal-methods.js');
4+
const { CancelSteps, PullSteps, ReleaseSteps } = require('./abstract-ops/internal-methods.js');
55
const { ResetQueue } = require('./abstract-ops/queue-with-sizes.js');
66
const aos = require('./abstract-ops/readable-streams.js');
77

@@ -67,15 +67,7 @@ exports.implementation = class ReadableByteStreamControllerImpl {
6767

6868
if (this._queueTotalSize > 0) {
6969
assert(aos.ReadableStreamGetNumReadRequests(stream) === 0);
70-
71-
const entry = this._queue.shift();
72-
this._queueTotalSize -= entry.byteLength;
73-
74-
aos.ReadableByteStreamControllerHandleQueueDrain(this);
75-
76-
const view = new Uint8Array(entry.buffer, entry.byteOffset, entry.byteLength);
77-
78-
readRequest.chunkSteps(view);
70+
aos.ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest);
7971
return;
8072
}
8173

@@ -106,4 +98,13 @@ exports.implementation = class ReadableByteStreamControllerImpl {
10698
aos.ReadableStreamAddReadRequest(stream, readRequest);
10799
aos.ReadableByteStreamControllerCallPullIfNeeded(this);
108100
}
101+
102+
[ReleaseSteps]() {
103+
if (this._pendingPullIntos.length > 0) {
104+
const firstPullInto = this._pendingPullIntos[0];
105+
firstPullInto.readerType = 'none';
106+
107+
this._pendingPullIntos = [firstPullInto];
108+
}
109+
}
109110
};

reference-implementation/lib/ReadableStream-impl.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ exports.implementation = class ReadableStreamImpl {
129129
const readRequest = {
130130
chunkSteps: chunk => resolvePromise(promise, chunk),
131131
closeSteps: () => {
132-
aos.ReadableStreamReaderGenericRelease(reader);
132+
aos.ReadableStreamDefaultReaderRelease(reader);
133133
resolvePromise(promise, idlUtils.asyncIteratorEOI);
134134
},
135135
errorSteps: e => {
136-
aos.ReadableStreamReaderGenericRelease(reader);
136+
aos.ReadableStreamDefaultReaderRelease(reader);
137137
rejectPromise(promise, e);
138138
}
139139
};
@@ -151,11 +151,11 @@ exports.implementation = class ReadableStreamImpl {
151151

152152
if (iterator._preventCancel === false) {
153153
const result = aos.ReadableStreamReaderGenericCancel(reader, arg);
154-
aos.ReadableStreamReaderGenericRelease(reader);
154+
aos.ReadableStreamDefaultReaderRelease(reader);
155155
return result;
156156
}
157157

158-
aos.ReadableStreamReaderGenericRelease(reader);
158+
aos.ReadableStreamDefaultReaderRelease(reader);
159159
return promiseResolvedWith(undefined);
160160
}
161161
};

reference-implementation/lib/ReadableStreamBYOBReader-impl.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,7 @@ class ReadableStreamBYOBReaderImpl {
4141
return;
4242
}
4343

44-
if (this._readIntoRequests.length > 0) {
45-
throw new TypeError('Tried to release a reader lock when that reader has pending read() calls un-settled');
46-
}
47-
48-
aos.ReadableStreamReaderGenericRelease(this);
44+
aos.ReadableStreamBYOBReaderRelease(this);
4945
}
5046
}
5147

reference-implementation/lib/ReadableStreamDefaultController-impl.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { CancelSteps, PullSteps } = require('./abstract-ops/internal-methods.js');
3+
const { CancelSteps, PullSteps, ReleaseSteps } = require('./abstract-ops/internal-methods.js');
44
const { DequeueValue, ResetQueue } = require('./abstract-ops/queue-with-sizes.js');
55
const aos = require('./abstract-ops/readable-streams.js');
66

@@ -55,4 +55,6 @@ exports.implementation = class ReadableStreamDefaultControllerImpl {
5555
aos.ReadableStreamDefaultControllerCallPullIfNeeded(this);
5656
}
5757
}
58+
59+
[ReleaseSteps]() {}
5860
};

reference-implementation/lib/ReadableStreamDefaultReader-impl.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,7 @@ class ReadableStreamDefaultReaderImpl {
3131
return;
3232
}
3333

34-
if (this._readRequests.length > 0) {
35-
throw new TypeError('Tried to release a reader lock when that reader has pending read() calls un-settled');
36-
}
37-
38-
aos.ReadableStreamReaderGenericRelease(this);
34+
aos.ReadableStreamDefaultReaderRelease(this);
3935
}
4036
}
4137

reference-implementation/lib/abstract-ops/internal-methods.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ exports.AbortSteps = Symbol('[[AbortSteps]]');
44
exports.ErrorSteps = Symbol('[[ErrorSteps]]');
55
exports.CancelSteps = Symbol('[[CancelSteps]]');
66
exports.PullSteps = Symbol('[[PullSteps]]');
7+
exports.ReleaseSteps = Symbol('[[ReleaseSteps]]');

0 commit comments

Comments
 (0)