Description
- Version: 13.5.0
- Platform: Darwin tau.local 19.0.0 Darwin Kernel Version 19.0.0: Thu Oct 17 16:17:15 PDT 2019; root:xnu-6153.41.3~29/RELEASE_X86_64 x86_64 (but irrelevant to this issue)
- Subsystem: async hooks
The change in #30965 means that Node now relies on process._fatalException
calling emitAfter(asyncId);
However, this cannot be done in userland code, because the emitAfter
method is not exposed.
With the deprecation of domains in Node.js version 4, as far as I've been able to tell, patching process._fatalException
and tracking the process flow through async hooks is the only way to reliably tie an error back to a chain of continuations. (And it doesn't even work 100% reliably, because Promises break async hooks.)
For node-tap, in order to fail the appropriate test when an error is thrown or promise is rejected (most of the time, at least), I'm using async-hook-domain. I used to use domains, but was scared off by the noisy warnings.
Now, a test like this:
const t = require('tap')
t.test('failure', t => {
throw new Error('nope')
})
which should output this:
$ node fail.js
TAP version 13
# Subtest: failure
not ok 1 - nope
---
stack: |
Test.<anonymous> (fail.js:3:9)
at:
line: 3
column: 9
file: fail.js
function: Test.<anonymous>
tapCaught: testFunctionThrow
test: failure
source: |
t.test('failure', t => {
throw new Error('nope')
--------^
})
...
1..1
# failed 1 test
not ok 1 - failure # time=18.611ms
instead (as of 503900b) outputs this:
$ node fail.js
TAP version 13
# Subtest: failure
not ok 1 - nope
---
stack: |
Test.<anonymous> (fail.js:3:9)
at:
line: 3
column: 9
file: fail.js
function: Test.<anonymous>
tapCaught: testFunctionThrow
test: failure
source: |
t.test('failure', t => {
throw new Error('nope')
--------^
})
...
1..1
# failed 1 test
not ok 1 - failure # time=18.611ms
Error: async hook stack has become corrupted (actual: 18, expected: 1)
1: 0x10000b8d9 node::AsyncHooks::pop_async_id(double) [/usr/local/bin/node]
2: 0x10000189a node::InternalCallbackScope::Close() [/usr/local/bin/node]
3: 0x10000149a node::InternalCallbackScope::~InternalCallbackScope() [/usr/local/bin/node]
4: 0x1000b3f45 node::NodeMainInstance::Run() [/usr/local/bin/node]
5: 0x10005e46f node::Start(int, char**) [/usr/local/bin/node]
6: 0x7fff657932e5 start [/usr/lib/system/libdyld.dylib]
7: 0x2
What has to happen to get error handling and continuation tracking shipped in core? The pieces are all there, they're just not exposed in any kind of useful way. I'm happy to help.
Failing that, I'd suggest reverting 503900b.