Skip to content

trace: false is ignored by most promise-API methods, including per-query hot pathsΒ #4501

Description

@sidorares

Summary

trace: false is only honoured by four of the promise-API entry points. Every other promise method still calls captureStackHolder(...) unconditionally, so the per-call Error.captureStackTrace cannot be opted out of. Three of the ungated call sites are per-query hot paths, not just lifecycle methods.

Current state on master

Gated (respect config.trace):

site method
lib/promise/connection.js:33 PromiseConnection#query
lib/promise/connection.js:53 PromiseConnection#execute
lib/promise/pool.js:40 PromisePool#query
lib/promise/pool.js:60 PromisePool#execute

Not gated β€” per-query paths:

site method
lib/promise/prepared_statement_info.js:14 PromisePreparedStatementInfo#execute
lib/promise/pool_cluster.js:28 PromisePoolNamespace#query
lib/promise/pool_cluster.js:44 PromisePoolNamespace#execute

Not gated β€” lifecycle/per-connection:

site method
lib/promise/connection.js:85 beginTransaction
lib/promise/connection.js:96 commit
lib/promise/connection.js:105 rollback
lib/promise/connection.js:116 ping
lib/promise/connection.js:131 reset
lib/promise/connection.js:146 connect
lib/promise/connection.js:162 prepare
lib/promise/connection.js:181 changeUser
lib/promise/pool.js:80 PromisePool#end

The most user-visible consequence is PromisePreparedStatementInfo#execute: the documented way to reuse a prepared statement is const stmt = await conn.prepare(sql) followed by repeated stmt.execute(params), and that loop pays a stack capture per query with no way to turn it off. PoolCluster users are in the same position for every query they run.

Why it matters

Measured on the gated path, so it is a direct indication of what the ungated paths still pay. Workload: 8 concurrent pool.execute() of a single-row prepared query against MySQL 9.7.2 (local Docker), metric = client CPU microseconds per query, median of interleaved runs, fresh process each:

runtime trace default trace: false delta
node v26.0.0 38.8 us/query 32.9 us/query -15%
bun 1.4.0 37.1 us/query 35.3 us/query -5%

(Repeat runs put the node figure in the 11-15% band and bun in the 5-9% band.)

Suggested fix

Apply the same ternary already used at connection.js:33 to the remaining sites, reading config.trace (or config.connectionConfig.trace for pool/cluster, this.statement.connection.config.trace or similar for the prepared-statement wrapper). makeDoneCb / applyCapturedStack already handle stackHolder === undefined, so no other change is needed.

This stays backward compatible: ConnectionConfig sets this.trace = options.trace !== false, so only an explicit false opts out β€” unset, true, 0, null and undefined all keep today's behaviour.

Note on affected versions

Worth flagging for anyone reading this later: trace was parsed but never read anywhere in the library before 3.24.0 (in 3.16.3-3.23.0 the only references are the default and this.trace = options.trace !== false in connection_config.js). The promise wrapper captured unconditionally regardless of the setting. Gating was introduced in 3.24.0 for query/execute; this issue is about finishing that work.

Happy to send a PR if the approach looks right.

Activity

  1. added a commit that references this issue on Aug 24, 2026
    1dcd8ef
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions