# Week 16 — Update
## TL;DR
Refactored publish/receive RPC message memory management to use an arena allocator in zig-libp2p.
PR: https://github.com/zen-eth/eth-p2p-z/pull/83/commits/80dec6c68db8f48fc560a15f1580d83813781d46
---
## Quick summary
- Use a short-lived, per-RPC arena to allocate temporary objects used while decoding, validating, and processing messages.
- Free the arena once processing completes (success or error) so no individual deallocation is required.
- Keep long-lived state (mesh, fanout, peers, interned topic refs) outside the arena.
- Measure and tune arena capacity and reuse policy to avoid unbounded growth.
---
## Terminology
- Arena: contiguous allocation region where small objects are allocated by bumping a pointer.
- ScopedArena: arena whose lifetime is tied to a single RPC handling scope (publish or receive).
- Long-lived data: objects that must persist beyond a single RPC (stored on heap or interned pools).
- Short-lived data: temporary parsing buffers, intermediate structures, and decoded fields created during RPC handling.
---
## Why an arena fits this workload
- RPC handlers allocate many small temporary objects (decoded fields, slices, validation state).
- These objects have the same lifetime: the request handling scope.
- Arena allocation is O(1) and avoids repeated small heap allocations and frees.
- Single release at scope end prevents leaks from forgotten frees and simplifies error paths.
---
## Design overview
1. Per-RPC ScopedArena:
- Create a ScopedArena at the start of processing an incoming RPC or when preparing an outgoing RPC.
- Use the arena for all ephemeral allocations during decode/validation/processing.
- Deallocate by resetting/freeing the ScopedArena at the end of processing.
2. Long-lived allocations:
- Mesh/fanout maps, interned topic references, and persistent caches should NOT use the ScopedArena.
- Copy or reference durable data out of the arena only when explicitly required (e.g., intern topic string into global pool).
3. Arena pooling:
- Maintain a small pool of pre-allocated arenas (or sized buffers) to avoid frequent arena construction costs.
- When an arena is returned to the pool, reset it for reuse.
- Carefully bound the pool size to avoid unbounded memory.
4. Safety:
- Prevent returning pointers into the arena to long-lived structures.
- Add debug assertions or runtime checks (in test builds) to catch accidental long-lived references to arena memory.
---
## Typical usage pattern
- Receive RPC frame -> allocate ScopedArena (possibly taken from a pool)
- Decode message fields into arena-allocated buffers / slices
- Validate/transform using arena memory for intermediates
- For data that must persist, copy to long-lived allocations or intern them
- Send responses / forward; finish processing
- Reset or free the ScopedArena (return to pool)
---
## Pseudocode
```pseudo
// Handle incoming RPC
function handle_rpc(frame):
arena = arena_pool.acquire() // or ScopedArena.new()
defer arena.reset() // ensure the arena is released at scope exit
msg = decode_rpc(frame, arena) // allocations done in arena
if !validate(msg):
return error
// For values that must persist beyond RPC, copy or intern:
topic_ref = global_pool.intern(arena.slice_to_bytes(msg.topic))
mesh_map[topic_ref].insert(peer)
process_message(msg, arena) // temporary structures used here
// when function returns, arena.reset() reclaims all temporary memory
```
Notes:
- decode_rpc should allocate decoded strings/slices inside arena.
- intern or copy any data that must survive beyond the handler before the arena is reset.
---
## Sequence diagram
```mermaid
sequenceDiagram
participant Net
participant Handler
participant ArenaPool as ArenaPool
participant Global as GlobalState
Net->>Handler: incoming RPC frame
Handler->>ArenaPool: acquire() -> ScopedArena
Handler->>Handler: decode frame into ScopedArena
Handler->>Global: intern(topic) [copy out of arena]
Handler->>Handler: process (use arena allocations)
Handler->>ArenaPool: reset(release) ScopedArena
```
---
## Implementation considerations
- Arena size and growth:
- Start with a moderate initial size (e.g., 8–32 KiB) and grow exponentially for large messages.
- Limit max arena size to avoid pathological allocations from a single message.
- Pooling strategy:
- Keep a small bounded pool of reusable arenas for common concurrency levels.
- If no pooled arena is available, create one on demand and consider returning it to the pool afterward.
- Thread-safety:
- ScopedArena usage is single-threaded per handler. ArenaPool must be thread-safe if handlers run concurrently (use lock-free stack or mutex).
- Avoid sharing a ScopedArena between threads.
- Defensive programming:
- In debug builds, tag arena allocations so attempts to leak pointers (into long-lived state) can be detected.
- Provide helper APIs to "promote" arena data into persistent storage (copy or intern) with a clear cost.
- Interactions with interned pool:
- Always intern topic strings or other identifiers when they must persist.
- Intern API should copy from arena-sourced bytes before arena reset.
- Error and cancellation paths:
- Ensure arena.reset() is invoked on every exit path (use defer/panic handlers).
- If processing is canceled mid-way, still release arena resources promptly.
---
## Next
Start implementing the ScopedArena and ArenaPool modules, replace ephemeral allocations in publish/receive handlers with arena allocations, and run the unit tests and benchmarks described above.