Skip to content

Commit 588b852

Browse files
authored
remove async storage from graphql (#5966)
* remove async storage from graphql instrumentation
1 parent 1ec3e85 commit 588b852

File tree

6 files changed

+172
-152
lines changed

6 files changed

+172
-152
lines changed

packages/datadog-instrumentations/src/graphql.js

Lines changed: 90 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
const {
44
addHook,
5-
channel,
6-
AsyncResource
5+
channel
76
} = require('./helpers/instrument')
87
const shimmer = require('../../datadog-shimmer')
98

@@ -89,29 +88,28 @@ function wrapParse (parse) {
8988
return parse.apply(this, arguments)
9089
}
9190

92-
const asyncResource = new AsyncResource('bound-anonymous-fn')
93-
94-
return asyncResource.runInAsyncScope(() => {
95-
parseStartCh.publish()
96-
let document
91+
const ctx = { source }
92+
return parseStartCh.runStores(ctx, () => {
9793
try {
98-
document = parse.apply(this, arguments)
99-
const operation = getOperation(document)
94+
ctx.document = parse.apply(this, arguments)
95+
const operation = getOperation(ctx.document)
10096

101-
if (!operation) return document
97+
if (!operation) return ctx.document
10298

10399
if (source) {
104-
documentSources.set(document, source.body || source)
100+
documentSources.set(ctx.document, source.body || source)
105101
}
102+
ctx.docSource = documentSources.get(ctx.document)
106103

107-
return document
104+
return ctx.document
108105
} catch (err) {
109106
err.stack
110-
parseErrorCh.publish(err)
107+
ctx.error = err
108+
parseErrorCh.publish(ctx)
111109

112110
throw err
113111
} finally {
114-
parseFinishCh.publish({ source, document, docSource: documentSources.get(document) })
112+
parseFinishCh.publish(ctx)
115113
}
116114
})
117115
}
@@ -123,25 +121,25 @@ function wrapValidate (validate) {
123121
return validate.apply(this, arguments)
124122
}
125123

126-
const asyncResource = new AsyncResource('bound-anonymous-fn')
127-
128-
return asyncResource.runInAsyncScope(() => {
129-
validateStartCh.publish({ docSource: documentSources.get(document), document })
130-
124+
const ctx = { docSource: documentSources.get(document), document }
125+
return validateStartCh.runStores(ctx, () => {
131126
let errors
132127
try {
133128
errors = validate.apply(this, arguments)
134129
if (errors && errors[0]) {
135-
validateErrorCh.publish(errors && errors[0])
130+
ctx.error = errors && errors[0]
131+
validateErrorCh.publish(ctx)
136132
}
137133
return errors
138134
} catch (err) {
139135
err.stack
140-
validateErrorCh.publish(err)
136+
ctx.error = err
137+
validateErrorCh.publish(ctx)
141138

142139
throw err
143140
} finally {
144-
validateFinishCh.publish({ document, errors })
141+
ctx.errors = errors
142+
validateFinishCh.publish(ctx)
145143
}
146144
})
147145
}
@@ -155,44 +153,46 @@ function wrapExecute (execute) {
155153
return exe.apply(this, arguments)
156154
}
157155

158-
const asyncResource = new AsyncResource('bound-anonymous-fn')
159-
return asyncResource.runInAsyncScope(() => {
160-
const args = normalizeArgs(arguments, defaultFieldResolver)
161-
const schema = args.schema
162-
const document = args.document
163-
const source = documentSources.get(document)
164-
const contextValue = args.contextValue
165-
const operation = getOperation(document, args.operationName)
166-
167-
if (contexts.has(contextValue)) {
168-
return exe.apply(this, arguments)
169-
}
156+
const args = normalizeArgs(arguments, defaultFieldResolver)
157+
const schema = args.schema
158+
const document = args.document
159+
const source = documentSources.get(document)
160+
const contextValue = args.contextValue
161+
const operation = getOperation(document, args.operationName)
162+
163+
if (contexts.has(contextValue)) {
164+
return exe.apply(this, arguments)
165+
}
166+
167+
const ctx = {
168+
operation,
169+
args,
170+
docSource: documentSources.get(document),
171+
source,
172+
fields: {},
173+
abortController: new AbortController()
174+
}
170175

176+
return startExecuteCh.runStores(ctx, () => {
171177
if (schema) {
172178
wrapFields(schema._queryType)
173179
wrapFields(schema._mutationType)
174180
}
175181

176-
startExecuteCh.publish({
177-
operation,
178-
args,
179-
docSource: documentSources.get(document)
180-
})
181-
182-
const context = { source, asyncResource, fields: {}, abortController: new AbortController() }
183-
184-
contexts.set(contextValue, context)
182+
contexts.set(contextValue, ctx)
185183

186-
return callInAsyncScope(exe, asyncResource, this, arguments, context.abortController, (err, res) => {
187-
if (finishResolveCh.hasSubscribers) finishResolvers(context)
184+
return callInAsyncScope(exe, this, arguments, ctx.abortController, (err, res) => {
185+
if (finishResolveCh.hasSubscribers) finishResolvers(ctx)
188186

189187
const error = err || (res && res.errors && res.errors[0])
190188

191189
if (error) {
192-
executeErrorCh.publish(error)
190+
ctx.error = error
191+
executeErrorCh.publish(ctx)
193192
}
194193

195-
finishExecuteCh.publish({ res, args, context })
194+
ctx.res = res
195+
finishExecuteCh.publish(ctx)
196196
})
197197
})
198198
}
@@ -205,14 +205,17 @@ function wrapResolve (resolve) {
205205
function resolveAsync (source, args, contextValue, info) {
206206
if (!startResolveCh.hasSubscribers) return resolve.apply(this, arguments)
207207

208-
const context = contexts.get(contextValue)
208+
const ctx = contexts.get(contextValue)
209209

210-
if (!context) return resolve.apply(this, arguments)
210+
if (!ctx) return resolve.apply(this, arguments)
211211

212-
const field = assertField(context, info, args)
212+
const field = assertField(ctx, info, args)
213213

214-
return callInAsyncScope(resolve, field.asyncResource, this, arguments, context.abortController, (err) => {
215-
updateFieldCh.publish({ field, info, err })
214+
return callInAsyncScope(resolve, this, arguments, ctx.abortController, (err) => {
215+
field.ctx.error = err
216+
field.ctx.info = info
217+
field.ctx.field = field
218+
updateFieldCh.publish(field.ctx)
216219
})
217220
}
218221

@@ -221,32 +224,30 @@ function wrapResolve (resolve) {
221224
return resolveAsync
222225
}
223226

224-
function callInAsyncScope (fn, aR, thisArg, args, abortController, cb) {
227+
function callInAsyncScope (fn, thisArg, args, abortController, cb) {
225228
cb = cb || (() => {})
226229

227-
return aR.runInAsyncScope(() => {
228-
if (abortController?.signal.aborted) {
229-
cb(null, null)
230-
throw new AbortError('Aborted')
231-
}
230+
if (abortController?.signal.aborted) {
231+
cb(null, null)
232+
throw new AbortError('Aborted')
233+
}
232234

233-
try {
234-
const result = fn.apply(thisArg, args)
235-
if (result && typeof result.then === 'function') {
236-
// bind callback to this scope
237-
result.then(
238-
aR.bind(res => cb(null, res)),
239-
aR.bind(err => cb(err))
240-
)
241-
} else {
242-
cb(null, result)
243-
}
244-
return result
245-
} catch (err) {
246-
cb(err)
247-
throw err
235+
try {
236+
const result = fn.apply(thisArg, args)
237+
if (result && typeof result.then === 'function') {
238+
// bind callback to this scope
239+
result.then(
240+
res => cb(null, res),
241+
err => cb(err)
242+
)
243+
} else {
244+
cb(null, result)
248245
}
249-
})
246+
return result
247+
} catch (err) {
248+
cb(err)
249+
throw err
250+
}
250251
}
251252

252253
function pathToArray (path) {
@@ -259,59 +260,26 @@ function pathToArray (path) {
259260
return flattened.reverse()
260261
}
261262

262-
function assertField (context, info, args) {
263+
function assertField (rootCtx, info, args) {
263264
const pathInfo = info && info.path
264265

265266
const path = pathToArray(pathInfo)
266267

267268
const pathString = path.join('.')
268-
const fields = context.fields
269+
const fields = rootCtx.fields
269270

270271
let field = fields[pathString]
271272

272273
if (!field) {
273-
const parent = getParentField(context, path)
274-
275-
// we want to spawn the new span off of the parent, not a new async resource
276-
parent.asyncResource.runInAsyncScope(() => {
277-
/* this child resource will run a branched scope off of the parent resource, which
278-
accesses the parent span from the storage unit in its own scope */
279-
const childResource = new AsyncResource('bound-anonymous-fn')
280-
281-
childResource.runInAsyncScope(() => {
282-
startResolveCh.publish({
283-
info,
284-
context,
285-
args
286-
})
287-
})
288-
289-
field = fields[pathString] = {
290-
parent,
291-
asyncResource: childResource,
292-
error: null
293-
}
294-
})
295-
}
296-
297-
return field
298-
}
299-
300-
function getParentField (context, path) {
301-
for (let i = path.length - 1; i > 0; i--) {
302-
const field = getField(context, path.slice(0, i))
303-
if (field) {
304-
return field
274+
const fieldCtx = { info, rootCtx, args }
275+
startResolveCh.publish(fieldCtx)
276+
field = fields[pathString] = {
277+
error: null,
278+
ctx: fieldCtx
305279
}
306280
}
307281

308-
return {
309-
asyncResource: context.asyncResource
310-
}
311-
}
312-
313-
function getField (context, path) {
314-
return context.fields[path.join('.')]
282+
return field
315283
}
316284

317285
function wrapFields (type) {
@@ -349,13 +317,13 @@ function wrapFieldType (field) {
349317
function finishResolvers ({ fields }) {
350318
Object.keys(fields).reverse().forEach(key => {
351319
const field = fields[key]
352-
const asyncResource = field.asyncResource
353-
asyncResource.runInAsyncScope(() => {
354-
if (field.error) {
355-
resolveErrorCh.publish(field.error)
356-
}
357-
finishResolveCh.publish(field.finishTime)
358-
})
320+
field.ctx.finishTime = field.finishTime
321+
field.ctx.field = field
322+
if (field.error) {
323+
field.ctx.error = field.error
324+
resolveErrorCh.publish(field.ctx)
325+
}
326+
finishResolveCh.publish(field.ctx)
359327
})
360328
}
361329

packages/datadog-plugin-graphql/src/execute.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ class GraphQLExecutePlugin extends TracingPlugin {
1111
static get type () { return 'graphql' }
1212
static get kind () { return 'server' }
1313

14-
start ({ operation, args, docSource }) {
14+
bindStart (ctx) {
15+
const { operation, args, docSource } = ctx
16+
1517
const type = operation && operation.operation
1618
const name = operation && operation.name && operation.name.value
1719
const document = args.document
@@ -27,20 +29,25 @@ class GraphQLExecutePlugin extends TracingPlugin {
2729
'graphql.operation.name': name,
2830
'graphql.source': source
2931
}
30-
})
32+
}, ctx)
3133

3234
addVariableTags(this.config, span, args.variableValues)
35+
36+
return ctx.currentStore
3337
}
3438

35-
finish ({ res, args }) {
36-
const span = this.activeSpan
39+
finish (ctx) {
40+
const { res, args } = ctx
41+
const span = ctx?.currentStore?.span || this.activeSpan
3742
this.config.hooks.execute(span, args, res)
3843
if (res?.errors) {
3944
for (const err of res.errors) {
4045
extractErrorIntoSpanEvent(this._tracerConfig, span, err)
4146
}
4247
}
43-
super.finish()
48+
super.finish(ctx)
49+
50+
return ctx.parentStore
4451
}
4552
}
4653

packages/datadog-plugin-graphql/src/parse.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,29 @@ class GraphQLParsePlugin extends TracingPlugin {
66
static get id () { return 'graphql' }
77
static get operation () { return 'parser' }
88

9-
start () {
9+
bindStart (ctx) {
1010
this.startSpan('graphql.parse', {
1111
service: this.config.service,
1212
type: 'graphql',
1313
meta: {}
14-
})
14+
}, ctx)
15+
16+
return ctx.currentStore
1517
}
1618

17-
finish ({ source, document, docSource }) {
18-
const span = this.activeSpan
19+
finish (ctx) {
20+
const { source, document, docSource } = ctx
21+
const span = ctx?.currentStore?.span || this.activeSpan
1922

2023
if (this.config.source && document) {
2124
span.setTag('graphql.source', docSource)
2225
}
2326

2427
this.config.hooks.parse(span, source, document)
2528

26-
super.finish()
29+
super.finish(ctx)
30+
31+
return ctx.parentStore
2732
}
2833
}
2934

0 commit comments

Comments
 (0)