Fix memory layout#9797
Conversation
There was a problem hiding this comment.
Pull request overview
This PR reworks the internal memory layout of ResultDocument (Core) and CompositeResultDocument (Fusion). Cursors are repacked to include a 3-bit chunk-size bucket (12-bit chunk + 14-bit row + 3-bit size), parent/reference fields in DbRow are widened from 27/28 bits to 29 bits and now store packed cursor values rather than linear row indices, and Fusion's DbRow relocates the 4-bit token type to share the int that already holds sourceDocumentId. New ChunkSize enums and cursor unit tests are added for both Core and Fusion.
Changes:
- Introduce
ChunkSizeenum and stamp chunk-size bits into packed cursor values; replaceCursor.Zero/FromIndexwithCreateZero(ChunkSize)andnew Cursor(value)and driveRowsPerChunkper-cursor. - Switch stored parent/reference fields to 29-bit packed cursor values across both
DbRowtypes, relocating Fusion's token type to share storage withsourceDocumentId, and update all writers/readers and root checks to usecursor.Value/IsZero. - Add
ResultDocumentCursorTestsandCompositeResultDocumentCursorTests, and updateCompositeResultDocumentMetaDbTeststo the newCreate(Cursor zero)API and renamedDbRow.Parentaccessor.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/HotChocolate/Core/src/Types/Text/Json/ChunkSize.cs | New chunk-size enum (1K–128K) sized for the cursor's 3 size bits. |
| src/HotChocolate/Core/src/Types/Text/Json/ResultDocument.Cursor.cs | Repacks cursor as int with size/chunk/row, adds Value/Index/IsZero/ChunkSize/RowsPerChunk, removes Zero/FromIndex/ToIndex/ToTotalBytes. |
| src/HotChocolate/Core/src/Types/Text/Json/ResultDocument.DbRow.cs | Splits _locationAndOpRefType into a dedicated 29-bit _location; moves opRefType into _opRefIdAndFlags; renames ParentRow→Parent (29 bits). |
| src/HotChocolate/Core/src/Types/Text/Json/ResultDocument.MetaDb.cs | Threads ChunkSize through CreateForEstimatedRows/Append; renames parentRow→parent; reads parents/locations as packed cursor values. |
| src/HotChocolate/Core/src/Types/Text/Json/ResultDocument.WriteTo.cs | Root cursor obtained via CreateZero(_chunkSize). |
| src/HotChocolate/Core/src/Types/Text/Json/ResultDocument.cs | Stores _chunkSize = Size128K; passes cursor Value as parent; drops the old IsRoot heuristic and ToIndex helper. |
| src/HotChocolate/Core/src/Types/Text/Json/ResultElement.cs | Replaces _cursor == Cursor.Zero with _cursor.IsZero. |
| src/HotChocolate/Core/test/Types.Tests/Text/Json/ResultDocumentCursorTests.cs | New cursor unit tests (chunk/row/size round-trip, AddRows, Index, IsZero, DbRow packing). |
| src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/ChunkSize.cs | Fusion mirror of ChunkSize enum. |
| src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.Cursor.cs | Fusion cursor mirror of the new packed layout. |
| src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.DbRow.cs | Relocates token type into _sourceAndType alongside sourceDocumentId; renames ParentRow→Parent and widens to 29 bits. |
| src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.MetaDb.cs | New Create(Cursor zero) factory; specialized append paths write packed parents and write the token type at the new offset; reads use 29-bit masks and rebuild cursors via new Cursor(value). |
| src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs | Root and reference resolution use new cursor APIs. |
| src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.cs | Stores _chunkSize, switches parent/path traversal to packed cursor values and IsZero, drops the IsRoot heuristic in WriteStartObject/Array. |
| src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs | Replaces Cursor.Zero comparisons with IsZero. |
| src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Text/Json/CompositeResultDocumentCursorTests.cs | New Fusion cursor tests, including parent/reference round-trip and token+sourceDocumentId packing. |
| src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Text/Json/CompositeResultDocumentMetaDbTests.cs | Updates tests to MetaDb.Create(Cursor.CreateZero(...)), CreateCursor helper, TotalBytes, and renamed DbRow.Parent. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var c0 = _metaDb.AppendNull(0); | ||
| var c1 = _metaDb.AppendNull(c0.Index); | ||
| var c2 = _metaDb.AppendNull(c1.Index); | ||
|
|
||
| // Assert | ||
| Assert.Equal(0, c0.Index); | ||
| Assert.Equal(1, c1.Index); | ||
| Assert.Equal(2, c2.Index); | ||
| Assert.Equal(0, _metaDb.Get(c0).ParentRow); | ||
| Assert.Equal(c0.Index, _metaDb.Get(c1).ParentRow); | ||
| Assert.Equal(c1.Index, _metaDb.Get(c2).ParentRow); | ||
| Assert.Equal(0, _metaDb.Get(c0).Parent); | ||
| Assert.Equal(c0.Index, _metaDb.Get(c1).Parent); | ||
| Assert.Equal(c1.Index, _metaDb.Get(c2).Parent); |
Code Coverage OverviewLanguages: C# C# / code-coverage/dotnetThe overall coverage in the branch remains at 49%, unchanged from the branch. Show a code coverage summary of the most impacted files.
Updated |
| // arrange | ||
| // Keep-alive comment blocks appear before, between, and after the events and are ignored. | ||
| var ms = new MemoryStream(); | ||
| var sw = new StreamWriter(ms); |
| // An event name that is neither "next" nor "complete" is not part of the GraphQL over SSE | ||
| // protocol and is ignored, even when it carries data. | ||
| var ms = new MemoryStream(); | ||
| var sw = new StreamWriter(ms); |
| await using var fixture = await RoutingTestFixture.CreateAsync(); | ||
| var context = fixture.CreateContext(); | ||
| await using var client = new HttpSourceSchemaClient( | ||
| GraphQLHttpClient.Create(new HttpClient()), |
| { | ||
| // arrange | ||
| // The event payload is split across several data lines that are joined with a single line feed. | ||
| var ms = new MemoryStream(); |
| // arrange | ||
| // The event payload is split across several data lines that are joined with a single line feed. | ||
| var ms = new MemoryStream(); | ||
| var sw = new StreamWriter(ms); |
| ms.Position = 0; | ||
|
|
||
| var handler = new MockHttpMessageHandler(ms, "text/event-stream"); | ||
| using var client = new DefaultGraphQLHttpClient(new HttpClient(handler)); |
| // arrange | ||
| await using var fixture = await BatchBufferTestFixture.CreateAsync(); | ||
| using var graphQLClient = new DefaultGraphQLHttpClient( | ||
| new HttpClient(new BatchHandler()), |
| var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK) | ||
| { | ||
| Content = new StringContent( | ||
| """ | ||
| [ | ||
| { | ||
| "data": { | ||
| "field": "a" | ||
| }, | ||
| "requestIndex": 0 | ||
| }, | ||
| { | ||
| "data": { | ||
| "field": "b" | ||
| }, | ||
| "requestIndex": 1 | ||
| } | ||
| ] | ||
| """, | ||
| Encoding.UTF8, | ||
| "application/json") | ||
| }; |
No description provided.