Skip to content

Conversation

@jensneuse
Copy link
Member

@jensneuse jensneuse commented Oct 9, 2025

Summary by CodeRabbit

  • New Features

    • Added optional arena-backed constructors and arena-enabled parsing, merging, and set APIs for lower-allocation workflows.
  • Refactor

    • Migrated public APIs to accept an arena parameter (breaking-change surface) and removed legacy pooling/caching internals and per-parse caches.
  • Tests

    • Removed many legacy pool-related tests/benchmarks; added arena-aware benchmarks and updated tests/examples to use arenas.
  • Chores

    • Bumped Go toolchain to 1.25, refreshed dependencies, and updated CI workflows.

@coderabbitai
Copy link

coderabbitai bot commented Oct 9, 2025

Walkthrough

Deleted the internal Arena and pooling code, integrated external github.com/wundergraph/go-arena, threaded arena through parsing, merging, updating, and utility APIs, added arena-backed Value constructors, removed related tests/benchmarks, and bumped Go/tooling and CI dependencies.

Changes

Cohort / File(s) Summary
Remove internal Arena implementation
arena.go
Deleted internal Arena type and all its methods (Reset, NewObject, NewArray, NewString/Bytes, NewNumber*, NewNull/NewTrue/NewFalse).
Remove pools
pool.go
Deleted ParserPool, ArenaPool types and their methods (Get/Put/PutIfSizeLessThan).
Remove arena/pool tests & benchmarks
arena_test.go, arena_timing_test.go, pool_test.go
Removed tests and benchmarks that exercised the deleted Arena/pools and the Arena benchmarks.
Add arena-backed value constructors
values.go
New constructors: StringValue, StringValueBytes, IntValue, FloatValue, NumberValue, TrueValue, FalseValue, ObjectValue, ArrayValue — all allocate via arena.
Parser arena integration
parser.go, scanner.go
Thread arena.Arena through parsing APIs and internals (added ParseWithArena/ParseBytesWithArena, changed parseValue/parseArray/parseObject signatures), switched allocations to arena.Allocate/arena.SliceAppend, changed Object.kvs to []*kv, and removed scanner cache field.
Handy/public parsing helpers & tests
handy.go, parser_test.go
Replaced cache-based ParseWithoutCache/ParseBytesWithoutCache with ParseWithArena/ParseBytesWithArena, removed handyPool usage, updated tests, and added arena vs non-arena benchmarks.
Merge API changes
mergevalues.go, mergevalues_test.go
Added arena.Arena as first parameter to MergeValues and MergeValuesWithPath, propagated arena through merge logic; tests updated to pass leading nil where used.
Update/Set operations arena-aware
update.go, update_example_test.go, update_test.go
Changed Object.Set, Value.Set, Value.SetArrayItem to accept arena.Arena, used arena.SliceAppend and arena-aware kv allocation; examples/tests updated to create/pass arenas and added a nil-arena test.
Utility call-site adjustments & validation tweak
util.go, validate.go
Updated call sites to pass nil for new Set/SetArrayItem signatures; minor control-flow change in validateString (break→continue).
Parser timing / benchmark rework
parser_timing_test.go
Reworked benchmarks to use fixture helpers, switched pool usage to direct Parser instances, renamed/expanded benchmark coverage and data fixtures.
Remove arena timing benchmark
arena_timing_test.go
Deleted arena-specific benchmark file and helpers.
Go module and CI updates
go.mod, .github/workflows/ci.yml
Bumped Go toolchain to 1.25, added github.com/wundergraph/go-arena dependency, updated testify to v1.11.1, and upgraded CI actions / golangci-lint versions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly highlights the core feature change—enhancing the arena implementation—and aligns directly with the replacement of the internal Arena and integration of the external go-arena library across the codebase.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
parser.go (1)

349-433: Add nil-arena guard in unescapeStringBestEffort
AllocateSlice and SliceAppend require a non-nil *Arena (passing nil panics). Branch on a == nil to use built-in make/append for heap fallback (or introduce helper wrappers like MakeSliceOrHeap/AppendOrHeap).

🧹 Nitpick comments (1)
parser.go (1)

