Closed watson closed 7 years ago
Here's a test case that will break when #113 is applied:
global.Promise = require('bluebird')
require('async-listener')
new Promise()
The correct behavior would be for async listener not to wrap non native promises
A quick test reveals that this is a race condition. The guard actually correctly detects that it shouldn't instrument by ending up here: https://github.com/othiym23/async-listener/blob/3e29e8cf645c732a2a4f833bd7c3d9df67936304/index.js#L331
But at this point the wrapPromise()
function have already been called here:
https://github.com/othiym23/async-listener/blob/3e29e8cf645c732a2a4f833bd7c3d9df67936304/index.js#L388
That is actually the intended behavior, that callback is being invoked asynchronously. What this means is either another async should have trigger (a few lines above) or bluebird is using an app not yet wrapped to schedule the async callback. That particular check was to handle bad implementations of promises that resolved a then synchronously
Oh ignore my previous comment. I think I probably misinterpreted the "should not resolve synchronously" test at first glance. What this test does is checking if then
calls its callback synchronously, in which case it's not considered a native promise. But apparently bluebird have become so good at impersonating native promises so that this guard doesn't detect it
Sorry for all the typos (I'm on my phone)
@hayes haha yes you are right - GitHub needs a "is typing...". I just discovered the same thing as evident by my new comment ๐
Looks like bluebird uses native promises to schedule it's tasks: https://github.com/petkaantonov/bluebird/blob/master/src/schedule.js which we have not (and now cannot) wrap. This is really inconvenient.
The only way I see to fix this off the top of my head is to wrap bluebird as if it was native, not a big fan of that though
Another option - which I'm not a big fan of either - is feature detection. The Bluebird Promise object have a few properties that the native promise doesn't
The thing I worry about is that promises still need to he wrapped. For an app using bluebird to work, we do need to wrap something
On Fri, Jun 23, 2017, 3:36 PM Thomas Watson notifications@github.com wrote:
Another option - which I'm not a big fan of either - is feature detection. The Bluebird Promise object have a few properties that the native promise doesn't
โ You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/othiym23/async-listener/issues/123#issuecomment-310787765, or mute the thread https://github.com/notifications/unsubscribe-auth/AAx1jymQGauqvr0bh3qPqD1qcoaUPF8Wks5sHD3kgaJpZM4OEFlj .
We could call into Promise.setScheduler
if it exists and behaves like a native promise.
In that case, we could.fall back to something other than native promises as the asynchronizer
I'm not sure this module should patch non-native promise implementations as the patching is complex and can have unexpected consequences (a was the case with bluebird). Also, patching the bluebird scheduler would not give bluebird promises the same behavior as native promises to my knowledge which could make things more confusing as instead of better (there is extra logic to determine what context to propagate when then is called that would be missing among other things).
If we want to strengthen the native promise check, maybe we could add:
Promise.toString() === 'function Promise() { [native code] }'
I'm not aware of a check that would be complete but it seems unlikely that there would be many userspace C++ promise implementations.
@matthewloring I asked the same question a while back. Seems like some implementations would fake the toString
function to return this sting. But I'm not sure if this is still the case.
I got a chance to talk to the V8 team a bit about this and there is no way outside of native code to determine definitively that something is a native promise (there is %IsPromise
but that requires starting node with the natives syntax flag). I'm not sure we'll be able to do better than a hack without adding a native component or dependency to this module. Maybe we would have higher assurance with:
Promise.toString() === 'function Promise() { [native code] }' && Promise.toString.toString() === 'function toString() { [native code] }'
@hayes @watson @Qard What do you think?
@hayes @watson @Qard any opinions? It would be good to make forward progress on this.
How does async_hooks
deal with this? By hiding it behind the implementation wall? I haven't reviewed @addaleax's recent PRs related to promises closely enough to know.
Sorry, I missed this before. My github notifications got out of hand so I just did a mark-all-as-read recently. ๐ฅ
I'm ๐ on checking toString()
and toString.toString()
are both native.
How does
async_hooks
deal with this? By hiding it behind the implementation wall?
Iโm not sure I understand what you mean by that โ if it answers your question, async_hooks
can only deal with native Promises, it doesnโt care about what the global Promise
object is.
async_hooks handled it on the native side, with PromiseHook, since we have async/await to worry about, which bypasses a bunch of the user-facing promise API. Simply patching the then method didn't work there.
We need to reintroduce the ability to subclass Promise which was allowed by #113 but was reverted in #122. For this to happen we need to make sure we don't break bluebird as evident by #121.