Skip to content

Commit c9e4ca2

Browse files
authored
fix: more reliable span.sync determination, drop unused transaction.sync (#2326)
Currently `span.sync` is tracked via: - the "before" async hook setting it false on the "active" span, and - Instrumentation#bindFunction's wrapper setting it false, on the fair assumption that all usages of bindFunction are for async callbacks. The former has issues when there are multiple active spans within a single async task -- as is the case with Elasticsearch instrumentation (issue #1996) and the aws-sdk instrumentations (which have manual workarounds). This changes to set sync=false if the executionAsyncId() at end-time is different than at start-time. This decouples from async context management, so works for whatever value of the `asyncHooks` config var. This also drops the `transaction.sync` field which was never used. Fixes: #1996 Fixes: #2292
1 parent 52d5ffc commit c9e4ca2

File tree

15 files changed

+48
-103
lines changed

15 files changed

+48
-103
lines changed

CHANGELOG.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ Notes:
4040
[float]
4141
===== Bug fixes
4242
43+
* Improve `span.sync` determination (fixes {issue}1996[#1996]) and stop
44+
reporting `transaction.sync` which was never used ({issue}2292[#2292]).
45+
A minor semantic change is that `span.sync` is not set to a final value
46+
until `span.end()` is called. Before `span.end()` the value will always
47+
by `true`.
48+
4349
* Guard against a negative value of `metricsInterval`, which can lead to
4450
high CPU usage as metrics are collected as fast as possible. Also ensure
4551
no metrics collection can happen if `metricsInterval="0s"` as intended.

lib/instrumentation/async-hooks.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,6 @@ module.exports = function (ins) {
9292
}
9393

9494
function before (asyncId) {
95-
const span = activeSpans.get(asyncId)
96-
if (span && !span.ended) {
97-
span.sync = false
98-
}
99-
const transaction = span ? span.transaction : activeTransactions.get(asyncId)
100-
if (transaction && !transaction.ended) {
101-
transaction.sync = false
102-
}
10395
ins.bindingSpan = null
10496
}
10597

lib/instrumentation/generic-span.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ function GenericSpan (agent, ...args) {
2323

2424
this.timestamp = this._timer.start
2525
this.ended = false
26-
this.sync = true
2726

2827
this.outcome = constants.OUTCOME_UNKNOWN
2928

lib/instrumentation/index.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,7 @@ var wrapped = Symbol('elastic-apm-wrapped-function')
353353
// the callback function gets instrument on "the right" transaction and span.
354354
//
355355
// The instrumentation programmer is still responsible for starting a span,
356-
// and ending a span. Additionally, this function will set a span's sync
357-
// property to `false` -- it's up to the instrumentation programmer to ensure
358-
// that the callback they're binding is really async. If bindFunction is
359-
// passed a callback that the wrapped function executes synchronously, it will
360-
// still mark the span's `async` property as `false`.
356+
// and ending a span.
361357
//
362358
// @param {function} original
363359
Instrumentation.prototype.bindFunction = function (original) {
@@ -379,8 +375,6 @@ Instrumentation.prototype.bindFunction = function (original) {
379375
ins.currentTransaction = trans
380376
ins.bindingSpan = null
381377
ins.activeSpan = span
382-
if (trans) trans.sync = false
383-
if (span) span.sync = false
384378
var result = original.apply(this, arguments)
385379
ins.currentTransaction = prevTrans
386380
return result

lib/instrumentation/modules/aws-sdk/dynamodb.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,6 @@ function instrumentationDynamoDb (orig, origArguments, request, AWS, agent, { ve
100100
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE)
101101
}
102102

103-
// Workaround a bug in the agent's handling of `span.sync`.
104-
//
105-
// The bug: Currently this span.sync is not set `false` because there is
106-
// an HTTP span created (for this S3 request) in the same async op. That
107-
// HTTP span becomes the "active span" for this async op, and *it* gets
108-
// marked as sync=false in `before()` in async-hooks.js.
109-
span.sync = false
110103
span.end()
111104
})
112105

lib/instrumentation/modules/aws-sdk/s3.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,6 @@ function instrumentationS3 (orig, origArguments, request, AWS, agent, { version,
109109
}
110110
}
111111

112-
// Workaround a bug in the agent's handling of `span.sync`.
113-
//
114-
// The bug: Currently this span.sync is not set `false` because there is
115-
// an HTTP span created (for this S3 request) in the same async op. That
116-
// HTTP span becomes the "active span" for this async op, and *it* gets
117-
// marked as sync=false in `before()` in async-hooks.js.
118-
span.sync = false
119-
120112
span.end()
121113
})
122114
}

lib/instrumentation/modules/aws-sdk/sns.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,6 @@ function instrumentationSns (orig, origArguments, request, AWS, agent, { version
131131
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE)
132132
}
133133

134-
// we'll need to manually mark this span as async. The actual async hop
135-
// is captured by the agent's async hooks instrumentation
136-
span.sync = false
137134
span.end()
138135
})
139136

lib/instrumentation/modules/aws-sdk/sqs.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,6 @@ function instrumentationSqs (orig, origArguments, request, AWS, agent, { version
166166
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE)
167167
}
168168

169-
// we'll need to manually mark this span as async. The actual async hop
170-
// is captured by the agent's async hooks instrumentation
171-
span.sync = false
172169
span.end()
173170

174171
if (request.operation === 'receiveMessage' && response && response.data) {

lib/instrumentation/span.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict'
22

3+
const { executionAsyncId } = require('async_hooks')
34
var util = require('util')
45

56
var Value = require('async-value-promise')
@@ -34,6 +35,8 @@ function Span (transaction, name, ...args) {
3435
this._message = null
3536
this._stackObj = null
3637
this._capturedStackTrace = null
38+
this.sync = true
39+
this._startXid = executionAsyncId()
3740

3841
this.transaction = transaction
3942
this.name = name || 'unnamed'
@@ -71,6 +74,9 @@ Span.prototype.end = function (endTime) {
7174
}
7275

7376
this._timer.end(endTime)
77+
if (executionAsyncId() !== this._startXid) {
78+
this.sync = false
79+
}
7480

7581
this._setOutcomeFromSpanEnd()
7682

lib/instrumentation/transaction.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ Transaction.prototype.toJSON = function () {
129129
result: String(this.result),
130130
sampled: this.sampled,
131131
context: undefined,
132-
sync: this.sync,
133132
span_count: {
134133
started: this._builtSpans
135134
},

0 commit comments

Comments
 (0)