Skip to content

Commit

Permalink
http,stream: add writableEnded
Browse files Browse the repository at this point in the history
This is work towards resolving the response.finished confusion and
future deprecation.

Note that implementation-wise, streams have both an ending and ended
state. However, in this case (in order to avoid confusion in user space)
writableEnded is equal to writable.ending. The ending vs ended situation
is internal state required for internal stream logic.

PR-URL: #28934
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
ronag authored and Trott committed Aug 17, 2019
1 parent e4bbbcc commit 6f613d8
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 5 deletions.
34 changes: 34 additions & 0 deletions doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,27 @@ req.once('response', (res) => {
});
```

### request.writableEnded
<!-- YAML
added: REPLACEME
-->

* {boolean}

Is `true` after [`request.end()`][] has been called. This property
does not indicate whether the data has been flushed, for this use
[`request.writableFinished`][] instead.

### request.writableFinished
<!-- YAML
added: v12.7.0
-->

* {boolean}

Is `true` if all data has been flushed to the underlying system, immediately
before the [`'finish'`][] event is emitted.

### request.write(chunk[, encoding][, callback])
<!-- YAML
added: v0.1.29
Expand Down Expand Up @@ -1436,6 +1457,17 @@ response.statusMessage = 'Not found';
After response header was sent to the client, this property indicates the
status message which was sent out.

### response.writableEnded
<!-- YAML
added: REPLACEME
-->

* {boolean}

Is `true` after [`response.end()`][] has been called. This property
does not indicate whether the data has been flushed, for this use
[`response.writableFinished`][] instead.

### response.writableFinished
<!-- YAML
added: v12.7.0
Expand Down Expand Up @@ -2222,11 +2254,13 @@ not abort the request or do anything besides add a `'timeout'` event.
[`request.setTimeout()`]: #http_request_settimeout_timeout_callback
[`request.socket.getPeerCertificate()`]: tls.html#tls_tlssocket_getpeercertificate_detailed
[`request.socket`]: #http_request_socket
[`request.writableFinished`]: #http_request_writablefinished
[`request.write(data, encoding)`]: #http_request_write_chunk_encoding_callback
[`response.end()`]: #http_response_end_data_encoding_callback
[`response.getHeader()`]: #http_response_getheader_name
[`response.setHeader()`]: #http_response_setheader_name_value
[`response.socket`]: #http_response_socket
[`response.writableFinished`]: #http_response_writablefinished
[`response.write()`]: #http_response_write_chunk_encoding_callback
[`response.write(data, encoding)`]: #http_response_write_chunk_encoding_callback
[`response.writeContinue()`]: #http_response_writecontinue
Expand Down
12 changes: 12 additions & 0 deletions doc/api/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -3271,6 +3271,17 @@ added: v8.4.0

The [`Http2Stream`][] object backing the response.

#### response.writableEnded
<!-- YAML
added: REPLACEME
-->

* {boolean}

Is `true` after [`response.end()`][] has been called. This property
does not indicate whether the data has been flushed, for this use
[`writable.writableFinished`][] instead.

#### response.write(chunk[, encoding][, callback])
<!-- YAML
added: v8.4.0
Expand Down Expand Up @@ -3510,3 +3521,4 @@ following additional properties:
[`tls.connect()`]: tls.html#tls_tls_connect_options_callback
[`tls.createServer()`]: tls.html#tls_tls_createserver_options_secureconnectionlistener
[error code]: #error_codes
[`writable.writableFinished`]: stream.html#stream_writable_writablefinished
13 changes: 13 additions & 0 deletions doc/api/stream.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,17 @@ added: v11.4.0

Is `true` if it is safe to call [`writable.write()`][stream-write].

##### writable.writableEnded
<!-- YAML
added: REPLACEME
-->

* {boolean}

Is `true` after [`writable.end()`][] has been called. This property
does not indicate whether the data has been flushed, for this use
[`writable.writableFinished`][] instead.

##### writable.writableFinished
<!-- YAML
added: v12.6.0
Expand Down Expand Up @@ -2707,7 +2718,9 @@ contain multi-byte characters.
[`stream.unpipe()`]: #stream_readable_unpipe_destination
[`stream.wrap()`]: #stream_readable_wrap_stream
[`writable.cork()`]: #stream_writable_cork
[`writable.end()`]: #stream_writable_end_chunk_encoding_callback
[`writable.uncork()`]: #stream_writable_uncork
[`writable.writableFinished`]: #stream_writable_writablefinished
[`zlib.createDeflate()`]: zlib.html#zlib_zlib_createdeflate_options
[API for Stream Consumers]: #stream_api_for_stream_consumers
[API for Stream Implementers]: #stream_api_for_stream_implementers
Expand Down
4 changes: 4 additions & 0 deletions lib/_http_outgoing.js
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,10 @@ Object.defineProperty(OutgoingMessage.prototype, 'headersSent', {
get: function() { return !!this._header; }
});

Object.defineProperty(OutgoingMessage.prototype, 'writableEnded', {
get: function() { return this.finished; }
});


const crlf_buf = Buffer.from('\r\n');
OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
Expand Down
16 changes: 13 additions & 3 deletions lib/_stream_duplex.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', {
// userland will fail
enumerable: false,
get() {
return this._writableState.highWaterMark;
return this._writableState && this._writableState.highWaterMark;
}
});

Expand All @@ -94,7 +94,7 @@ Object.defineProperty(Duplex.prototype, 'writableLength', {
// userland will fail
enumerable: false,
get() {
return this._writableState.length;
return this._writableState && this._writableState.length;
}
});

Expand All @@ -104,7 +104,17 @@ Object.defineProperty(Duplex.prototype, 'writableFinished', {
// userland will fail
enumerable: false,
get() {
return this._writableState.finished;
return this._writableState ? this._writableState.finished : false;
}
});

Object.defineProperty(Duplex.prototype, 'writableEnded', {
// Making it explicit this property is not enumerable
// because otherwise some prototype manipulation in
// userland will fail
enumerable: false,
get() {
return this._writableState ? this._writableState.ending : false;
}
});

Expand Down
14 changes: 12 additions & 2 deletions lib/_stream_writable.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,23 @@ function decodeChunk(state, chunk, encoding) {
return chunk;
}

Object.defineProperty(Writable.prototype, 'writableEnded', {
// Making it explicit this property is not enumerable
// because otherwise some prototype manipulation in
// userland will fail
enumerable: false,
get: function() {
return this._writableState ? this._writableState.ending : false;
}
});

Object.defineProperty(Writable.prototype, 'writableHighWaterMark', {
// Making it explicit this property is not enumerable
// because otherwise some prototype manipulation in
// userland will fail
enumerable: false,
get: function() {
return this._writableState.highWaterMark;
return this._writableState && this._writableState.highWaterMark;
}
});

Expand Down Expand Up @@ -711,7 +721,7 @@ Object.defineProperty(Writable.prototype, 'writableFinished', {
// userland will fail
enumerable: false,
get() {
return this._writableState.finished;
return this._writableState ? this._writableState.finished : false;
}
});

Expand Down
5 changes: 5 additions & 0 deletions lib/internal/http2/compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,11 @@ class Http2ServerResponse extends Stream {
return this.headersSent;
}

get writableEnded() {
const state = this[kState];
return state.ending;
}

get finished() {
const stream = this[kStream];
return stream.destroyed ||
Expand Down
2 changes: 2 additions & 0 deletions test/parallel/test-http-outgoing-finish-writable.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ const http = require('http');
const server = http.createServer(common.mustCall(function(req, res) {
assert.strictEqual(res.writable, true);
assert.strictEqual(res.finished, false);
assert.strictEqual(res.writableEnded, false);
res.end();

// res.writable is set to false after it has finished sending
// Ref: https://github.com/nodejs/node/issues/15029
assert.strictEqual(res.writable, true);
assert.strictEqual(res.finished, true);
assert.strictEqual(res.writableEnded, true);

server.close();
}));
Expand Down
2 changes: 2 additions & 0 deletions test/parallel/test-http2-compat-serverresponse-end.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,10 @@ const {
// for http1 compatibility
const server = createServer(mustCall((request, response) => {
strictEqual(response.finished, true);
strictEqual(response.writableEnded, false);
response.writeHead(HTTP_STATUS_OK, { foo: 'bar' });
response.end('data', mustCall());
strictEqual(response.writableEnded, true);
}));
server.listen(0, mustCall(() => {
const { port } = server.address();
Expand Down
2 changes: 2 additions & 0 deletions test/parallel/test-http2-compat-serverresponse-finished.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ server.listen(0, common.mustCall(() => {
}));
}));
assert.strictEqual(response.finished, false);
assert.strictEqual(response.writableEnded, false);
response.end();
assert.strictEqual(response.finished, true);
assert.strictEqual(response.writableEnded, true);
}));

const url = `http://localhost:${port}`;
Expand Down
4 changes: 4 additions & 0 deletions test/parallel/test-stream-writable-ended-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ const writable = new stream.Writable();

writable._write = (chunk, encoding, cb) => {
assert.strictEqual(writable._writableState.ended, false);
assert.strictEqual(writable.writableEnded, false);
cb();
};

assert.strictEqual(writable._writableState.ended, false);
assert.strictEqual(writable.writableEnded, false);

writable.end('testing ended state', common.mustCall(() => {
assert.strictEqual(writable._writableState.ended, true);
assert.strictEqual(writable.writableEnded, true);
}));

assert.strictEqual(writable._writableState.ended, true);
assert.strictEqual(writable.writableEnded, true);

0 comments on commit 6f613d8

Please sign in to comment.