perf: avoid stack trace capture in NotEnoughDataError#1753
Conversation
`NotEnoughDataError` is thrown and caught as part of normal parsing control flow: every time a parser hits the end of the currently buffered data mid-value, it throws this error and the caller waits for the next chunk before retrying. Because it extended `Error`, every throw captured a stack trace that was never used, making each throw/catch cycle roughly 3x more expensive than necessary. Turn it into a plain class. All catch sites detect it via `instanceof NotEnoughDataError` and it never escapes the parsing internals, so behavior is unchanged. Also add test coverage for the throw/retry contract: direct tests for the read helpers' incomplete-data behavior, and byte-at-a-time parsing tests for the token stream and row parsers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review: perf: avoid stack trace capture in NotEnoughDataError (#1753) Overview Correctness
Performance Minor suggestion (non-blocking) Residual risk (already called out, worth a second look) Test coverage Summary |
Matches the `Result` class above and avoids emitting a class field that is immediately overwritten in the constructor. No measurable performance difference on V8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ReviewNice, well-substantiated change. I audited the diff and cross-checked it against the codebase. Correctness
Code quality
Test coverage
Performance & security
Overall: solid, low-risk perf win with good test coverage and an honest writeup of the tradeoffs. Approving from a code-review standpoint. |
|
🎉 This PR is included in version 20.0.5 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
NotEnoughDataErroris pure parsing control flow: whenever a parser hits the end of the currently buffered data mid-value, it throws this error, and the caller waits for the next chunk before retrying the read. Because the class extendedError, every one of these throws captured a stack trace that was never used — the error never escapes the parsing internals (all 18 catch sites detect it viainstanceof NotEnoughDataError), and neithermessagenorstackis ever read.This PR turns it into a plain class that no longer extends
Error, with a comment explaining why. Behavior is unchanged: theinstanceofchecks and thebyteCountcontract work exactly as before.Performance
Measured at three levels (Node 24, Linux):
Microbenchmark — cost of one
throw new NotEnoughDataError(n)/catchcycle:extends Error)Parser-level — parsing a stream of 5,000 row tokens (
varchar+intcolumns) delivered in fixed-size chunks viaParser.parseTokens. Smaller chunks mean more throw/retry cycles, since every chunk boundary that lands mid-value triggers one:End-to-end — repeated
SELECTs of 1,000–2,000 row result sets against a real SQL Server with the default 4KB packet size: differences were within run-to-run noise (±3%). At the default packet size a throw only happens roughly once per packet, so the savings are diluted by network and stream overhead; the win grows as packets/chunks shrink relative to row size (e.g. wide rows or values spanning packet boundaries).Behavior considerations
Errorthrow. It is fully internal today (audited all catch sites), and the new tests pin down the throw/retry contract.assert.throws(fn, SomeClass)only matchesErrorsubclasses, so the new tests assert via explicitinstanceofchecks instead.Test coverage
Previously no test referenced
NotEnoughDataErrorat all, and only a single two-buffer split test exercised the retry path. This PR adds:test/unit/token/helpers-test.ts— direct contract tests: read helpers throwNotEnoughDataErrorwith the correctbyteCounton incomplete data (fixed-width and length-prefixed reads), and don't throw when enough data is available.All new tests pass unchanged against the previous implementation (behavior-pinning). Full unit suite: 407 passing.
🤖 Generated with Claude Code