⚡️ Fix FormData.readAsBytes excessive memory usage with large payloads#2544
Conversation
Replace the O(n²) `reduce`+spread approach with a single pre-allocated `Uint8List` and `setRange` writes. Since `FormData.length` is already known before finalization, we allocate the exact result buffer upfront and stream each chunk directly into it — no intermediate copies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is |
PR Review: Fix
|
| Case | Old behavior | New behavior |
|---|---|---|
| Stream > length | Silently oversized buffer | setRange throws RangeError (fail-fast) |
| Stream < length | Silently undersized buffer | Trailing bytes left as zero |
The "stream > length" case is a fail-fast improvement — the old code silently produced corrupted data. The "stream < length" case still fails silently (zero padding). Optional suggestion: add assert(offset == result.length) before return to catch length mismatches in debug mode. Not required, but worth considering.
2. Test helper _indexOfSubList
Hand-rolled sublist search — Dart's stdlib has no direct equivalent, so this is acceptable. Since headers/boundaries are ASCII, utf8.decode + String.indexOf would also work, but the current approach is more robust for mixed binary content. Fine as-is.
3. Test coverage
The new test precisely targets the O(n²) scenario — a chunked stream upload. Assertions are thorough: total length, header location, payload integrity, and trailing boundary. The expectedPayload uses (i ~/ chunkSize) % 256 to produce distinguishable per-chunk content, which would catch chunk reordering.
Missing Coverage
- No
readAsBytestest for an emptyFormData(boundary-only) — existing tests cover field-only scenarios, low risk. - No
readAsBytestest for stream error propagation — pre-existing gap, can be addressed separately.
Recommendation
APPROVE. The fix is correct, well-tested, and low-risk. Optional nit: add assert(offset == result.length) to surface length mismatches in debug builds.
⚠️ This review was authored with AI assistance. The reviewer (a human) directed the analysis and validated the findings.Agent: Devin (Cognition) — interactive command-line agent
Model: GLM-5.2 High
The previous implementation used
reduce((a, b) => Uint8List.fromList([...a, ...b])), which re-allocates and copies all accumulated bytes on every chunk — O(n²) memory in total.Since
FormData.lengthis already computed before finalization, we can pre-allocate the exact result buffer and write each chunk in-place withsetRange, bringing memory usage down to O(n) with a single allocation.Related: #2506