[BUG]: mysql2 instrumentation not supporting promises
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 error
Error 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
Thank you for reporting this in details. I'm part of the team that own this part of the code, and I will investigate it myself. As we're approaching holiday season, this might take more time than it should. But feel free to ping me again if too much time has passed.
I'm also taking the liberty to rename this issue, as it looks like it has no title ? Feel free to rename it yourself better if you want to.