I've got a strange problem with an app running on NodeJS, which I'd 
appreciate any help with.

The problem manifests as either TypeError trying to access objects that 
haven't been initialized consistently, or AssertionError due to asserts in 
my code finding violations of invariants that should hold -- in both cases, 
it looks like the code was invoked with other data structures in an 
inconsistent state. This could just be a bug in my code, sure, except for a 
peculiarity with _tickCallback getting called seemingly out of nowhere as 
you'll see below.

My understanding of the NodeJS concurrency model is that what's going on 
here should be impossible -- I'd explain the concurrency model as, there's 
only one thread and no preemption, scheduling happens only at the level of 
the event loop, synchronous code runs until it decides to yield by 
returning to the event loop, so the only boundary where code needs to worry 
about data structures changing out from under it is when scheduling a 
callback asynchronously (with a trip back to the event loop dispatch 
mechanism in between). Or said more shortly, every synchronous invocation 
is essentially a critical section until it returns all the way back to 
Node's event loop.

But then I get an exception with a call stack like:

TypeError: Cannot call method 'end' of undefined
      at Request.end 
(/opt/buildbot-slave/tree/build/node_modules/request/request.js:1320:12)
      at 
/opt/buildbot-slave/tree/build/node_modules/request/request.js:418:14
      at process._tickCallback (node.js:415:13)
      at 
/opt/buildbot-slave/tree/build/node_modules/request/lib/copy.js:5:10
      at Array.forEach (native)
      at copy 
(/opt/buildbot-slave/tree/build/node_modules/request/lib/copy.js:4:20)
      at Request.start 
(/opt/buildbot-slave/tree/build/node_modules/request/request.js:668:20)
      at Request.end 
(/opt/buildbot-slave/tree/build/node_modules/request/request.js:1319:28)
      at Object.exports.request 
(/opt/buildbot-slave/tree/build/node_modules/soap/lib/http.js:55:11)

Note the call to process._tickCallback in the middle of this stack trace, 3 
frames down.

Is this actually legal and possible? According to my understanding of the 
NodeJS concurrency model (see above), no; if _tickCallback can be called 
from arbitrary points (and then dispatch into other arbitrary code), it 
seems like this would break the world. But I haven't found any official 
documentation that explicitly rules this out.

I see 3 possibilities:

- this is happening and is legal: then I need to completely rethink my 
understanding of data structure consistency in NodeJS.
- this is happening and is not legal: through what bug is it happening, 
then?
- it's not actually happening and the call stack is a lie: but then I need 
some other explanation for the data structure inconsistency that my 
assertions are observing.

Clearly, none of these possibilities is attractive :)

For what it's worth, the code at node_modules/request/lib/copy.js:5:10 
which is alleged to have triggered a call to _tickCallback is the second 
line of

  Object.keys(obj).forEach(function (i) {
    o[i] = obj[i]

that is, just a property lookup against a JS object.

I did dig into the NodeJS source trying to find all callers of 
_tickCallback; it seems mostly it's used by the C++ code in MakeCallback() 
which calls it every time after invoking the JS code "callback". 
MakeCallback has a ton of callers, both direct and via a mechanism where 
it's registered with libuv as uv_check_immediate callback; I think I read 
somewhere that MakeCallback is how native code always invokes JS code, and 
so nextTick callbacks are processed (by running process._tickCallback) on 
every transition from JS code back to native code.

So my question: is it possible for _tickCallback to be called (and then 
dispatch into arbitrary JS code, whatever was registered as a nextTick 
callback) from arbitrary JS code, and under what conditions? If it's true 
that we invoke _tickCallback via MakeCallback() when leaving JS code back 
to native code, does this happen only when the entire synchronous execution 
(driven by the last async event) is done and we're going back to sleep in 
the event loop, or also if the JS code calls some C++ code in a native-code 
extension which itself calls back into JS code (so then unwinding, we go 
from JS to C++ but not back to the event loop; we have more JS to run still 
on the stack as we unwind; does this scenario also invoke MakeCallback and 
possibly _tickCallback)?

Or am I overthinking all this; my initial hunch that _tickCallback should 
never show up in the middle of a call stack was true, and this is just a 
bug that shouldn't happen?

I'd appreciate knowing where to concentrate my search to understand this 
better.

I'm running on Node stable releases (0.10.x; I think I've seen this 
behavior since 0.10.16 and it continues in 0.10.26; I haven't tried newer 
versions yet). I am using the node-fibers module [1] but I don't think it's 
implicated here; fibers have to choose to yield, and if they do yield then 
either we go back to the event loop or another fiber starts running from 
where it left off; any such fiber interactions would be visible in the call 
stack; it should be easy to distinguish between these cases and the one I'm 
asking about here. Clearly, that line of code quoted above from 
request/lib/copy.js, "o[i] = obj[i]", should not be triggering a fiber 
yield, and if it did (say in fancy situations with proxies or getters are 
involved), that should not manifest as a call to _tickCallback.

thanks,

Matt

[1]: https://github.com/laverdet/node-fibers

-- 
Job board: http://jobs.nodejs.org/
New group rules: 
https://gist.github.com/othiym23/9886289#file-moderation-policy-md
Old group rules: 
https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
--- 
You received this message because you are subscribed to the Google Groups 
"nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/nodejs/af1b6c78-ba23-4ec3-ad98-b0b6c148d435%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to