596-599: Optional: arena-aware slow paths for Get/Visit.

Get/Visit unescape with nil arena, which may allocate on the heap and add GC pressure. If these are hot in arena-backed flows, consider arena-aware variants (e.g., GetWithArena/VisitWithArena) or plumbing arena into these calls.

Also applies to: 616-618

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bf14e2 and 2616cd7.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • arena.go (0 hunks)
  • arena_test.go (0 hunks)
  • arena_timing_test.go (0 hunks)
  • go.mod (1 hunks)
  • handy.go (9 hunks)
  • mergevalues.go (5 hunks)
  • mergevalues_test.go (23 hunks)
  • parser.go (15 hunks)
  • parser_test.go (4 hunks)
  • parser_timing_test.go (0 hunks)
  • pool.go (0 hunks)
  • pool_test.go (0 hunks)
  • scanner.go (1 hunks)
  • update.go (3 hunks)
  • update_example_test.go (2 hunks)
  • update_test.go (5 hunks)
  • util.go (2 hunks)
💤 Files with no reviewable changes (6)
  • pool_test.go
  • pool.go
  • parser_timing_test.go
  • arena.go
  • arena_test.go
  • arena_timing_test.go
🧰 Additional context used
🧬 Code graph analysis (8)
parser_test.go (2)
parser.go (1)
  • Parser (38-40)
handy.go (2)
  • Parse (125-128)
  • ParseWithArena (130-133)
update.go (1)
parser.go (4)
  • Object (511-514)
  • Value (629-634)
  • TypeObject (688-688)
  • TypeArray (691-691)
mergevalues_test.go (2)
mergevalues.go (4)
  • MergeValues (16-111)
  • ErrMergeDifferentTypes (11-11)
  • ErrMergeDifferingArrayLengths (12-12)
  • MergeValuesWithPath (113-127)
handy.go (1)
  • MustParse (139-145)
handy.go (1)
parser.go (2)
  • Parser (38-40)
  • Value (629-634)
mergevalues.go (1)
parser.go (2)
  • Value (629-634)
  • TypeObject (688-688)
update_test.go (2)
parser.go (1)
  • Object (511-514)
handy.go (1)
  • MustParse (139-145)
parser.go (1)
handy.go (2)
  • ParseWithArena (130-133)
  • ParseBytesWithArena (155-158)
update_example_test.go (1)
handy.go (1)
  • MustParse (139-145)
🪛 GitHub Check: Linters
handy.go

[failure] 130-130:
undefined: arena (typecheck)


[failure] 155-155:
undefined: arena (typecheck)

mergevalues.go

[failure] 16-16:
undefined: arena (typecheck)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CI Success
🔇 Additional comments (15)
scanner.go (1)

64-64: LGTM! Consistent with arena-based parsing pattern.

The change correctly passes nil as the arena parameter to parseValue, indicating default heap allocation behavior. This aligns with the broader refactor to support arena-based memory management while maintaining backward compatibility.

parser_test.go (2)

100-100: LGTM! Test correctly uses nil arena for default behavior.

The test properly passes nil as the arena parameter to unescapeStringBestEffort, maintaining the test's focus on string unescaping logic while accommodating the new arena-aware signature.


1292-1318: Excellent benchmark coverage for arena performance comparison.

The two benchmarks effectively measure:

  1. BenchmarkParse: Baseline performance with standard heap allocation
  2. BenchmarkParseArena: Arena-based allocation with proper arena reset between iterations

The arena benchmark correctly:

  • Pre-allocates a 2MB arena buffer (WithMinBufferSize(1024 * 1024 * 2))
  • Resets the arena between iterations (a.Reset())
  • Measures the same workload for fair comparison

This provides clear performance data to justify the arena integration.

util.go (3)

35-35: LGTM! Correctly passes nil for parent arena.

The call array.SetArrayItem(nil, len(items), value) properly passes nil as the arena/parent parameter, indicating default allocation behavior when appending to an array.


44-44: LGTM! Consistent arena parameter usage.

The Set(nil, path[i], child) call correctly passes nil as the arena parameter when creating intermediate path nodes in the SetValue function.


48-48: LGTM! Final set operation uses nil arena.

