Skip to content

[BUG]: mysql2 instrumentation causes stack overflow with large prepared statement params #7074

@myeongseoklee

Description

@myeongseoklee

Tracer Version(s)

5.28.0

Node.js Version(s)

18.19.1

Bug Report

After calling connection.execute() with a large number of parameters (~3,300+), the application crashes with Maximum call stack size exceeded.

Stack Trace:

RangeError: Maximum call stack size exceeded
at AsyncResource.runInAsyncScope (node:async_hooks:197:18)
at Prepare.bound (node:async_hooks:235:16)
at AsyncResource.runInAsyncScope (node:async_hooks:203:9)
at Prepare.bound (node:async_hooks:235:16)
... (repeating pattern)

Root Cause:

In packages/datadog-instrumentations/src/mysql2.js, the bindExecute function re-wraps onResult on every execute() call (Line 108-109):

The onResult callback gets wrapped with asyncResource.bind() on every packet. MySQL's prepared statement protocol sends one packet per parameter definition. For 3,366 params, Prepare.execute() is called 3,369 times, nesting the callback 3,369 layers deep.

Evidence:

  • 3,060 params → 3,063 execute() calls → ✅ OK
  • 3,366 params → 3,369 execute() calls → ❌ Stack Overflow

Suggested Fix:
Only wrap onResult once by checking if already wrapped, or move wrapping to addCommand.

Reproduction Repository:
https://github.com/myeongseoklee/mysql2-callstack-reproduction

Reproduction Code

// Problematic pattern
const params = []; // 3,366+ params
connection.execute(
  `INSERT INTO table (...) VALUES (?, ?, ...), (?, ?, ...), ...`,
  params,
  callback
);

// Workaround: inline values, pass empty array
const values = rows.map(r => `(${escape(r.col1)}, ...)`).join(',');
connection.execute(`INSERT ... VALUES ${values}`, [], callback);

Error Logs

RangeError: Maximum call stack size exceeded
    at AsyncResource.runInAsyncScope (node:async_hooks:197:18)
    at Prepare.bound (node:async_hooks:235:16)
    at AsyncResource.runInAsyncScope (node:async_hooks:203:9)
    at Prepare.bound (node:async_hooks:235:16)
    at AsyncResource.runInAsyncScope (node:async_hooks:203:9)
    at Prepare.bound (node:async_hooks:235:16)
    at AsyncResource.runInAsyncScope (node:async_hooks:203:9)
    at Prepare.bound (node:async_hooks:235:16)

Tracer Config

// init-tracer.cjs
const tracer = require('dd-trace');
tracer.init({
  service: 'mysql2-test',
  env: 'test',
  enabled: true,
});

Operating System

macOS (Darwin)

Bundling

No Bundling

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions