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 Readable instead of Stream #740

Merged
merged 1 commit into from
May 3, 2017

Conversation

zbjornson
Copy link
Collaborator

Would benefit from some testers (@mitar?) and feedback...

Both JPEGStream and PNGStream currently inherit from stream.Stream, not stream.Readable. As a consequence, they are missing a bunch of methods. (See #674, #232.) They will also start flowing without a listener, but because the stream is wrapped in process.nextTick I think that would only be noticed from the node prompt.

It was straightforward to switch to Readable. I didn't attempt to make the C++ code wait to queue more data after the stream starts (first call to .read); after the first chunk it pushes data into the stream queue without waiting for the stream consumer to call .read (directly or indirectly). There should be no lost bytes as a result of flowing without a sink anymore, but consumers could see more memory consumption from the queuing of bytes that previously would have been lost into the ether.

I'm still confused about the "async" vs "sync" names of these APIs -- they all emit chunks of data within callbacks, which seems async to the consumer, if not internally.

@@ -749,6 +749,7 @@ describe('Canvas', function () {
it('Canvas#createSyncPNGStream()', function (done) {
var canvas = new Canvas(20, 20);
var stream = canvas.createSyncPNGStream();
assert(stream instanceof require("stream").Readable);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Single quotes please :)

@LinusU
Copy link
Collaborator

LinusU commented Mar 21, 2016

Awesome 🙌 I've wanted to do this myself for quite some time, I think that this looks very good.

I think the point with "async" vs "sync" is that "sync" is being run on the main thread, although calling back to javascript land in chunks? Haven't looked at the code properly to say though.

In my opinion we shouldn't have sync streams at all since that seems like a very weird concept.

@zbjornson zbjornson force-pushed the stream-polishing branch 3 times, most recently from dbee175 to 871f010 Compare March 21, 2016 20:07
@zbjornson
Copy link
Collaborator Author

Thanks!

  1. I forgot that node 0.8 doesn't have stream.Readable to extend. @rvagg has a nice article about compatibility here: https://r.va.gg/2014/06/why-i-dont-use-nodes-core-stream-module.html. Drop 0.8 or make it compatible?
  2. Apparently 0.10 will miss the 'end' event if it is attached after the 'data' event is attached -- or at least, when I re-ordered the event listener attachments in the toDataURL method and tests, it worked. This could be breaking for consumers. I don't see anything about this in the 0.10 docs, and some quick googling didn't reveal any relevant bugs that were fixed in 0.12. https://nodejs.org/docs/v0.10.43/api/stream.html#stream_compatibility_with_older_node_versions.

@@ -749,26 +749,35 @@ describe('Canvas', function () {
it('Canvas#createSyncPNGStream()', function (done) {
var canvas = new Canvas(20, 20);
var stream = canvas.createSyncPNGStream();
assert(stream instanceof require('stream').Readable);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you move the require call to the top of the file? You could still do var Readable = require('stream').Readable

@LinusU
Copy link
Collaborator

LinusU commented Mar 25, 2016

Hmm

I don't have a problem with dropping 0.8 support, according to this tweet and conversation, almost no one is using 0.8 anymore. https://twitter.com/seldo/status/712386414902521861

screen shot 2016-03-25 at 13 40 20

It should be a major bump though.

The problems on 0.10 is a bit disturbing. Would it be possible for us to delay the first data event until the next tick?

@calvinmetcalf
Copy link

Maybe the data event is causing the stream to synchronously drain before you get around to putting the end listener.

@mcollina
Copy link

I agree with @calvinmetcalf. In streams 2, if all the data is available in JS-land and a 'data' handler is added, it would be completely synch, and you will add the 'end' event after 'end' was already happened. Does createSyncPNGStream create all the data synchronously?

@zbjornson
Copy link
Collaborator Author

@calvinmetcalf and @mcollina that appears to be what's happening -- thanks.

I was hoping to find documentation about this change between 0.10 and 0.12 just to confirm that I wasn't doing something wrong. I think this is the diff that caused the behavior change:
nodejs/node-v0.x-archive@0f8de5e#diff-ba6a0df0f5212f5cba5ca5179e209a17L681
Notice .resume calls through a nextTick, whereas the removed emitDataEvents looks like it plowed straight through.

Anyway, wrapping it in a nextTick now...

@zbjornson
Copy link
Collaborator Author

From nodejs/node-v0.x-archive#5865 (comment)

The other semantic change is that adding a readable event handler will not cause readable to be emitted until nextTick

Indeed.

@zbjornson
Copy link
Collaborator Author

Passing in 0.10 and later. Undid the changes to toDataUrl and tests.

Node 5 doesn't seem to need to be an allowed failure anymore, btw.

process.nextTick(function(){
canvas[method](options.bufsize, options.quality, options.progressive, function(err, chunk){
if (err) {
self.emit('error', err);
Copy link
Collaborator

Choose a reason for hiding this comment

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

wrong indentation

});
});
}
};

Choose a reason for hiding this comment

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

allocating read here or in the prototype has few differences, apart from style. I prefer to use prototype when dealing with a class an inheriting.

Regarding this block, I would consider removing the process.nextTick  call. Plus, I think it's possible to recycle (declare inside the constructor) the function passed to canvas[method].

The golden rule is "try not to allocate functions inside _read, _write or _transform". That is usually a very hot path and all those function allocation will slow you down.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

From earlier, we must use process.nextTick.

Sorry for not being more clear, or for still not understanding what you're saying, but this is not a hot path because it has the surrounding if block that causes evaluation of the function expression exactly once, thus there is nothing to reuse. I think then the only difference is style, but please let me know if I'm missing something.

As far as size of npm/inherits vs util.inherits, this is a node module only.

@mcollina
Copy link

If you care about browser size, I suggest you using inherits from NPM instead of util.inherits.

@zbjornson zbjornson force-pushed the stream-polishing branch 2 times, most recently from ac615b7 to 4cacbe8 Compare May 10, 2016 18:24
@zbjornson
Copy link
Collaborator Author

Updated travis.yaml: remove node 0.8, add 6.x and no longer allow 5.x to fail. CI green!

@zbjornson
Copy link
Collaborator Author

(Needs update since PDF stream was added)

@LinusU
Copy link
Collaborator

LinusU commented Oct 23, 2016

@zbjornson sorry for the delay on this, I've now cut a 1.x branch from master and from now on master is for Canvas 2.x. I'm dropping 0.8 support in #825 so this should be good to merge 👍

@zbjornson
Copy link
Collaborator Author

No problemo, will fix conflicts and add PDF stream this week. :)

@zbjornson
Copy link
Collaborator Author

@LinusU updated :)

@chearon
Copy link
Collaborator

chearon commented Oct 31, 2016

I do think the comments by @mcollina deserve another look, can _read be defined on the prototype? If we drop 0.10, does that mean the nextTick stuff can be removed? It seems like it is not an issue in streams3

@zbjornson
Copy link
Collaborator Author

If 0.10 is dropped, nextTick can be removed, yes.

I had _read in the ctor so that hasStarted is not visible, but that's a small reason. There's no difference in stream performance, only in stream construction:

  • As it was: construct 1,430,136 ops/sec, end event 576 ops/sec
  • As it was with guard clause reversed (if (hasStarted) return): 1,415,082 & 573
  • On proto: 8,442,640 & 574
  • On proto, reassign _read = noop on first _read call: 8,452,849 & 580
  • nextTick removed: no change

The 4th option (reassign to noop) is the best of both worlds -- no internal properties exposed and fast construction. Updated PR to use that method.

@zbjornson
Copy link
Collaborator Author

@LinusU gentle nudge, from my side this is ready for merge into 2.0. :)

@LinusU
Copy link
Collaborator

LinusU commented Nov 22, 2016

Sorry for the delay, I hope to get some time this week 👍

@LinusU
Copy link
Collaborator

LinusU commented May 3, 2017

Sorry for the delay, awesome work!

@LinusU LinusU merged commit 382fb07 into Automattic:master May 3, 2017
@zbjornson zbjornson deleted the stream-polishing branch May 3, 2017 21:59
@zbjornson
Copy link
Collaborator Author

TODO at myself (or anyone else who wants to do it): since 0.10 is dropped now, the nextTick stuff can be removed, per discussion above.

zbjornson added a commit to zbjornson/node-canvas that referenced this pull request May 4, 2017
zbjornson added a commit to zbjornson/node-canvas that referenced this pull request Jun 20, 2017
zbjornson added a commit to zbjornson/node-canvas that referenced this pull request Jun 20, 2017
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.

6 participants