-
Notifications
You must be signed in to change notification settings - Fork 0
/
async-break-finder.js
131 lines (114 loc) · 3.9 KB
/
async-break-finder.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
const ah = require('async_hooks')
const treeToDot = require('./lib/tree-to-dot')
const fs = require('fs')
const path = require('path')
const archy = require('archy')
const template = fs.readFileSync(path.join(__dirname, 'lib/template.html'), 'utf8')
const idToNode = new Map()
function frameIsInternal (frame) {
if (frame.includes(' (')) {
frame = frame.split(' (')[1].split(')')[0]
}
return !frame.startsWith('/') || frame.includes(__filename)
}
function getStack () {
const oldLimit = Error.stackTraceLimit
Error.stackTraceLimit = Infinity
const stack = new Error().stack
Error.stackTraceLimit = oldLimit
const frames = stack.split('\n')
frames.shift() // Error
frames.shift() // getStack
const result = []
for (let frame of frames) {
frame = frame.replace(' at ', '')
if (process.env.ABF_KEEP_INTERNALS || !frameIsInternal(frame)) {
result.push(frame)
}
}
return result.join('\n')
}
function renderHtml (tree, list) {
const treeDot = treeToDot(tree)
const listDot = treeToDot(list)
const html = template
.replace('{{tree}}', treeDot)
.replace('{{list}}', listDot)
const filename = path.join(process.cwd(), Date.now() + '.async-break.html')
fs.writeFileSync(filename, html)
return filename
}
function archify (node) {
const nodes = node.children
? node.children.map(archify)
: node.child ? [archify(node.child)] : []
return {
label: '\u001b[33m### ' + node.type + ' ###\u001b[0m\n' + node.stack,
nodes
}
}
class AsyncBreakError extends Error {
constructor (subtree, list) {
super('No path found! ' +
(process.env.ABF_HTML
? 'See ' + renderHtml(subtree, list)
: 'There is no async context chain between the two pieces of code you\'ve identified.\n\n' +
'\u001b[35mHere is the async tree starting at the first point you identified.\n' +
'In one of the edges, asynchronous context is lost probably due to userland scheduling.\u001b[0m\n' +
'╔══════════════\n' +
archy(archify(subtree)).trim().split('\n').map(l => '║ ' + l).join('\n') + '\n' +
'╚══════════════\n\n' +
'\u001b[35mHere is the async branch that leads to the second point you identified.\n' +
'Somewhere, you\'ll need to bind the two together.\u001b[0m\n' +
'╔══════════════\n' +
archy(archify(list)).trim().split('\n').map(l => '║ ' + l).join('\n') + '\n' +
'╚══════════════'))
Error.captureStackTrace(this, AsyncBreakError)
Object.defineProperty(this, 'subtree', { value: subtree })
Object.defineProperty(this, 'list', { value: list })
}
}
class AsyncNode {
constructor (id, type) {
this.id = id
this.type = type
const parent = ah.executionAsyncId()
this.parent = idToNode.get(parent) || undefined
if (this.parent) {
this.parent.children.push(this)
}
this.children = []
this.stack = getStack()
idToNode.set(id, this)
}
pathTo (child) {
const pathNode = { id: this.id, type: this.type, stack: this.stack, child }
return this.parent ? this.parent.pathTo(pathNode) : pathNode.child
}
removeParents () {
delete this.parent
this.children.forEach(child => child.removeParents())
}
validatePathFrom (ancestor) {
let current = this
while (current.parent) {
if (current.parent === ancestor) {
return
}
current = current.parent
}
ancestor.removeParents()
throw new AsyncBreakError(ancestor, this.pathTo())
}
static create (id, type) {
return new AsyncNode(id, type)
}
}
AsyncNode.create(1, 'root')
ah.createHook({ init: AsyncNode.create }).enable()
module.exports = function asyncBreakFinder (ancestor) {
if (ancestor) {
return AsyncNode.create(null, 'descendant').validatePathFrom(ancestor)
}
return idToNode.get(ah.executionAsyncId())
}