The final Set(nil, path[len(path)-1], value) call correctly completes the path-based value assignment with default allocation behavior.

update_example_test.go (1)

53-64: LGTM! Example correctly demonstrates arena-based API usage.

The example effectively shows:

  1. Creating a MonotonicArena instance (line 53)
  2. Passing the arena to all Set operations (lines 55, 57, 61, 63)
  3. Using arena for both direct and chained Get(...).Set(...) calls

This provides clear guidance for users adopting the arena-based API while maintaining expected output.

update_test.go (2)

12-56: LGTM! Test correctly uses arena for object mutations.

The test properly:

  1. Creates an arena instance (line 12)
  2. Passes the arena to Set operations (lines 39, 56)
  3. Handles nil object/value cases gracefully (lines 54-56)

The arena usage is consistent and maintains test correctness.


61-107: LGTM! Comprehensive test coverage for arena-based value operations.

The test effectively validates:

  1. Arena-based Set operations on values (line 87)
  2. Array item updates with arena (lines 91, 94)
  3. Nil value handling (lines 103-106)

The use of a separate arena instance (ar) and consistent parameter passing demonstrates proper API usage.

mergevalues_test.go (1)

14-256: LGTM! Comprehensive test coverage maintained with arena-aware API.

All tests consistently pass nil as the arena parameter to MergeValues and MergeValuesWithPath, demonstrating backward-compatible default behavior. The test cases cover:

  • Nil value handling (lines 14, 25)
  • Type mismatches (lines 36, 44, 53)
  • Various data types (numbers, floats, arrays, objects, strings)
  • Array length validation (line 143)
  • Boolean merging (lines 159, 168)
  • Null handling (lines 177, 186, 192)
  • Nested structures (lines 198, 207)
  • Path-based merging (lines 234, 240, 249)

The consistent nil arena usage indicates the tests focus on merge logic rather than arena allocation behavior.

mergevalues.go (2)

16-126: LGTM! Arena parameter correctly threaded through merge operations.

The refactor properly:

  1. Adds arena.Arena parameter to both exported functions (lines 16, 113)
  2. Passes arena to unescapeKeys (lines 38-39)
  3. Uses arena in Set operations (lines 45, 53, 122, 125)
  4. Threads arena through recursive MergeValues calls (lines 48, 70, 115, 126)

The arena integration is consistent and maintains the existing merge logic while enabling arena-based memory management.


6-7: Static analysis error resolved: arena import is correct and go build ./... succeeds.

go.mod (2)

7-7: Verify go-arena pseudo-version dependency
The module path github.com/wundergraph/go-arena v0.0.0-20251008210416-55cb97e6f68f isn’t found publicly. Confirm this is the correct, actively maintained repo (or a private module) and, if possible, switch to a tagged release to ensure API stability, semantic versioning and reproducibility.


3-3: No changes needed for Go version requirement
Go 1.25 is available (go1.25.1), so the go.mod bump is valid.

parser.go (1)

52-55: Arena-plumbed parse path looks good.

ParseWithArena/ParseBytesWithArena and internal parse(a, ...) correctly propagate arena through parseValue/parseArray/parseObject.

Also applies to: 66-68, 70-82

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

23-27: Update GitHub Actions to latest versions.

Static analysis indicates that the action versions are outdated:

  • actions/setup-go@v4 should be updated to @v5
  • golangci/golangci-lint-action@v3 should be updated to @v4

Apply these diffs to update the actions:

     - name: Check out code into the Go module directory
       uses: actions/checkout@v3
-    - name: Set up Go ${{ matrix.go }}
-      uses: actions/setup-go@v4
+    - name: Set up Go ${{ matrix.go }}
+      uses: actions/setup-go@v5
       with:
         go-version: ^${{ matrix.go }}
       id: go
     - name: Run linters
-      uses: golangci/golangci-lint-action@v3
+      uses: golangci/golangci-lint-action@v4
       with:
         version: v2.5.0
         args: --timeout=3m

Also applies to: 45-47

🧹 Nitpick comments (1)
parser.go (1)

206-211: Consider larger initial array capacity.

