Skip to content

[BUG]: mysql2 instrumentation not supporting promises #7044

@alexpavlov

Description

@alexpavlov

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

  1. Use Next.js 15+ with mysql2/promise
  2. Enable dd-trace with default settings (appsec enabled)
  3. 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:

  1. Duplicate requests are common due to React Server Components behavior in dev mode (React's strict mode double-invokes components)
  2. Long compilation times on first request can cause client-side request timeouts/aborts
  3. Hot module replacement can interrupt in-flight database connections

Error Logs

No response

Tracer Config

No response

Operating System

No response

Bundling

Unsure

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions