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

Implement new writable stream #9

Closed
wants to merge 2 commits into from
Closed

Conversation

ivan-tymoshenko
Copy link
Member

No description provided.

lib/writable.js Outdated
}
}

inherits(Writable, EventEmitter);
Copy link
Member

Choose a reason for hiding this comment

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

class Writable extends EventEmitter at the declaration site?

lib/writable.js Outdated
this.encoding = encoding;
};
class WritableBuffer {
constructor(
Copy link
Member

Choose a reason for hiding this comment

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

Unneeded line break around argument.

lib/writable.js Outdated
constructor(
highWaterMark
) {
this.pointer = 0;
Copy link
Member

Choose a reason for hiding this comment

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

offset?

lib/writable.js Outdated
}

add(chunk) {
const writeSize = this.buffer.length - this.pointer;
Copy link
Member

Choose a reason for hiding this comment

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

remainingSpace, spaceLeft, remainingSize maybe? As writeSize is chunk.length IMO.

lib/writable.js Outdated
highWaterMark = DEFAULT_HIGH_WATERMARK,
defaultEncoding = 'utf-8',
write = null,
writev = null
Copy link
Member

Choose a reason for hiding this comment

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

There arguments should be reversed, the defaults go last.

lib/writable.js Outdated
this.writableLength = 0;
this.doWrite(this.buffer.getData());
};
if (this._writev) this._writev(data, cb);
Copy link
Member

Choose a reason for hiding this comment

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

_writev is used to write an array of data at once, but you pass just one big buffer here IIUC. This won't give any benefits.

lib/writable.js Outdated
this.writing = false;
return;
}
this.writableLength = 0;
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 this length should be tracked in your WritableBuffer instance.

lib/writable.js Outdated
this.emit('close');

const err = new Error('[ERR_STREAM_DESTROYED]');
this.write = (...args) => {
Copy link
Member

Choose a reason for hiding this comment

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

Why don't you just set destroyed and check it in the write function, I don't think it's worth overriding a function for such a use case.

lib/writable.js Outdated
}

const err = new Error('[ERR_STREAM_WRITE_AFTER_END]');
this.write = (...args) => {
Copy link
Member

Choose a reason for hiding this comment

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

Same as the comment in destroy.

lib/writable.js Outdated
process.nextTick(cb, err);
};

this.emit('finish');
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 'finish' should only be emitted after all of the data has been sent to the underlying system. (i.e. similar to https://nodejs.org/api/stream.html#stream_event_finish)

@ivan-tymoshenko
Copy link
Member Author

@lundibundi please, reviewe after refactoring.

@ivan-tymoshenko
Copy link
Member Author

@lundibundi please reviewe.

lib/writable.js Outdated
add(chunk) {
const remainingSize = this.buffer.length - this.offset;

if (chunk.length < this.remainingSize) {
Copy link
Member

Choose a reason for hiding this comment

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

<=

lib/writable.js Show resolved Hide resolved
lib/writable.js Outdated Show resolved Hide resolved
lib/writable.js Outdated
const currentBufferChunk = chunk.slice(0, this.remainingSize);
this.writeChunk(currentBufferChunk);
this.queue.push(this.buffer);
this.buffer = Buffer.alloc(this.buffer.length);
Copy link
Member

Choose a reason for hiding this comment

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

this.buffer = Buffer.alloc(this.highWaterMark);

lib/writable.js Outdated
}

if (typeof cb !== 'function') {
cb = () => {};
Copy link
Member

@tshemsedinov tshemsedinov Nov 1, 2018

Choose a reason for hiding this comment

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

Use common.safeCallback or common.once. But safeCallback takes array args so we may need safeFunction:

const safeFunction = fn => typeof fn === 'function' ? fn : emptiness;

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you want to add a safeFunction to a stream library or to common?

Copy link
Member

Choose a reason for hiding this comment

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

yes

Copy link
Member

@lundibundi lundibundi Nov 1, 2018

Choose a reason for hiding this comment

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

Why does this assignment is even needed? Can't you just add
if (cb) ... on line 117 and later on in this function?
Also, IMO having empty callback here is harmful as

  • it will grow the callbacks array needlessly
  • you will have it scheduled on nextTick in case of error process.nextTick(cb, error);
  • it makes passing errors to the callback harder as you may accidentally pass an error to the empty function thus ignoring it.

lib/writable.js Outdated
} else {
this._write(data, cb);
}
callbacks.forEach(cb => cb());
Copy link
Member

Choose a reason for hiding this comment

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

This should be run in the cb as callback call means that the data has been written, not that it was passed to the underlying backend (flushed). (you can check out node docs or _stream_writable/doWrite & _stream_writable/onwrite).

@lundibundi
Copy link
Member

Also, @bugagashenkj could you add some tests?

@ivan-tymoshenko ivan-tymoshenko force-pushed the new-writable-stream branch 3 times, most recently from 9207dfd to 2afbe12 Compare November 5, 2018 09:36
@ivan-tymoshenko
Copy link
Member Author

@lundibundi please reviewe.

lib/writable.js Outdated

writeChunk(chunk, encoding) {
if (typeof chunk === 'string') {
this.buffer.write(chunk, this.offset, encoding);
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 this can write more than chunk.length due to the encoding. You have to use the return value of the write call.

lib/writable.js Outdated
this.buffer = Buffer.alloc(highWaterMark);
}

writeChunk(chunk, encoding) {
Copy link
Member

Choose a reason for hiding this comment

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

This should probably be hidden under Symbol.

lib/writable.js Show resolved Hide resolved
lib/writable.js Show resolved Hide resolved
lib/writable.js Outdated
this.defaultEncoding = encoding;
}

error(err, cb) {
Copy link
Member

Choose a reason for hiding this comment

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

Also, private?

lib/writable.js Outdated

if (this.destroyed) {
const err = new Error('[ERR_STREAM_DESTROYED]');
this.emit(err, cb);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
this.emit(err, cb);
this.error(err, cb);

?

lib/writable.js Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
test/writable.js Show resolved Hide resolved
test/writable.js Outdated
metatests.testSync('Writable / destroy', test => {
const [stream, ] = createStream(5);

stream.on('error', err => { test.isError(err); });
Copy link
Member

@lundibundi lundibundi Nov 5, 2018

Choose a reason for hiding this comment

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

You should use test.plan in this test.

@ivan-tymoshenko ivan-tymoshenko force-pushed the new-writable-stream branch 2 times, most recently from d301605 to ca1dbd4 Compare November 5, 2018 12:03
lib/writable.js Outdated
this.buffer = Buffer.alloc(highWaterMark);
}

[writeChunkFuncName](chunk) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I think writeChunkSymbol will be clearer.

lib/writable.js Outdated

uncork() {
if (this.corked) this.corked--;
if (!this.corked && this.writableLength) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (!this.corked && this.writableLength) {
if (this.corked === 0 && this.writableLength) {

Also, you should also add check to avoud this.corked become <0.

lib/writable.js Outdated
this[errorFuncName](err);
}

validChunk(chunk, cb) {
Copy link
Member

Choose a reason for hiding this comment

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

Also private?

lib/writable.js Outdated Show resolved Hide resolved
lib/writable.js Outdated
};

if (this._writev) {
if (Array.isArray(data) && data.length > 1) this._writev(data, cb);
Copy link
Member

Choose a reason for hiding this comment

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

I don't think there is any merit in checking data.length > 1.

lundibundi
lundibundi previously approved these changes Nov 5, 2018
Copy link
Member

@lundibundi lundibundi left a comment

Choose a reason for hiding this comment

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

LGTM with a few nits.
Should we add/make benchmarks here or land and then work on those?
/cc @tshemsedinov

lib/writable.js Outdated
this.buffer = Buffer.alloc(highWaterMark);
}

[writeChunkSymbol](chunk) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: actually, you don't export this class, so there is no need ti hide it's methods behind the symbols, though that's up to you.

lib/writable.js Outdated
this.defaultEncoding = encoding;
}

[errorFuncName](err, cb) {
Copy link
Member

Choose a reason for hiding this comment

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

errorSymbol or errorFuncSymbol?

lib/writable.js Outdated
}

if (this.ended) {
const err = new Error('[ERR_STREAM_WRITE_AFTER_END]');
Copy link
Member

@lundibundi lundibundi Nov 5, 2018

Choose a reason for hiding this comment

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

Forgot about errors.

Why do you have errors as strings? Those should be potentially be separate error classes and each have unique code.

@lundibundi lundibundi dismissed their stale review November 5, 2018 22:33

Forgot about errors.

@ivan-tymoshenko
Copy link
Member Author

@lundibundi please review.

ERR_OUT_OF_RANGE
} = require('./errors');

const { FSReqCallback, writeBuffers } = process.binding('fs');
Copy link
Member

Choose a reason for hiding this comment

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

process.binding is deprecated in node 11 and will most likely be removed in node 12
nodejs/node#22478 and
https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V11.md#2018-10-23-version-1100-current-jasnell

General
Use of process.binding() has been deprecated. Userland code using process.binding() should re-evaluate that use and begin migrating. If there are no supported API alternatives, please open an issue in the Node.js GitHub repository so that a suitable alternative may be discussed.

Could you post the results of benchmarks with and without using those methods?

@ivan-tymoshenko
Copy link
Member Author

r

 chunkLength     kind     rate confidence.interval
           8 metarhia 11606494           229144.66
           8     node  3491421            82066.44
          64 metarhia  6354815           288684.74
          64     node  3114953            98569.04
         128 metarhia  4313109           174982.62
         128     node  2867002            80028.80

https://gist.github.com/bugagashenkj/a0f7cd4929d45e0c03ee11e19b3a87cf

@ivan-tymoshenko
Copy link
Member Author

@lundibundi please review.

lib/fs-writable.js Outdated Show resolved Hide resolved
lib/fs-writable.js Outdated Show resolved Hide resolved
lib/writable.js Outdated Show resolved Hide resolved
lib/writable.js Outdated Show resolved Hide resolved
@ivan-tymoshenko
Copy link
Member Author

ivan-tymoshenko commented Nov 22, 2018

@lundibundi

lib/fs-writable.js Outdated Show resolved Hide resolved
lib/fs-writable.js Outdated Show resolved Hide resolved
lib/fs-writable.js Outdated Show resolved Hide resolved
lib/fs-writable.js Outdated Show resolved Hide resolved
lib/fs-writable.js Outdated Show resolved Hide resolved
lib/fs-writable.js Outdated Show resolved Hide resolved
lib/fs-writable.js Outdated Show resolved Hide resolved
lib/writable.js Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
test/fs-writable.js Outdated Show resolved Hide resolved
}

fs.write(this.fd, data, 0, data.length, this.pos, (error, bytes) => {
if (error) this[errorSymbol](error, cb);
Copy link
Member

Choose a reason for hiding this comment

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

Missed return?

er = er || err;
cb(er);
this.closed = true;
if (!er) this.emit('close');
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason behind not emitting 'close' here but still emitting it in case of error in [errorSymbol]?

this.emit('error', error);
this.emit('close');
}
if (cb) cb(error);
Copy link
Member

Choose a reason for hiding this comment

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

Now that I think about it, the cb should be callback for an operation that 'erroed' so IMO it should be called at the same time or before 'error' event.
SO this should probably be

    if (cb) cb(error);
    if (this.autoClose) {
      this.destroy(error);
    } else {
      this.emit('error', error);
      this.emit('close');
    }

});
}

[closeSymbol](cb) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this method unused?

lib/fs-writable.js Outdated Show resolved Hide resolved
lib/writable.js Outdated

uncork() {
if (this.corked > 0) this.corked--;
if (this.corked === 0 && this.writableLength) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to be explicit

Suggested change
if (this.corked === 0 && this.writableLength) {
if (this.corked === 0 && this.writableLength > 0) {

ivan-tymoshenko added a commit that referenced this pull request Dec 18, 2018
ivan-tymoshenko added a commit that referenced this pull request Dec 19, 2018
Copy link
Member

@tshemsedinov tshemsedinov left a comment

Choose a reason for hiding this comment

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

I'd prefer callback instead of cb in most cases except those where we have two callbacks in enclosed contexts

lib/errors.js Outdated
@@ -0,0 +1,68 @@
'use strict';

class ERR_INVALID_ARG_TYPE extends TypeError {
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer UpperCamelCase InvalidArgTypeError

lib/writable.js Outdated
}
encoding = encoding.toLowerCase();
if (!Buffer.isEncoding(encoding))
throw new ERR_UNKNOWN_ENCODING(encoding);
Copy link
Member

Choose a reason for hiding this comment

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

    if (!Buffer.isEncoding(encoding))
      throw new ERR_UNKNOWN_ENCODING(encoding);
    }

lib/writable.js Outdated
if (this.ending) {
this.ended = true;
if (this._final) {
this._final(() => this.emit('finish'));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
this._final(() => this.emit('finish'));
this._final(() => {
this.emit('finish');
});

lib/writable.js Outdated
this.callbacks = [];

const cb = () => {
callbacks.forEach(cb => cb());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
callbacks.forEach(cb => cb());
callbacks.forEach(callback => {
callback();
});

lib/writable.js Outdated
return this.buffer.writableLength;
}

_destroy(err, cb) { cb(err); }
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
_destroy(err, cb) { cb(err); }
_destroy(err, cb) {
cb(err);
}

});

module.exports = submodules;
module.exports = Object.assign({}, ...submodules);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
module.exports = Object.assign({}, ...submodules);
module.exports = { ...submodules };

Copy link
Member Author

Choose a reason for hiding this comment

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

I think these two implementations are doing different things.

package.json Outdated
@@ -25,9 +25,6 @@
"engines": {
"node": ">=6.0.0"
Copy link
Member

Choose a reason for hiding this comment

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

    "node": ">=8.6.0"

Copy link
Member

Choose a reason for hiding this comment

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

In separate commit before this

ivan-tymoshenko added a commit that referenced this pull request Jan 11, 2019
fs.close(this.fd, (er) => {
er = er || err;
callback(er);
this.closed = true;
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 we should set this.closed = true; before calling a callback so that the callback will see a correct state.

@ivan-tymoshenko
Copy link
Member Author

/cc @lundibundi

lundibundi pushed a commit that referenced this pull request Jan 22, 2019
lundibundi pushed a commit that referenced this pull request Jan 22, 2019
@lundibundi
Copy link
Member

Landed in b5d195f and 1f4e659.

@lundibundi lundibundi closed this Jan 22, 2019
@lundibundi lundibundi deleted the new-writable-stream branch January 22, 2019 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants