Skip to content

Commit 5d6e10e

Browse files
authored
tracing: fix child process bug when used with bluebird promises (#6002)
* fix bug
1 parent 41ce55c commit 5d6e10e

File tree

3 files changed

+115
-1
lines changed

3 files changed

+115
-1
lines changed

packages/datadog-instrumentations/src/child_process.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ function wrapChildProcessCustomPromisifyMethod (customPromisifyMethod, shell) {
179179
return result
180180
}
181181

182-
return Promise.prototype.then.call(result, resolve, reject)
182+
return Promise.resolve(result).then(resolve, reject)
183183
}
184184
}
185185

packages/datadog-plugin-child_process/test/index.spec.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,114 @@ describe('Child process plugin', () => {
364364
})
365365
})
366366

367+
describe('Bluebird Promise Compatibility', () => {
368+
// BLUEBIRD REGRESSION TEST - Prevents "this._then is not a function" bug
369+
370+
let childProcess, tracer, util
371+
let originalPromise
372+
let Bluebird
373+
374+
beforeEach(() => {
375+
return agent.load('child_process', undefined, { flushInterval: 1 }).then(() => {
376+
tracer = require('../../dd-trace')
377+
childProcess = require('child_process')
378+
util = require('util')
379+
tracer.use('child_process', { enabled: true })
380+
Bluebird = require('../../../versions/bluebird').get()
381+
382+
originalPromise = global.Promise
383+
global.Promise = Bluebird
384+
})
385+
})
386+
387+
afterEach(() => {
388+
global.Promise = originalPromise
389+
return agent.close({ ritmReset: false })
390+
})
391+
392+
it('should not crash with "this._then is not a function" when using Bluebird promises', async () => {
393+
const execFileAsync = util.promisify(childProcess.execFile)
394+
395+
expect(global.Promise).to.equal(Bluebird)
396+
expect(global.Promise.version).to.exist
397+
398+
const expectedPromise = expectSomeSpan(agent, {
399+
type: 'system',
400+
name: 'command_execution',
401+
error: 0,
402+
meta: {
403+
component: 'subprocess',
404+
'cmd.exec': '["echo","bluebird-test"]',
405+
}
406+
})
407+
408+
const result = await execFileAsync('echo', ['bluebird-test'])
409+
expect(result).to.exist
410+
expect(result.stdout).to.contain('bluebird-test')
411+
412+
return expectedPromise
413+
})
414+
415+
it('should work with concurrent Bluebird promise calls', async () => {
416+
const execFileAsync = util.promisify(childProcess.execFile)
417+
418+
const promises = []
419+
for (let i = 0; i < 5; i++) {
420+
promises.push(
421+
execFileAsync('echo', [`concurrent-test-${i}`])
422+
.then(result => {
423+
expect(result.stdout).to.contain(`concurrent-test-${i}`)
424+
return result
425+
})
426+
)
427+
}
428+
429+
const results = await Promise.all(promises)
430+
expect(results).to.have.length(5)
431+
})
432+
433+
it('should handle Bluebird promise rejection properly', async () => {
434+
global.Promise = Bluebird
435+
436+
const execFileAsync = util.promisify(childProcess.execFile)
437+
438+
const expectedPromise = expectSomeSpan(agent, {
439+
type: 'system',
440+
name: 'command_execution',
441+
error: 1,
442+
meta: {
443+
component: 'subprocess',
444+
'cmd.exec': '["node","-invalidFlag"]'
445+
}
446+
})
447+
448+
try {
449+
await execFileAsync('node', ['-invalidFlag'], { stdio: 'pipe' })
450+
throw new Error('Expected command to fail')
451+
} catch (error) {
452+
expect(error).to.exist
453+
expect(error.code).to.exist
454+
}
455+
456+
return expectedPromise
457+
})
458+
459+
it('should work with util.promisify when global Promise is Bluebird', async () => {
460+
// Re-require util to get Bluebird-aware promisify
461+
delete require.cache[require.resolve('util')]
462+
const utilWithBluebird = require('util')
463+
464+
const execFileAsync = utilWithBluebird.promisify(childProcess.execFile)
465+
466+
const promise = execFileAsync('echo', ['util-promisify-test'])
467+
expect(promise.constructor).to.equal(Bluebird)
468+
expect(promise.constructor.version).to.exist
469+
470+
const result = await promise
471+
expect(result.stdout).to.contain('util-promisify-test')
472+
})
473+
})
474+
367475
describe('Integration', () => {
368476
describe('Methods which spawn a shell by default', () => {
369477
const execAsyncMethods = ['exec']

packages/dd-trace/test/plugins/externals.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@
6161
"versions": ["^4"]
6262
}
6363
],
64+
"child_process": [
65+
{
66+
"name": "bluebird",
67+
"versions": ["^3"]
68+
}
69+
],
6470
"cookie-parser": [
6571
{
6672
"name": "express",

0 commit comments

Comments
 (0)