The array parsing allocates the initial slice with capacity 1 (arena.AllocateSlice[*Value](a, 1, 1)), which means arena.SliceAppend will need to reallocate and copy when the second element is added. For better performance, consider starting with a larger capacity (e.g., 4 or 8) to reduce allocations for small-to-medium arrays.

Apply this diff to use a larger initial capacity:

     if arr.a == nil {
-      arr.a = arena.AllocateSlice[*Value](a, 1, 1)
+      arr.a = arena.AllocateSlice[*Value](a, 1, 4)
       arr.a[0] = v
     } else {
       arr.a = arena.SliceAppend(a, arr.a, v)
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2616cd7 and 1193498.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml (2 hunks)
  • parser.go (16 hunks)
  • parser_timing_test.go (0 hunks)
  • validate.go (1 hunks)
  • values.go (1 hunks)
💤 Files with no reviewable changes (1)
  • parser_timing_test.go
✅ Files skipped from review due to trivial changes (1)
  • validate.go
🧰 Additional context used
🧬 Code graph analysis (2)
parser.go (1)
handy.go (2)
  • ParseWithArena (130-133)
  • ParseBytesWithArena (155-158)
values.go (1)
parser.go (7)
  • Value (629-634)
  • TypeString (694-694)
  • TypeNumber (697-697)
  • TypeTrue (700-700)
  • TypeFalse (703-703)
  • TypeObject (688-688)
  • TypeArray (691-691)
🪛 actionlint (1.7.7)
.github/workflows/ci.yml

41-41: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


45-45: the runner of "golangci/golangci-lint-action@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🔇 Additional comments (8)
values.go (3)

23-35: LGTM!

The numeric value constructors correctly allocate the Value in the arena and format the numbers as strings. While fmt.Sprintf allocates the formatted string on the heap rather than in the arena, this is acceptable because:

  • The string representations are typically small
  • The Value struct itself is arena-allocated, which is the primary benefit
  • String storage optimization would add significant complexity

56-66: LGTM!

The ObjectValue and ArrayValue constructors correctly allocate arena-based Value instances with appropriate type tags. The o and a fields are left uninitialized, which is correct because they are lazily initialized when elements are added (as seen in parseArray and parseObject in parser.go).


9-54: LGTM!

The string, number, and boolean value constructors are correctly implemented:

  • Arena-based allocation using arena.Allocate[Value](a)
  • Appropriate type tags set via v.t
  • String fields properly assigned where needed
  • StringValueBytes correctly uses b2s for byte-to-string conversion
parser.go (4)

47-82: LGTM!

The parser methods correctly implement both backward-compatible (non-arena) and new arena-enabled parsing paths:

  • Parse() and ParseBytes() maintain backward compatibility by passing nil arena
  • ParseWithArena() and ParseBytesWithArena() provide new arena-enabled entry points
  • All methods properly delegate to the internal parse() function

512-512: LGTM with performance note.

The change from kvs []kv to kvs []*kv enables proper arena-based allocation of key-value pairs. While this adds a level of indirection (which can impact cache locality), it's necessary for arena semantics where each allocation is tracked by the arena for bulk deallocation.

Also applies to: 553-559


349-432: LGTM!

The unescapeStringBestEffort function has been correctly converted to arena-based allocation:

  • Reasonable capacity estimation (len(s) + 4) to minimize reallocations
  • Consistent use of arena.SliceAppend for all byte slice operations
  • Preserves the original logic while integrating arena semantics

597-597: Verify nil arena behavior in unescapeKeys: ensure passing nil to o.unescapeKeys (in Object.Get/Visit) won’t panic—confirm go-arena falls back to heap allocations when Arena is nil or add appropriate nil checks and tests.

.github/workflows/ci.yml (1)

15-15: Configuration uses valid Go version. Go 1.25.0 was released on August 12 2025, with patch 1.25.2 on October 7 2025—no updates required.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1193498 and f1b4b27.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml (2 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
.github/workflows/ci.yml (1)

47-49: golangci-lint binary version is invalid (v2.x does not exist).

The action will fail downloading v2.5.0. Drop the override or pin to a real v1.x binary.

Apply one of these diffs:

-      - name: golangci-lint
-        uses: golangci/golangci-lint-action@v8
-        with:
-          version: v2.5.0
+      - name: golangci-lint
+        uses: golangci/golangci-lint-action@v8

Or pin to a valid v1.x (example):

-      - name: golangci-lint
-        uses: golangci/golangci-lint-action@v8
-        with:
-          version: v2.5.0
+      - name: golangci-lint
+        uses: golangci/golangci-lint-action@v8
+        with:
+          version: v1.61.0

If you prefer pinning to latest, verify the current v1.x:

What is the latest golangci-lint release tag?
🧹 Nitpick comments (1)
update_test.go (1)

12-12: Consider releasing arenas in tests (if API provides it).

If go-arena exposes Release/Reset/Close, add a defer to avoid lingering allocations and to mirror production usage.

Please confirm the correct teardown API name for github.com/wundergraph/go-arena and whether it's recommended in tests.

Also applies to: 61-61

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1b4b27 and 5bcda71.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml (2 hunks)
  • update.go (3 hunks)
  • update_test.go (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
update.go (1)
parser.go (4)
  • Object (511-514)
  • Value (629-634)
  • TypeObject (688-688)
  • TypeArray (691-691)
update_test.go (2)
parser.go (2)
  • Parser (38-40)
  • Object (511-514)
handy.go (2)
  • MustParse (139-145)
  • Parse (125-128)
🔇 Additional comments (2)
update_test.go (1)

122-151: Confirm nil-arena guarantee; test name assumes specific allocator semantics.

The test asserts that calling code paths using unescapeKeys(nil) is safe. Please confirm that github.com/wundergraph/go-arena explicitly supports nil arenas (i.e., operations like internal slice growth fall back to heap) so this remains stable across versions. If not guaranteed, guard nil internally.

Based on learnings

update.go (1)

26-27: Recheck nil-arena assumption; avoid relying on undocumented fallback.

Comment claims nil falls back to heap. Please confirm github.com/wundergraph/go-arena guarantees this across APIs used by unescapeKeys; otherwise guard nil and use heap paths explicitly.

Based on learnings

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
parser_timing_test.go (2)

101-131: Avoid sharing a single Parser across workers

Line 102 declares benchPool once, but Lines 118-129 call benchPool.Parse from multiple goroutines spawned by RunParallel. Parser retains mutable state (b []byte), so concurrent reuse corrupts the buffer and introduces data races. Give each worker its own Parser.

-func benchmarkObjectGet(b *testing.B, itemsCount, lookupsCount int) {
-	var benchPool Parser
+func benchmarkObjectGet(b *testing.B, itemsCount, lookupsCount int) {
@@
-	b.RunParallel(func(pb *testing.PB) {
-		for pb.Next() {
-			v, err := benchPool.Parse(s)
+	b.RunParallel(func(pb *testing.PB) {
+		var parser Parser
+		for pb.Next() {
+			v, err := parser.Parse(s)

233-268: Isolate Parser state inside each parallel worker

Line 234’s benchPool is shared, yet Lines 241-266 parse concurrently via RunParallel. The shared Parser’s mutable buffer makes this a data race. Allocate a separate Parser per worker to keep parsing state isolated.

-func benchmarkFastJSONParseGet(b *testing.B, s string) {
-	var benchPool Parser
+func benchmarkFastJSONParseGet(b *testing.B, s string) {
@@
-	b.RunParallel(func(pb *testing.PB) {
-		var n int
-		for pb.Next() {
-			v, err := benchPool.Parse(s)
+	b.RunParallel(func(pb *testing.PB) {
+		var (
+			parser Parser
+			n      int
+		)
+		for pb.Next() {
+			v, err := parser.Parse(s)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bcda71 and d990a3d.

📒 Files selected for processing (1)
  • parser_timing_test.go (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
parser_timing_test.go (2)
parser.go (3)
  • Parser (38-40)
  • Type (681-681)
  • TypeObject (688-688)
handy.go (1)
  • Parse (125-128)

@jensneuse jensneuse merged commit a4f0844 into master Oct 10, 2025
5 checks passed
@jensneuse jensneuse deleted the feat/improve-arena-handling branch October 10, 2025 10:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants