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

zlib.*Sync methods fire asynchorous callbacks #1668

Closed
ChALkeR opened this issue May 10, 2015 · 18 comments
Closed

zlib.*Sync methods fire asynchorous callbacks #1668

ChALkeR opened this issue May 10, 2015 · 18 comments
Labels
memory Issues and PRs related to the memory management or memory footprint. zlib Issues and PRs related to the zlib subsystem.

Comments

@ChALkeR
Copy link
Member

ChALkeR commented May 10, 2015

https://github.com/iojs/io.js/blob/v2.0.1/lib/zlib.js#L458 — this line is called even for *Sync methods. That's not the only one.

See #1479 and #1665 (comment)

@ChALkeR
Copy link
Member Author

ChALkeR commented May 10, 2015

@trevnorris

@mscdex mscdex added the zlib Issues and PRs related to the zlib subsystem. label May 10, 2015
@ChALkeR
Copy link
Member Author

ChALkeR commented May 10, 2015

@Qard It's not the garbage collector that is broken as you supposed in #1665 (comment), it's zlib.*Sync methods not working as expected.

@ChALkeR
Copy link
Member Author

ChALkeR commented May 10, 2015

That's since 07d3b21, I suppose.
I might be wrong, I haven't tested that commit.
@isaacs

@Qard
Copy link
Member

Qard commented May 10, 2015

I didn't say garbage collection was broken, I said your expectations of it were incorrect and that there needed to be documentation to explain that.

In this case, the objects are not collectable simply because they are still accessible by future code--the close emitting function. That can be fixed.

JavaScript in general is not a good language for sync looping though, as you need to trigger GC manually and hope that enough things are collectable to not rapidly run out of memory.

@trevnorris
Copy link
Contributor

@ChALkeR Can you give me a code example where that manifests itself as a problem?

@ChALkeR
Copy link
Member Author

ChALkeR commented May 10, 2015

@trevnorris I initially posted that to the issue: #1665 (comment) has the code sample, #1479 is the original report.

@targos commented 20 days ago
I was running a script to read sequentially a lot of gzipped files, and print on stdout the result of a computation for each file.
After about 16'000 files, the process just stopped and Killed was printed on the terminal.

@trevnorris
Copy link
Contributor

@ChALkeR Thanks. IMO it's a bug.

@ChALkeR
Copy link
Member Author

ChALkeR commented May 11, 2015

@indutny On a side note, zlib.* methods (both sync and async) are collected only during full GC runs. Could something be done with that?
Edit: It's Buffers fault: #1671.

@ChALkeR
Copy link
Member Author

ChALkeR commented May 17, 2015

@bnoordhuis
But the reason to defer the event is to prevent infinite recursion. I'm not sure if this is fixable without breaking something else.

Is that the case for *Sync methods? Will it be safe not to defer the event for *Sync methods?

@bnoordhuis
Copy link
Member

Is that the case for *Sync methods?

Yes. Events always need to be asynchronous. The problem in a nutshell:

// example 1
function next() {
  var t = task();
  t.on('complete', next);
}

// example 2
function next() {
  task(next);
}

In the first example, if 'complete' is synchronous, you don't have time to attach an event listener.

In the second example, if the callback is synchronous, you get infinite recursion.

@ChALkeR
Copy link
Member Author

ChALkeR commented May 17, 2015

@bnoordhuis But those events are not public in *Sync case and can't be used outside of the core code. I am not even sure if they are needed in the *Sync case.

@trevnorris
Copy link
Contributor

@bnoordhuis just curious, is there ever a time where a synchronous task allows adding event listeners?

@piscisaureus
Copy link
Contributor

@bnoordhuis just curious, is there ever a time where a synchronous task allows adding event listeners?

It happens in other places too (e.g. fs.createSyncReadStream(), process.stdout).
It pretty much always causes problems, and I think it's a broken pattern that we shouldn't use.

@trevnorris
Copy link
Contributor

@piscisaureus Sorry, I should have clarified the question. Is there ever a time where the public API allows attaching events to a synchronous task?

@bnoordhuis
Copy link
Member

I'm not aware of any such APIs.

@trevnorris
Copy link
Contributor

@bnoordhuis Thanks. Just wanted to confirm that the reason this can't be done is because of our own internal way of handling these events.

So, it seems at some point this should be fixed. Not volunteering for it, and it would probably help to have a discussion about it before anyone actually got started.

@ChALkeR ChALkeR added the memory Issues and PRs related to the memory management or memory footprint. label Aug 17, 2015
Trott added a commit to Trott/io.js that referenced this issue Mar 15, 2016
WIP, probably less desirable than the other proposed fix, which also
might be undesirable, but letting people comment just in case.

Still needs a test.

Refs: nodejs#1668
@Trott
Copy link
Member

Trott commented Mar 15, 2016

For the specific problem demonstrated with @ChALkeR's test code:

'use strict';

var zlib = require('zlib');
var data = 'abcdefghijklmnopqrstuvwxyz';
var gzipped = zlib.gzipSync(data);

var time = process.hrtime();
var count = 0, limit = 800000, block = 20000;

function call() {
    var contents = zlib.gunzipSync(gzipped);
    count++;
    if (count % block === 0) {
        var t = process.hrtime(time);
        console.log(count + ': ' + block / (t[0] + t[1] / 1e9) + ' per second ' + JSON.stringify(process.memoryUsage()));
        time = process.hrtime();
    }
}

for (var i = 0; i < limit; i++) {
    call();
}

This appears to be fixed by just getting rid of the mechanism to emit close events altogether on the internal-ish Zlib object. It is not used internally and it is not documented. Still, that would probably be a semver-major move and perhaps an undesirable one? #5708

Another option is to add an options object as a parameter to (again, internal-ish) Zlib.prototype.close() and set an option indicating that the thing is invoked by a *Sync() function (and thus no event should be queued). Effective but perhaps too inelegant? #5707

@Trott
Copy link
Member

Trott commented Mar 17, 2016

I believe #5707 is a fix for this. PTAL

Trott added a commit to Trott/io.js that referenced this issue Mar 19, 2016
WIP right now, still need to do it for more methods than just
gunzipSync().

Refs: nodejs#1668
@Trott Trott closed this as completed in 8b43d3f Mar 19, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
memory Issues and PRs related to the memory management or memory footprint. zlib Issues and PRs related to the zlib subsystem.
Projects
None yet
Development

No branches or pull requests

7 participants