-
Notifications
You must be signed in to change notification settings - Fork 360
Description
Tracer Version(s)
5.80.0
Node.js Version(s)
22.11.0
Bug Report
When using mysql2/promise with dd-trace, the mysql2 instrumentation can return a callback-style Query object instead of a Promise when the appsec abortController.signal.aborted check is true. This causes mysql2's Query.then() safety method to throw an error.
The issue is in packages/datadog-instrumentations/src/mysql2.js lines 167-204. When wrapping Pool.prototype.query, if the abort signal is triggered, the code returns queryCommand (a callback-style object) instead of a Promise:
shimmer.wrap(Pool.prototype, 'query', query => function (sql, values, cb) {
// ...
if (abortController.signal.aborted) {
// ...
return queryCommand; // This is NOT a promise!
}
return query.apply(this, arguments)
})When application code awaits this result, mysql2's Query class has a then() method that throws:
You have tried to call .then(), .catch(), or invoked await on the result of query that is not a promise...
Reproduction Code
- Use Next.js 15+ with
mysql2/promise - Enable dd-trace with default settings (appsec enabled)
- On first request after server restart, when a connection pool is being created, the error appears
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const tracer = await import('dd-trace');
tracer.default.init({
runtimeMetrics: true,
});
}
}
// lib/db.ts
import mysql from 'mysql2/promise';
const pool = mysql.createPool({ /* config */ });
const [rows] = await pool.query('SELECT * FROM table'); // Throws errorError Logs
DEBUG: Creating pool for: [schema_id]
You have tried to call .then(), .catch(), or invoked await on the result of query that is not a promise, which is a programming error. Try calling con.promise().query(), or require('mysql2/promise') instead of 'mysql2' for a promise-compatible version of the query interface.
Error: aborted
at ignore-listed frames {
code: 'ECONNRESET'
}
Workaround
Disabling appsec resolves the issue:
tracer.default.init({
appsec: { enabled: false },
});Suggested Fix
The wrapPool function should properly handle the promise wrapper. When the pool is from mysql2/promise, the wrapped query should return a rejected Promise instead of a callback-style object:
if (abortController.signal.aborted) {
// Return a rejected promise for promise-based pools
if (this.promise) {
return Promise.reject(abortController.signal.reason);
}
// ... existing callback handling
}Operating System
macOS Darwin 24.6.0 (also reproducible on Linux)
Additional Context
- mysql2 version: 3.15.3
Reproduction Environment
The error is triggered when a database connection is severed (ECONNRESET). This is easier to reproduce in the Next.js development environment where:
- Duplicate requests are common due to React Server Components behavior in dev mode (React's strict mode double-invokes components)
- Long compilation times on first request can cause client-side request timeouts/aborts
- Hot module replacement can interrupt in-flight database connections
Error Logs
No response
Tracer Config
No response
Operating System
No response
Bundling
Unsure