13 KiB
Parser Span Retention Implementation Plan
For Codex: REQUIRED SUB-SKILLS: Use
using-git-worktreesto create an isolated worktree before making changes,executeplanto implement this plan task-by-task, andfinishing-a-development-branchto merge the verified work back tomainwhen implementation is complete.
Goal: Reduce parser hot-path allocations by keeping protocol fields and payloads in byte-oriented views until a caller explicitly needs materialized string or copied byte[] values.
Architecture: Introduce a byte-first parser representation alongside the current ParsedCommand contract, then migrate NatsClient and adjacent hot paths to consume the new representation without changing wire behavior. Preserve compatibility through an adapter layer so functional parity stays stable while allocation-heavy paths move to spans, pooled buffers, and sequence slices.
Tech Stack: .NET 10, C#, System.Buffers, System.IO.Pipelines, ReadOnlySequence<byte>, SequenceReader<byte>, xUnit, existing benchmark test harness.
Scope Anchors
- Primary source:
src/NATS.Server/Protocol/NatsParser.cs - Primary consumer:
src/NATS.Server/NatsClient.cs - Existing parser tests:
tests/NATS.Server.Core.Tests/ParserTests.cs - Existing snippet/parity tests:
tests/NATS.Server.Core.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs - Documentation to update:
Documentation/Protocol/Parser.md - Benchmark project:
tests/NATS.Server.Benchmark.Tests/NATS.Server.Benchmark.Tests.csproj - Benchmark run instructions:
tests/NATS.Server.Benchmark.Tests/README.md - Benchmark comparison report:
benchmarks_comparison.md
Task 0: Create an isolated git worktree for the parser optimization work
Files:
- Verify:
.worktrees/ - Modify if needed:
.gitignore
Step 1: Verify the preferred worktree directory is available and ignored
- Check that
.worktrees/exists and is ignored by git before creating a project-local worktree. - If
.worktrees/is not ignored, add it to.gitignore, then commit that repository hygiene fix before continuing.
Step 2: Create the feature worktree on a codex/ branch
- Run:
git worktree add .worktrees/codex-parser-span-retention -b codex/parser-span-retention
cd .worktrees/codex-parser-span-retention
- Expected: a new isolated worktree exists at
.worktrees/codex-parser-span-retentionon branchcodex/parser-span-retention.
Step 3: Verify the worktree starts from a clean, passing baseline
- Run:
dotnet test tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj -c Release - Expected: PASS. If this fails, stop and resolve or explicitly confirm whether to proceed from a failing baseline.
Step 4: Commit any required worktree setup fix
- Only if
.gitignorechanged, run:
git add .gitignore
git commit -m "chore: ignore local worktree directories"
Task 1: Freeze parser behavior and add allocation-focused tests
Files:
- Modify:
tests/NATS.Server.Core.Tests/ParserTests.cs - Modify:
tests/NATS.Server.Core.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs - Create:
tests/NATS.Server.Core.Tests/Protocol/ParserSpanRetentionTests.cs
Step 1: Add failing tests for byte-first parser behavior
- Cover
PUB,HPUB,CONNECT, andINFOwith assertions that the new parser path can expose field data without forcing immediatestringmaterialization. - Add split-payload cases to prove the parser preserves pending payload state across reads.
Step 2: Add compatibility tests for existing ParsedCommand behavior
- Keep current semantics for
Type,Subject,ReplyTo,Queue,Sid,HeaderSize, andPayload. - Ensure malformed protocol inputs still throw
ProtocolViolationExceptionwith existing snippets/messages.
Step 3: Run targeted tests to verify the new tests fail first
- Run:
dotnet test tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj --filter "FullyQualifiedName~ParserTests|FullyQualifiedName~ParserSpanRetentionTests|FullyQualifiedName~ProtocolParserSnippetGapParityTests" -c Release - Expected: FAIL in the newly added parser span-retention tests only.
Step 4: Commit the failing-test baseline
- Run:
git add tests/NATS.Server.Core.Tests/ParserTests.cs tests/NATS.Server.Core.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs tests/NATS.Server.Core.Tests/Protocol/ParserSpanRetentionTests.cs
git commit -m "test: lock parser span-retention behavior"
Task 2: Introduce byte-oriented parser view types
Files:
- Create:
src/NATS.Server/Protocol/ParsedCommandView.cs - Modify:
src/NATS.Server/Protocol/NatsParser.cs
Step 1: Add a hot-path parser view contract
- Create a
ref structor smallreadonly structrepresentation for command views that can carry:- operation kind
- subject/reply/queue/SID as spans or sequence-backed views
- payload as
ReadOnlySequence<byte>orReadOnlyMemory<byte>when contiguous - header size and max-messages metadata
Step 2: Add an adapter to the current ParsedCommand shape
- Keep the public/internal
ParsedCommandentry point usable for existing consumers and tests. - Centralize materialization so
Encoding.ASCII.GetString(...)andToArray()happen in one adapter layer instead of inside every parse branch.
Step 3: Re-run parser tests
- Run:
dotnet test tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj --filter "FullyQualifiedName~Parser" -c Release - Expected: FAIL only in branches not yet migrated to the new view path.
Step 4: Commit the parser-view scaffolding
- Run:
git add src/NATS.Server/Protocol/ParsedCommandView.cs src/NATS.Server/Protocol/NatsParser.cs
git commit -m "feat: add byte-oriented parser view contract"
Task 3: Rework control-line parsing and pending payload state
Files:
- Modify:
src/NATS.Server/Protocol/NatsParser.cs
Step 1: Remove early string materialization from control-line parsing
- Change
ParsePub,ParseHPub,ParseSub,ParseUnsub,ParseConnect, andParseInfoto keep raw byte slices in the hot parser path. - Replace
_pendingSubjectand_pendingReplyTostring fields with byte-oriented pending state.
Step 2: Avoid unconditional payload copies
- Update
TryReadPayload()so single-segment payloads can flow through as borrowed memory/slices. - Copy only when the payload is multi-segment or when the compatibility adapter explicitly requires a standalone buffer.
Step 3: Replace repeated tiny literal allocations
- Stop using per-call
u8.ToArray()-style buffers for CRLF and other fixed protocol tokens inside this parser path. - Add shared static buffers where appropriate.
Step 4: Run targeted regression tests
- Run:
dotnet test tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj --filter "FullyQualifiedName~ParserTests|FullyQualifiedName~ProtocolParserSnippetGapParityTests" -c Release - Expected: PASS.
Step 5: Commit the parser hot-path rewrite
- Run:
git add src/NATS.Server/Protocol/NatsParser.cs
git commit -m "perf: keep parser state in bytes until materialization"
Task 4: Migrate NatsClient to the new parser path without changing behavior
Files:
- Modify:
src/NATS.Server/NatsClient.cs - Modify:
tests/NATS.Server.Core.Tests/ParserTests.cs - Modify:
tests/NATS.Server.Core.Tests/Protocol/ClientProtocolGoParityTests.cs
Step 1: Consume parser views first, materialize only at command handling boundaries
- Update
ProcessCommandsAsyncand any parser call sites so hotPUB/HPUBhandling can read subject, reply, and payload from the byte-oriented representation. - Keep logging/tracing behavior intact, but ensure tracing is the only reason strings are created on trace-enabled paths.
Step 2: Preserve feature parity
- Verify header parsing, payload size checks, connect/info handling, and slow-consumer/error behavior still match current tests.
Step 3: Run consumer-facing protocol tests
- Run:
dotnet test tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj --filter "FullyQualifiedName~ClientProtocolGoParityTests|FullyQualifiedName~ParserTests" -c Release - Expected: PASS.
Step 4: Commit the consumer migration
- Run:
git add src/NATS.Server/NatsClient.cs tests/NATS.Server.Core.Tests/ParserTests.cs tests/NATS.Server.Core.Tests/Protocol/ClientProtocolGoParityTests.cs
git commit -m "perf: consume parser command views in client hot path"
Task 5: Add benchmarks, document the change, run full verification, and refresh the benchmark comparison report
Files:
- Create:
tests/NATS.Server.Benchmark.Tests/Protocol/ParserHotPathBenchmarks.cs - Modify:
Documentation/Protocol/Parser.md - Modify:
benchmarks_comparison.md
Step 1: Add parser-focused benchmark coverage
- Add microbenchmarks for:
PING/PONGPUBHPUB- split payload reads
- Capture throughput and allocation deltas before/after.
Step 2: Update protocol documentation
- Document the new parser view + adapter split, why strings are deferred, and where payload copying is still intentionally required.
Step 3: Run full verification
- Run:
dotnet test tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj -c Release - Run:
dotnet test tests/NATS.Server.Benchmark.Tests/NATS.Server.Benchmark.Tests.csproj --filter "FullyQualifiedName~Parser" -c Release - Expected: PASS; benchmark output shows reduced allocations relative to the baseline run.
Step 4: Run the full benchmark suite per the benchmark project README
- Run:
dotnet test tests/NATS.Server.Benchmark.Tests \
--filter "Category=Benchmark" \
-v normal \
--logger "console;verbosity=detailed" 2>&1 | tee /tmp/bench-output.txt
- Expected: benchmark comparison output is captured in
/tmp/bench-output.txt, including the "Standard Output Messages" blocks described intests/NATS.Server.Benchmark.Tests/README.md.
Step 5: Update benchmarks_comparison.md with the new benchmark results
- Extract the comparison blocks from
/tmp/bench-output.txt. - Update
benchmarks_comparison.mdwith the latest msg/s, MB/s, ratio, and latency values. - Update the benchmark date, environment description, Summary table, and Key Observations so they match the new run.
Step 6: Commit the verification, docs, and benchmark report refresh
- Run:
git add tests/NATS.Server.Benchmark.Tests/Protocol/ParserHotPathBenchmarks.cs Documentation/Protocol/Parser.md benchmarks_comparison.md
git commit -m "docs: record parser hot-path allocation strategy"
Task 6: Merge the verified parser work back to main and clean up the worktree
Files:
- No source changes expected
Step 1: Confirm the feature branch is fully verified before merge
- Reuse the verification from Task 5. Do not merge if the core tests, parser benchmark tests, or full benchmark suite run did not complete successfully.
Step 2: Merge codex/parser-span-retention back into main
- Return to the primary repository worktree and run:
git checkout main
git pull
git merge codex/parser-span-retention
- Expected: the parser optimization commits merge cleanly into
main.
Step 3: Re-run verification on the merged main branch
- Run:
dotnet test tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj -c Release - Run:
dotnet test tests/NATS.Server.Benchmark.Tests/NATS.Server.Benchmark.Tests.csproj --filter "FullyQualifiedName~Parser" -c Release - Confirm
benchmarks_comparison.mdreflects the results captured from the Task 5 full benchmark suite. - Expected: PASS on merged
main.
Step 4: Delete the feature branch and remove the worktree
- Run:
git branch -d codex/parser-span-retention
git worktree remove .worktrees/codex-parser-span-retention
- Expected: only
mainremains checked out in the primary workspace, and the temporary parser worktree is removed.
Completion Checklist
- Parser optimization work was implemented in an isolated
.worktrees/codex-parser-span-retentionworktree on branchcodex/parser-span-retention. NatsParserno longer materializes hot-pathstringvalues during parse unless the compatibility adapter requests them.- Single-segment payloads can pass through without an unconditional
byte[]copy. - Existing parser and protocol behavior remains green in core tests.
- Parser-focused benchmark coverage exists in
tests/NATS.Server.Benchmark.Tests/Protocol/. - The full benchmark suite was run using the workflow from
tests/NATS.Server.Benchmark.Tests/README.md. benchmarks_comparison.mdwas updated to reflect the latest benchmark run.Documentation/Protocol/Parser.mdexplains the byte-first parser architecture.- Verified parser optimization commits were merged back into
main.