perf: defer Error object creation to error handlers in promise wrappers - #4257
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #4257 +/- ##
==========================================
- Coverage 90.87% 90.86% -0.01%
==========================================
Files 89 89
Lines 14498 14487 -11
Branches 1862 1862
==========================================
- Hits 13175 13164 -11
Misses 1323 1323
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Move `new Error()` instantiation from the call site into error handling blocks so Error objects are only created when actual errors occur. In high-throughput scenarios this eliminates unnecessary CPU and GC overhead from stack trace capture on every successful call. Continues work from #3169, rebased onto the current modular codebase. Co-authored-by: gunman <gunman@neowiz.com>
c10be68 to
a1711f7
Compare
|
Would it be possible to communicate the caller (application) filename / line-number where the error was triggered? It has become much harder to debug MySQL errors without knowing where in the application code they originated. |
This was the original intent of manual error construction, and this PR should not change this mostly Could you help testing this @chrisveness ? Create a simple repro case, and compare stack trace before / after this version Also maybe we need to reevaluate if we still have to do this manually - v8 captures async stack traces manually, maybe we can rely on that |
|
Create file import mysql from 'mysql2/promise';
const connection = await mysql.createConnection({
host: 'localhost',
user: 'user',
database: 'db',
password: 'pw',
});
const sql = 'SELECT nosuchcolumn FROM information_schema.tables';
const [ results, fields ] = await connection.query(sql);
console.log(results);
await connection.end();Run: Gives: Then run: Gives: ... which has no reference to bad-query.js line number. (Sometimes it might be straightforward to grep for the SQL string, but not always). |
|
yeah, I should have reviewed this a bit more carefully I'll see current state of native async stack traces but might actually revert this change if there's no better option We could have this optional with the flag, but I'm not sure how big of a perf impact are we talking realistically compared to a full round trip of an average query. If it's really small flag not worth it and I'll just revert |
|
I think it's best to revert. Alternatives considered: 'lightweight stacks' - const obj = {};
Error.captureStackTrace(obj, captureCallSite);Same performance as native async stack traces - only captures |
|
@chrisveness reverted, releasing as v3.22.1 |
…rs (sidorares#4257) Move `new Error()` instantiation from the call site into error handling blocks so Error objects are only created when actual errors occur. In high-throughput scenarios this eliminates unnecessary CPU and GC overhead from stack trace capture on every successful call. Continues work from sidorares#3169, rebased onto the current modular codebase. Co-authored-by: gunman <gunman@neowiz.com>
…troduced by sidorares#4257 (sidorares#4265) This reverts commit ab131de.
Summary
new Error()instantiation from function scope into error handling blocks across all promise wrapper modules, so Error objects are only created when actual errors occurtest-async-stackto verify error propagation without asserting on caller-frame line numbers (which are no longer captured at the call site)Trade-off
The previous pattern captured a stack trace at the call site on every invocation so that, on error, the rejected Error would include the caller's frame. This is useful for debugging but expensive: in benchmarks over 280k calls the eager
new Error()was a measurable CPU hot-spot. After this change, stack traces on errors still exist but originate from the internal callback rather than the caller.Changes
lib/promise/make_done_cb.jslocalErrparameter; createErrorinsideif (err)lib/promise/connection.jsnew Error()from all 10 methodslib/promise/pool.jsquery,execute,endlib/promise/pool_cluster.jsquery,executelib/promise/prepared_statement_info.jsexecutepromise.jscreateConnectionPromise,PromisePoolClustermethodstest/…/test-async-stack.test.mtsContinues #3169 — rebased onto the current modular codebase.
Co-authored-by: gunman gunman@neowiz.com
Test plan
npm run lint— passesactCI Build (typecheck + circular imports) — passesactCI Linux (Node 22, mysql:8.3, SSL=0, Compression=0) — 205/205 tests pass