Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clears error collections on transaction end after processing. #712

Merged
merged 2 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/instrumentation/core/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ function wrapResponseEnd(agent, proto) {
return end.apply(this, arguments)
}

if (!txInfo.transaction.isActive()) {
logger.trace('wrappedResEnd invoked for ended transaction implying multiple invocations.')
return end.apply(this, arguments)
}

// If an error happened, add it to the aggregator.
if (txInfo.error) {
if (!txInfo.errorHandled || urltils.isError(agent.config, this.statusCode)) {
Expand Down
29 changes: 29 additions & 0 deletions lib/transaction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,28 @@ Transaction.prototype.end = function end() {
}

this.agent.emit('transactionFinished', this)

// Do after emit so all post-processing can complete
this._cleanUneededReferences()

return this
}

/**
* Cleans up references that will not be used later for processing such as
* transaction traces.
*
* Errors won't be needed for later processing but can contain extra details we
* don't want to hold in memory. Particularly, axios errors can result in indirect
* references to promises which will prevent them from being destroyed and result
* in a memory leak. This is due to the TraceSegment not getting removed from the
* async-hooks segmentMap because 'destroy' never fires.
*/
Transaction.prototype._cleanUneededReferences = function _cleanUneededReferences() {
this.userErrors = null
this.exceptions = null
}

/**
* For web transactions, this represents the time from when the request was received
* to when response was sent. For background transactions, it is equal to duration
Expand Down Expand Up @@ -768,6 +787,11 @@ function _linkExceptionToSegment(exception) {
Transaction.prototype.addException = _addException

function _addException(exception) {
if (!this.isActive()) {
logger.trace('Transaction is not active. Not capturing error: ', exception)
return
}

this._linkExceptionToSegment(exception)
this.exceptions.push(exception)
}
Expand All @@ -782,6 +806,11 @@ function _addException(exception) {
Transaction.prototype.addUserError = _addUserError

function _addUserError(exception) {
if (!this.isActive()) {
logger.trace('Transaction is not active. Not capturing user error: ', exception)
return
}

this._linkExceptionToSegment(exception)
this.userErrors.push(exception)
}
Expand Down
58 changes: 58 additions & 0 deletions test/unit/transaction.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,64 @@ describe('Transaction', function() {
})
})

tap.test('transaction end', (t) => {
t.autoend()

let agent = null
let transaction = null

t.beforeEach((done) => {
agent = helper.loadMockedAgent({
attributes: {
enabled: true,
include: ['request.parameters.*']
},
distributed_tracing: {
enabled: true
}
})

transaction = new Transaction(agent)

done()
})

t.afterEach((done) => {
helper.unloadAgent(agent)

agent = null
transaction = null

done()
})

t.test('should clear errors', (t) => {
transaction.userErrors.push(new Error('user sadness'))
transaction.exceptions.push(new Error('things went bad'))

transaction.end()

t.equal(transaction.userErrors, null)
t.equal(transaction.exceptions, null)

t.end()
})

t.test('should not clear errors until after transactionFinished event', (t) => {
transaction.userErrors.push(new Error('user sadness'))
transaction.exceptions.push(new Error('things went bad'))

agent.on('transactionFinished', (endedTransaction) => {
t.equal(endedTransaction.userErrors.length, 1)
t.equal(endedTransaction.exceptions.length, 1)

t.end()
})

transaction.end()
})
})

tap.test('when being named with finalizeNameFromUri', (t) => {
t.autoend()

Expand Down