Skip to content

Commit

Permalink
add synchronous scope manager (#1252)
Browse files Browse the repository at this point in the history
  • Loading branch information
rochdev committed May 19, 2021
1 parent 9187868 commit 38327b4
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 51 deletions.
1 change: 1 addition & 0 deletions ext/scopes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ declare const scopes: {
ASYNC_RESOURCE: 'async_resource',
ASYNC_LOCAL_STORAGE: 'async_local_storage',
ASYNC_HOOKS: 'async_hooks'
SYNC: 'sync'
NOOP: 'noop'
}

Expand Down
1 change: 1 addition & 0 deletions ext/scopes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ module.exports = {
ASYNC_RESOURCE: 'async_resource',
ASYNC_LOCAL_STORAGE: 'async_local_storage',
ASYNC_HOOKS: 'async_hooks',
SYNC: 'sync',
NOOP: 'noop'
}
2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ export declare interface TracerOptions {
* implementation for the runtime. Only change this if you know what you are
* doing.
*/
scope?: 'async_hooks' | 'async_local_storage' | 'async_resource' | 'noop'
scope?: 'async_hooks' | 'async_local_storage' | 'async_resource' | 'sync' | 'noop'

/**
* Whether to report the hostname of the service host. This is used when the agent is deployed on a different host and cannot determine the hostname automatically.
Expand Down
2 changes: 2 additions & 0 deletions packages/dd-trace/src/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ module.exports = name => {

if (name === NOOP) {
Scope = require('./scope/base')
} else if (name === scopes.SYNC) {
Scope = require('./scope/sync')
} else if (name === scopes.ASYNC_LOCAL_STORAGE) {
Scope = require('./scope/async_local_storage')
} else if (name === scopes.ASYNC_RESOURCE || (!name && hasJavaScriptAsyncHooks)) {
Expand Down
29 changes: 29 additions & 0 deletions packages/dd-trace/src/scope/sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict'

const Base = require('./base')

class Scope extends Base {
constructor () {
super()

this._stack = []
this._current = null
}

_active () {
return this._current || null
}

_activate (span, callback) {
this._stack.push(this._current)
this._current = span

try {
return callback()
} finally {
this._current = this._stack.pop()
}
}
}

module.exports = Scope
14 changes: 14 additions & 0 deletions packages/dd-trace/test/scope/sync.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

const Scope = require('../../src/scope/sync')
const testScope = require('./test')

describe('Scope (sync)', () => {
let scope

beforeEach(() => {
scope = new Scope()
})

testScope(() => scope, false)
})
102 changes: 52 additions & 50 deletions packages/dd-trace/test/scope/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const Span = require('opentracing').Span

module.exports = factory => {
module.exports = (factory, supportsAsync = true) => {
let scope
let span

Expand Down Expand Up @@ -44,72 +44,74 @@ module.exports = factory => {
expect(scope.active()).to.be.null
})

it('should persist through setTimeout', done => {
scope.activate(span, () => {
setTimeout(() => {
expect(scope.active()).to.equal(span)
done()
}, 0)
if (supportsAsync) {
it('should persist through setTimeout', done => {
scope.activate(span, () => {
setTimeout(() => {
expect(scope.active()).to.equal(span)
done()
}, 0)
})
})
})

it('should persist through setImmediate', done => {
scope.activate(span, () => {
setImmediate(() => {
expect(scope.active()).to.equal(span)
done()
}, 0)
it('should persist through setImmediate', done => {
scope.activate(span, () => {
setImmediate(() => {
expect(scope.active()).to.equal(span)
done()
}, 0)
})
})
})

it('should persist through setInterval', done => {
scope.activate(span, () => {
let shouldReturn = false

const timer = setInterval(() => {
expect(scope.active()).to.equal(span)
it('should persist through setInterval', done => {
scope.activate(span, () => {
let shouldReturn = false

if (shouldReturn) {
clearInterval(timer)
return done()
}
const timer = setInterval(() => {
expect(scope.active()).to.equal(span)

shouldReturn = true
}, 0)
})
})
if (shouldReturn) {
clearInterval(timer)
return done()
}

if (global.process && global.process.nextTick) {
it('should persist through process.nextTick', done => {
scope.activate(span, () => {
process.nextTick(() => {
expect(scope.active()).to.equal(span)
done()
shouldReturn = true
}, 0)
})
})
}

it('should persist through promises', () => {
const promise = Promise.resolve()
if (global.process && global.process.nextTick) {
it('should persist through process.nextTick', done => {
scope.activate(span, () => {
process.nextTick(() => {
expect(scope.active()).to.equal(span)
done()
}, 0)
})
})
}

return scope.activate(span, () => {
return promise.then(() => {
expect(scope.active()).to.equal(span)
it('should persist through promises', () => {
const promise = Promise.resolve()

return scope.activate(span, () => {
return promise.then(() => {
expect(scope.active()).to.equal(span)
})
})
})
})

it('should handle concurrency', done => {
scope.activate(span, () => {
setImmediate(() => {
expect(scope.active()).to.equal(span)
done()
it('should handle concurrency', done => {
scope.activate(span, () => {
setImmediate(() => {
expect(scope.active()).to.equal(span)
done()
})
})
})

scope.activate(span, () => {})
})
scope.activate(span, () => {})
})
}

it('should handle errors', () => {
const error = new Error('boom')
Expand Down

0 comments on commit 38327b4

Please sign in to comment.