Repository navigation
Commit 517b4ef
authored
fix(odsp-driver): remove invalid byteOffset === 0 assumption (#28194)
## Description
Fixes compact snapshot parsing when the input is a valid `Uint8Array`
view with a non-zero `byteOffset`.
The ODSP compact snapshot parser asserted that its input must begin at
offset zero:
```ts
assert(input.byteOffset === 0, 0x3e8 /* code below assumes no offset */);
```
The affected snapshots were valid, but their binary data was represented
by a view into a larger backing buffer. The assertion closed the Fluid
container during loading and caused eight of the 29 Office eDiscovery
fixtures to fail with assertion `0x3e8`.
### Update: Why this started failing now
The assertion has existed since 2022, but the relevant inputs previously
happened to have a zero `byteOffset`, leaving the invalid assumption
hidden.
The failure started after [Node.js 24.18.0 increased the default
`Buffer.poolSize` from 8 KiB to 64
KiB](nodejs/node#63597). Node uses this pool for
`Buffer.allocUnsafe()` allocations smaller than half the pool size. This
increased the pooling threshold from approximately 4 KiB to 32 KiB.
The Office eDiscovery export path reads binary snapshots using:
```ts
fs.readFileSync(filePath)
```
Node's `fs.readFileSync()` allocates the returned `Buffer` using
`Buffer.allocUnsafe(fileSize)`. A Node `Buffer` is also a `Uint8Array`
view.
Before Node 24.18:
- The default buffer pool was 8 KiB.
- Allocations of approximately 4 KiB or larger bypassed the pool.
- Relevant snapshot reads therefore received dedicated backing buffers,
normally with `byteOffset === 0`.
Beginning with Node 24.18:
- The default buffer pool is 64 KiB.
- Allocations smaller than approximately 32 KiB can come from the shared
pool.
- Those buffers can be slices of the shared backing allocation and
therefore have a valid non-zero `byteOffset`.
Node 24.20 and 24.21 inherited this behavior. This explains why the
failure appeared when the Office External Partner pipeline moved from
Node 24.11 to newer Node 24 releases.
The Office External Partner integration pipeline selected Node using
this floating range:
```yaml
- task: UseNode@1
inputs:
version: ">=24.11.0 <25.0.0"
```
The range selects the newest available matching Node 24 release, not the
lowest version in the range. Normal Office CI did not initially
reproduce the issue because its managed build image still provided Node
24.11.0.
Controlled runs confirmed the Node version as the differentiating
variable:
- Node 24.21.0 with Fluid build 422789: eight eDiscovery failures.
- Node 24.11.0 with the same Fluid build and Office test code: all 29
eDiscovery tests passed.
Node did not introduce malformed snapshot data or change the
`Uint8Array` contract. The larger buffer pool caused more file reads to
use valid offset views, exposing Fluid's existing assumption about the
backing-buffer layout.
Pinning Node 24.11 would avoid the expanded pooling threshold and can
serve as a temporary mitigation. However, it would leave Fluid dependent
on an allocation detail that Node does not guarantee. Other consumers
can also provide valid non-zero-offset `Uint8Array` views independently
of Node's file-reading behavior.
### Root cause
The assertion was originally added by Vlad Sudzilouski in #12058 as part
of a string-parsing performance optimization. The PR discusses
null-character handling and differences between browser and Node string
decoding, but it does not document why a zero byte offset was required.
The parser does not require a zero-offset backing buffer:
- `Uint8Array` indexing is relative to the view.
- `Uint8Array.subarray()` is relative to the view.
- `ReadBuffer` positions and reads are relative to the supplied
`Uint8Array`.
- Existing `BlobShallowCopy` handling explicitly accounts for
`data.byteOffset`.
For example, if a view begins at offset 100 in its backing buffer,
`input[0]` still reads the first byte of that view, not byte zero of the
backing buffer. The string positions recorded by the parser are also
relative to that same view.
A non-zero offset is therefore a supported memory layout rather than
evidence of a malformed or corrupted snapshot. The assertion rejected
valid input without providing additional snapshot-integrity protection.
This change removes that assertion. It does not remove validation of
snapshot structure, string lengths, tree markers, skipped ranges, or
unexpected end-of-file conditions.
Copying the input into a new zero-offset buffer would also avoid the
assertion, but it would introduce an unnecessary full-snapshot
allocation and preserve the incorrect parser restriction.
### Changes
- Remove the `byteOffset === 0` assertion from compact snapshot string
loading.
- Add a regression test that:
- Serializes a compact snapshot.
- Embeds it inside a larger backing buffer.
- Passes a non-zero-offset `subarray()` to `TreeBuilder.load()`.
- Confirms that the test input actually has a non-zero `byteOffset`.
- Verifies that the resulting strings, blobs, and tree match the
original snapshot.
### Reproduction and integration validation
The issue was isolated using controlled Office External Partner pipeline
runs:
| Office build | Node | Fluid build | eDiscovery result |
| --- | --- | --- | --- |
|
[55007732](https://office.visualstudio.com/OC/_build/results?buildId=55007732&view=results)
| 24.21.0 | 422789 | 21 passed, 8 failed with `0x3e8` |
|
[55015480](https://office.visualstudio.com/OC/_build/results?buildId=55015480&view=results)
| 24.11.0 | 422789 | 29/29 passed |
|
[55069760](https://office.visualstudio.com/OC/_build/results?buildId=55069760&view=results)
| 24.21.0 | 422948 with this fix | 29/29 passed; pipeline succeeded |
The first two runs used the same Fluid build and Office test code, with
only the Node version changed. This confirmed that the larger buffer
pool in newer Node versions exposes the existing parser assumption.
The final run restored the triggering Node 24.21.0 environment and used
Fluid build 422948 containing this change. All 29 eDiscovery tests
passed, including the eight fixtures that previously failed. The overall
integration pipeline also succeeded.
The ODSP driver build completed successfully, including compilation,
linting, API checks, and test compilation. The Tree Representation test
suite passed in both ESM and CommonJS configurations.
A Fluid Real Service End to End Tests run was also queued using client
build 422948 to exercise the change against an actual ODSP tenant:
- [Fluid Real Service E2E build
423091](https://dev.azure.com/fluidframework/internal/_build/results?buildId=423091&view=results)
## Reviewer Guidance
Please focus on whether any compact snapshot parsing operation depends
on the backing buffer starting at offset zero.
The relevant string positions are relative to the supplied `Uint8Array`
view, and the added regression test exercises the parser using a
non-zero-offset view and compares the fully parsed result with the
original tree.
The original assertion author was Vlad Sudzilouski (`vladsud`) in
#12058. The original PR does not contain an explanation of a required
zero-offset invariant.1 parent c1f54db commit 517b4ef
2 files changed
Lines changed: 17 additions & 1 deletion
File tree
- packages/drivers/odsp-driver/src
- test
Lines changed: 17 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
123 | 140 | | |
124 | 141 | | |
125 | 142 | | |
| |||
Lines changed: 0 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
541 | 541 | | |
542 | 542 | | |
543 | 543 | | |
544 | | - | |
545 | 544 | | |
546 | 545 | | |
547 | 546 | | |
| |||
0 commit comments