Skip to content

Commit

Permalink
net: make readable/writable start as true
Browse files Browse the repository at this point in the history
`net.Socket` is slightly breaking stream invariants by
having readable/writable going from `false` to `true`.
Streams assume that readable/writable starts out `true`
and then goes to `false` through `push(null)`/`end()`
after which it never goes back to `true`, e.g. once a
stream is `writable == false` it is assumed it will
never become `true`.

This PR changes 2 things:

Unless explicitly set to `false` through options:

- starts as `readable`/`writable` `true` by default.
- uses `push(null)`/`end()` to set `readable`/`writable`
  to `false`. Note that this would cause the socket to
  emit the `'end'`/`'finish'` events, which it did not
  do previously.

In the case it is explicitly set to `false` through
options` it is assumed to never become `true`.

PR-URL: #32272
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: James M Snell <[email protected]>
  • Loading branch information
ronag committed Mar 24, 2020
1 parent 96f06e6 commit eeccd52
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 33 deletions.
10 changes: 6 additions & 4 deletions lib/_stream_readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

const {
ArrayIsArray,
Boolean,
NumberIsInteger,
NumberIsNaN,
ObjectDefineProperties,
Expand Down Expand Up @@ -1061,9 +1060,12 @@ ObjectDefineProperties(Readable.prototype, {
readable: {
get() {
const r = this._readableState;
if (!r) return false;
if (r.readable !== undefined) return r.readable && !r.endEmitted;
return Boolean(!r.destroyed && !r.errorEmitted && !r.endEmitted);
// r.readable === false means that this is part of a Duplex stream
// where the readable side was disabled upon construction.
// Compat. The user might manually disable readable side through
// deprecated setter.
return !!r && r.readable !== false && !r.destroyed && !r.errorEmitted &&
!r.endEmitted;
},
set(val) {
// Backwards compat.
Expand Down
10 changes: 6 additions & 4 deletions lib/_stream_writable.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

const {
Array,
Boolean,
FunctionPrototype,
ObjectDefineProperty,
ObjectDefineProperties,
Expand Down Expand Up @@ -744,9 +743,12 @@ ObjectDefineProperties(Writable.prototype, {
writable: {
get() {
const w = this._writableState;
if (!w) return false;
if (w.writable !== undefined) return w.writable && !w.ended;
return Boolean(!w.destroyed && !w.errored && !w.ending);
// w.writable === false means that this is part of a Duplex stream
// where the writable side was disabled upon construction.
// Compat. The user might manually disable writable side through
// deprecated setter.
return !!w && w.writable !== false && !w.destroyed && !w.errored &&
!w.ending;
},
set(val) {
// Backwards compatible.
Expand Down
9 changes: 1 addition & 8 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,9 +494,7 @@ function TLSSocket(socket, opts) {
handle: this._wrapHandle(wrap),
allowHalfOpen: socket ? socket.allowHalfOpen : tlsOptions.allowHalfOpen,
pauseOnCreate: tlsOptions.pauseOnConnect,
// The readable flag is only needed if pauseOnCreate will be handled.
readable: tlsOptions.pauseOnConnect,
writable: false
manualStart: true
});

// Proxy for API compatibility
Expand All @@ -506,11 +504,6 @@ function TLSSocket(socket, opts) {

this._init(socket, wrap);

// Make sure to setup all required properties like: `connecting` before
// starting the flow of the data
this.readable = true;
this.writable = true;

if (enableTrace && this._handle)
this._handle.enableTrace();

Expand Down
14 changes: 7 additions & 7 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,6 @@ function Socket(options) {
else
options = { ...options };

options.readable = options.readable || false;
options.writable = options.writable || false;
const { allowHalfOpen } = options;

// Prevent the "no-half-open enforcer" from being inherited from `Duplex`.
Expand Down Expand Up @@ -655,8 +653,6 @@ Socket.prototype._destroy = function(exception, cb) {

this.connecting = false;

this.readable = this.writable = false;

for (let s = this; s !== null; s = s._parent) {
clearTimeout(s[kTimeout]);
}
Expand Down Expand Up @@ -1120,9 +1116,13 @@ function afterConnect(status, handle, req, readable, writable) {
self._sockname = null;

if (status === 0) {
self.readable = readable;
if (!self._writableState.ended)
self.writable = writable;
if (self.readable && !readable) {
self.push(null);
self.read();
}
if (self.writable && !writable) {
self.end();
}
self._unrefTimer();

self.emit('connect');
Expand Down
10 changes: 5 additions & 5 deletions test/parallel/test-net-socket-constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ function test(sock, readable, writable) {
}

if (cluster.isMaster) {
test(undefined, false, false);
test(undefined, true, true);

const server = net.createServer(common.mustCall((socket) => {
socket.unref();
test(socket, true, true);
test({ handle: socket._handle }, false, false);
test({ handle: socket._handle }, true, true);
test({ handle: socket._handle, readable: true, writable: true },
true, true);
server.close();
Expand All @@ -45,7 +45,7 @@ if (cluster.isMaster) {
socket.end();
}));

test(socket, false, true);
test(socket, true, true);
}));

cluster.setupMaster({
Expand All @@ -58,8 +58,8 @@ if (cluster.isMaster) {
assert.strictEqual(signal, null);
}));
} else {
test(4, false, false);
test({ fd: 5 }, false, false);
test(4, true, true);
test({ fd: 5 }, true, true);
test({ fd: 6, readable: true, writable: true }, true, true);
process.disconnect();
}
15 changes: 10 additions & 5 deletions test/parallel/test-net-socket-setnodelay.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,32 @@ const genSetNoDelay = (desiredArg) => (enable) => {
// setNoDelay should default to true
let socket = new net.Socket({
handle: {
setNoDelay: common.mustCall(genSetNoDelay(true))
setNoDelay: common.mustCall(genSetNoDelay(true)),
readStart() {}
}
});
socket.setNoDelay();

socket = new net.Socket({
handle: {
setNoDelay: common.mustCall(genSetNoDelay(true), 1)
setNoDelay: common.mustCall(genSetNoDelay(true), 1),
readStart() {}
}
});
truthyValues.forEach((testVal) => socket.setNoDelay(testVal));

socket = new net.Socket({
handle: {
setNoDelay: common.mustNotCall()
setNoDelay: common.mustNotCall(),
readStart() {}
}
});
falseyValues.forEach((testVal) => socket.setNoDelay(testVal));

socket = new net.Socket({
handle: {
setNoDelay: common.mustCall(() => {}, 3)
setNoDelay: common.mustCall(() => {}, 3),
readStart() {}
}
});
truthyValues.concat(falseyValues).concat(truthyValues)
Expand All @@ -44,7 +48,8 @@ truthyValues.concat(falseyValues).concat(truthyValues)
// In the case below, if it is called an exception will be thrown
socket = new net.Socket({
handle: {
setNoDelay: null
setNoDelay: null,
readStart() {}
}
});
const returned = socket.setNoDelay(true);
Expand Down

0 comments on commit eeccd52

Please sign in to comment.