Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge http2 stream & net socket #19527

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 14 additions & 47 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@ const binding = process.binding('http2');
const { FileHandle } = process.binding('fs');
const { StreamPipe } = internalBinding('stream_pipe');
const assert = require('assert');
const { Buffer } = require('buffer');
const EventEmitter = require('events');
const net = require('net');
const tls = require('tls');
const util = require('util');
const fs = require('fs');
const {
errnoException,
codes: {
ERR_HTTP2_ALTSVC_INVALID_ORIGIN,
ERR_HTTP2_ALTSVC_LENGTH,
Expand Down Expand Up @@ -107,8 +105,13 @@ const {
validateTimerDuration,
refreshFnSymbol
} = require('internal/timers');
const {
createWriteWrap,
writeGeneric,
writevGeneric
} = require('internal/stream_base_commons');

const { ShutdownWrap, WriteWrap } = process.binding('stream_wrap');
const { ShutdownWrap } = process.binding('stream_wrap');
const { constants, nameForErrorCode } = binding;

const NETServer = net.Server;
Expand Down Expand Up @@ -1426,28 +1429,6 @@ class ClientHttp2Session extends Http2Session {
}
}

function createWriteReq(req, handle, data, encoding) {
switch (encoding) {
case 'utf8':
case 'utf-8':
return handle.writeUtf8String(req, data);
case 'ascii':
return handle.writeAsciiString(req, data);
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return handle.writeUcs2String(req, data);
case 'latin1':
case 'binary':
return handle.writeLatin1String(req, data);
case 'buffer':
return handle.writeBuffer(req, data);
default:
return handle.writeBuffer(req, Buffer.from(data, encoding));
}
}

function trackWriteState(stream, bytes) {
const session = stream[kSession];
stream[kState].writeQueueSize += bytes;
Expand Down Expand Up @@ -1671,16 +1652,12 @@ class Http2Stream extends Duplex {
if (!this.headersSent)
this[kProceed]();

const handle = this[kHandle];
const req = new WriteWrap();
const req = createWriteWrap(this[kHandle], afterDoStreamWrite);
req.stream = this[kID];
req.handle = handle;
req.callback = cb;
req.oncomplete = afterDoStreamWrite;
req.async = false;
const err = createWriteReq(req, handle, data, encoding);
if (err)
return this.destroy(errnoException(err, 'write', req.error), cb);

writeGeneric(this, req, data, encoding, cb);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have this done for writev too :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax done 👍


trackWriteState(this, req.bytes);
}

Expand Down Expand Up @@ -1708,22 +1685,12 @@ class Http2Stream extends Duplex {
if (!this.headersSent)
this[kProceed]();

const handle = this[kHandle];
const req = new WriteWrap();
const req = createWriteWrap(this[kHandle], afterDoStreamWrite);
req.stream = this[kID];
req.handle = handle;
req.callback = cb;
req.oncomplete = afterDoStreamWrite;
req.async = false;
const chunks = new Array(data.length << 1);
for (var i = 0; i < data.length; i++) {
const entry = data[i];
chunks[i * 2] = entry.chunk;
chunks[i * 2 + 1] = entry.encoding;
}
const err = handle.writev(req, chunks);
if (err)
return this.destroy(errnoException(err, 'write', req.error), cb);

writevGeneric(this, req, data, cb);

trackWriteState(this, req.bytes);
}

Expand Down
79 changes: 79 additions & 0 deletions lib/internal/stream_base_commons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

const { Buffer } = require('buffer');
const errors = require('internal/errors');
const { WriteWrap } = process.binding('stream_wrap');

const errnoException = errors.errnoException;

function handleWriteReq(req, data, encoding) {
const { handle } = req;

switch (encoding) {
case 'buffer':
return handle.writeBuffer(req, data);
case 'latin1':
case 'binary':
return handle.writeLatin1String(req, data);
case 'utf8':
case 'utf-8':
return handle.writeUtf8String(req, data);
case 'ascii':
return handle.writeAsciiString(req, data);
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return handle.writeUcs2String(req, data);
default:
return handle.writeBuffer(req, Buffer.from(data, encoding));
}
}

function createWriteWrap(handle, oncomplete) {
const req = new WriteWrap();

req.handle = handle;
req.oncomplete = oncomplete;
req.async = false;

return req;
}

function writevGeneric(self, req, data, cb) {
const allBuffers = data.allBuffers;
let chunks;
let i;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you restore these as var? I'm not sure since when let variables are on par with var variables for tight loop performance, but this is one of the hottest path in core, I would prefer if we used very fast ES5

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcollina I might be wrong, but I think it shouldn’t make a difference for variables that are at the top level of a function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const or let have some overhead in Node 8 and 9 in tight loops such as this one. I’m not sure if this still the case with current master.

I’d prefer if this refactoring was separated from changing from var to const/let.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcollina makes sense, i'll change them back to var

if (allBuffers) {
chunks = data;
for (i = 0; i < data.length; i++)
data[i] = data[i].chunk;
} else {
chunks = new Array(data.length << 1);
for (i = 0; i < data.length; i++) {
const entry = data[i];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make this a var as it was?

chunks[i * 2] = entry.chunk;
chunks[i * 2 + 1] = entry.encoding;
}
}
const err = req.handle.writev(req, chunks, allBuffers);

// Retain chunks
if (err === 0) req._chunks = chunks;

if (err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: it should be ok to use an else branch on the previous if statement for this.

return self.destroy(errnoException(err, 'write', req.error), cb);
}

function writeGeneric(self, req, data, encoding, cb) {
const err = handleWriteReq(req, data, encoding);

if (err)
return self.destroy(errnoException(err, 'write', req.error), cb);
}

module.exports = {
createWriteWrap,
writevGeneric,
writeGeneric
};
74 changes: 14 additions & 60 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,17 @@ const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const { Pipe, constants: PipeConstants } = process.binding('pipe_wrap');
const { TCPConnectWrap } = process.binding('tcp_wrap');
const { PipeConnectWrap } = process.binding('pipe_wrap');
const { ShutdownWrap, WriteWrap } = process.binding('stream_wrap');
const { ShutdownWrap } = process.binding('stream_wrap');
const {
newAsyncId,
defaultTriggerAsyncIdScope,
symbols: { async_id_symbol }
} = require('internal/async_hooks');
const {
createWriteWrap,
writevGeneric,
writeGeneric
} = require('internal/stream_base_commons');
const errors = require('internal/errors');
const {
ERR_INVALID_ADDRESS_FAMILY,
Expand Down Expand Up @@ -740,38 +745,15 @@ Socket.prototype._writeGeneric = function(writev, data, encoding, cb) {
return false;
}

var req = new WriteWrap();
req.handle = this._handle;
req.oncomplete = afterWrite;
req.async = false;
var err;

if (writev) {
var allBuffers = data.allBuffers;
var chunks;
var i;
if (allBuffers) {
chunks = data;
for (i = 0; i < data.length; i++)
data[i] = data[i].chunk;
} else {
chunks = new Array(data.length << 1);
for (i = 0; i < data.length; i++) {
var entry = data[i];
chunks[i * 2] = entry.chunk;
chunks[i * 2 + 1] = entry.encoding;
}
}
err = this._handle.writev(req, chunks, allBuffers);

// Retain chunks
if (err === 0) req._chunks = chunks;
} else {
err = createWriteReq(req, this._handle, data, encoding);
}
let ret;
const req = createWriteWrap(this._handle, afterWrite);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make these vars?

if (writev)
ret = writevGeneric(this, req, data, cb);
else
ret = writeGeneric(this, req, data, encoding, cb);

if (err)
return this.destroy(errnoException(err, 'write', req.error), cb);
// Bail out if handle.write* returned an error
if (ret) return ret;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the note above re: changed semantics for http2, I would rather we just test for err and then call this.destroy here rather than inside the helpers. The if (ret) return ret; is super opaque as to what the return value is or its meaning.

Copy link
Member

@apapirovski apapirovski Mar 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since @addaleax requested this related change, I rescind the feedback above. That said, a comment should be left indicating that if (ret) return ret; means there was an error and this.destroy was called with it.

Since this.destroy returns this, the same thing could be communicated without a comment. The variable could be changed to be err and then the code would be if (err) return this;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apapirovski Just for context, I talked with @aks- about this and I don’t think it’s a good permanent solution anyway, but doing more might require some changes to the bytesWritten tracking for net

But yes, a comment here would be very helpful

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there was a suggestion to leave a comment somewhere saying that this line is only run in case of an error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done :D


this._bytesDispatched += req.bytes;

Expand All @@ -794,34 +776,6 @@ Socket.prototype._write = function(data, encoding, cb) {
this._writeGeneric(false, data, encoding, cb);
};

function createWriteReq(req, handle, data, encoding) {
switch (encoding) {
case 'latin1':
case 'binary':
return handle.writeLatin1String(req, data);

case 'buffer':
return handle.writeBuffer(req, data);

case 'utf8':
case 'utf-8':
return handle.writeUtf8String(req, data);

case 'ascii':
return handle.writeAsciiString(req, data);

case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return handle.writeUcs2String(req, data);

default:
return handle.writeBuffer(req, Buffer.from(data, encoding));
}
}


protoGetter('bytesWritten', function bytesWritten() {
var bytes = this._bytesDispatched;
const state = this._writableState;
Expand Down
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
'lib/internal/v8_prof_polyfill.js',
'lib/internal/v8_prof_processor.js',
'lib/internal/vm/Module.js',
'lib/internal/stream_base_commons.js',
'lib/internal/streams/lazy_transform.js',
'lib/internal/streams/async_iterator.js',
'lib/internal/streams/BufferList.js',
Expand Down