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

Cross library interleaving can cause node 10's nextTick limit to be hit. #337

Merged
merged 2 commits into from Dec 6, 2014

Conversation

taras
Copy link
Contributor

@taras taras commented Dec 5, 2014

On Node 0.10.33, using RSVP causes deprecation error: (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral.

This issue seems to appear and disappear under strange circumstances:

This PR replaces process.nextTick with setImmediate. All tests pass.

@stefanpenner
Copy link
Collaborator

nextTick actually isn't deprecated and node 11 has removed this limit. Although setImmediate may not be correct here, we may need to use it in node 10..

@stefanpenner
Copy link
Collaborator

cc @domenic

@stefanpenner
Copy link
Collaborator

So the issue you see is multiple different RSVP's interacting with each other. This could also be caused by multiple different promises libraries interacting with each other in specific ways.

var A = require('rsvp1').Promise;
var B = require('rsvp2').Promise;

A.resolve().then(function() {
  console.log('first nextTick');
  B.resolve().then(function() {
    console.log('second nextTick');
    A.resolve().then(function() {
      console.log('third nextTick');
    });
  });
});

@taras ^^ if looped 500 or so times will cause the error to happen, likely a great test case to add.

this problem can happen quickly in for example broccoli were depending on the topology of node_modules, we can easily have a large number of rsvp's floating around

@stefanpenner
Copy link
Collaborator

I wonder actually if joliss/promise-map-series#4 may mitigate this problem.

I'll have to investigate the exact scenario in broccoli in broccoli that builds this specific problem

@stefanpenner
Copy link
Collaborator

via @domenic

in node 11:

nodejs/node@0761c90
nodejs/node@0761c90

@stefanpenner
Copy link
Collaborator

@stefanpenner
Copy link
Collaborator

@taras i have a test scenario you can implement.

@stefanpenner stefanpenner reopened this Dec 5, 2014
@taras
Copy link
Contributor Author

taras commented Dec 5, 2014

@stefanpenner got it, I'll work on that.

@stefanpenner
Copy link
Collaborator

@taras thank you sir :)

@stefanpenner
Copy link
Collaborator

btw the sky is falling :P

@@ -24,7 +24,7 @@ var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
// node
function useNextTick() {
return function() {
process.nextTick(flush);
setImmediate(flush);
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets do this only if node is < 0.11.0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This deprecation was introduced in 0.10.0. Should I make this change for 0.10.x?

@taras
Copy link
Contributor Author

taras commented Dec 5, 2014

It took me too long to figure out that "the sky is falling" means that it's time sensitive :) I'm working on it now.

@taras
Copy link
Contributor Author

taras commented Dec 6, 2014

I'm having a hard time replicating the error with your code example. Can you give me a gist with code that you used to cause the deprecation to show up?

@stefanpenner stefanpenner self-assigned this Dec 6, 2014
@taras
Copy link
Contributor Author

taras commented Dec 6, 2014

@stefanpenner I pushed a bit of WIP code to show you what I've been trying.

Note: I'm manually copying the dist/rsvp.js to dist/rsvp-2.js to give me another module to work with.

@stefanpenner
Copy link
Collaborator

var A = require('rsvp1').Promise;

A.resolve().then(function() {
  console.log('first nextTick');
   process.nextTick(function() { // <-- should be sufficient i think
    A.resolve().then(function() {
      console.log('third nextTick');
    });
  });
});

@taras
Copy link
Contributor Author

taras commented Dec 6, 2014

Here is the test that I tried, but I wasn't able to get it show the deprecation warning.

describe("on node 0.10.x, using process.nextTick recursively shows deprecation warning. setImmediate should be used instead.", function() {
  // (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral.

  specify("should not throw an error", function (done) {
    var promises = [];

    for (var i = 0; i < 1000; i++) {
      var promise = new Promise(function(resolve){
        RSVP.resolve().then(function() {
          process.nextTick(function() { // <-- should be sufficient i think
            RSVP.resolve().then(function() {
              process.nextTick(function(){
                RSVP.resolve().then(function() {
                  resolve();
                });
              })
            });
          });
        });
      });
      promises.push(promise);
    }

    RSVP.all(promises)
        .then(function(){
          done();
        });
  });

});

@stefanpenner
Copy link
Collaborator

ya that inner tree needs to have a depth of 1000, not repeating the whole thing 1000 times.

@taras
Copy link
Contributor Author

taras commented Dec 6, 2014

Got it.

The following code causes warnings to be displayed.

describe("on node 0.10.x, using process.nextTick recursively shows deprecation warning. setImmediate should be used instead.", function() {
  // (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral.

  specify("should not throw an error", function (done) {
    var depth = 0;
    var nextTick = function(promise) {
      if (depth < 500) {
        promise.then(function(){
          process.nextTick(function(){
            console.log('nextTick: ' + depth);
            depth++;
            nextTick(promise);
          })
        });
      }
      return promise;
    };

    nextTick(RSVP.resolve())
        .then(function(){
          done();
        });
  });
});

When I apply this code to async.js, the warnings go away.

function useNextTick() {
  var nextTick = process.nextTick;
  // node version 0.10.x displays a deprecation warning when nextTick is used recursively
  // setImmediate should be used instead instead
  var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);
  if (Array.isArray(version) && version[1] === '0' && version[2] === '10') {
    nextTick = setImmediate;
  }
  return function() {
    nextTick(flush);
  };
}

Now, I gotta figure out how to capture output.

@taras
Copy link
Contributor Author

taras commented Dec 6, 2014

I don't need to capture output to get deprecation errors because that test will cause RangeError: Maximum call stack size exceeded when setImmediate is not used.

If that's ok, then you can look at the code.

@taras
Copy link
Contributor Author

taras commented Dec 6, 2014

Grr... it looks like the test doesn't run 500 times with setImmediate. I'm going to look into it further. Sorry.

- Added test that causes Maximum call stack size exeeded error in node

Added test that causes recursion
@taras
Copy link
Contributor Author

taras commented Dec 6, 2014

@stefanpenner ok, I got it sorted out. I verified that my new test code creates a recursion 1000 levels deep. With original code, the test fails because an exception is thrown. The tests passes on the new code.

I also added node 0.11.x to travis.

@stefanpenner
Copy link
Collaborator

@taras awesome this looks fantastic. I really appreciate you digging into this!

stefanpenner added a commit that referenced this pull request Dec 6, 2014
Replaced deprecated process.nextTick with setImmediate
@stefanpenner stefanpenner merged commit 69714a9 into tildeio:master Dec 6, 2014
@stefanpenner stefanpenner changed the title Replaced deprecated process.nextTick with setImmediate Cross library interleaving can cause node 10's nextTick limit to be hit. Dec 6, 2014
@taras
Copy link
Contributor Author

taras commented Dec 6, 2014

It's my pleasure. Glad I could help out.

@soulcutter
Copy link

Great tag team work, big ups for getting to the bottom of this.

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.

None yet

4 participants