Skip to content

Add TypedCache[T] for pre-deserialized tree cache#54

Merged
epk merged 1 commit into
masterfrom
typed-cache
Mar 16, 2026
Merged

Add TypedCache[T] for pre-deserialized tree cache#54
epk merged 1 commit into
masterfrom
typed-cache

Conversation

@epk

@epk epk commented Mar 12, 2026

Copy link
Copy Markdown
Member

Summary

Adds TypedCache[T], a generic wrapper around TreeCache that deserializes node data once on write (ZK event) and serves concrete typed objects on read. This eliminates per-access Unmarshal calls.

API

// Create a typed cache wrapping an existing TreeCache.
cache := zk.NewTreeCache(conn, "/mcs.v1", zk.WithTreeCacheIncludeData(true))
typed := zk.NewTypedCache[mcsv1.Metadata](cache, json.Unmarshal,
    zk.WithTypedCacheListener[mcsv1.Metadata](metricsListener))

// Read pre-deserialized objects (lock-free).
val, stat, err := typed.GetTyped("/services/foo")

// Iterate all typed entries.
for path, val, stat := range typed.AllTyped() { ... }

// Count entries.
n := typed.Len()

Design

  • Thread safety: atomic.Pointer[sync.Map] gives lock-free concurrent reads. On re-sync after reconnection, a fresh map is built by walking the TreeCache and swapped atomically — no reader ever sees partial state.
  • Listener chaining: TypedCache implements TreeCacheListener and installs itself on the TreeCache. WithTypedCacheListener chains an inner listener that receives all events after TypedCache processes them.
  • Non-deserializable nodes: Silently skipped (logged at debug level). GetTyped returns ErrNoNode for them.
  • Path handling: Works with both relative and absolute path modes (WithTreeCacheAbsolutePaths).

Tests

  • Initial sync populates typed entries
  • Node create/update/delete reflected in GetTyped
  • Re-sync after disconnect clears stale entries
  • Inner listener chaining works
  • Absolute paths mode works
  • Non-deserializable nodes silently skipped
  • Concurrent reads during sync (safe with -race)

@epk
epk requested review from jpfourny and wayt March 16, 2026 13:03

@jerr jerr left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm not a zk expert, so I might have missed something, but this PR makes sense and should be a good improvement. 👍

Comment thread typed_cache.go Outdated
Comment thread typed_cache.go Outdated
Comment thread typed_cache_test.go Outdated
TypedCache wraps TreeCache and maintains a pre-deserialized view of the
tree using generics. Data is deserialized once on write (ZK event) and
served as concrete objects on read, avoiding per-access unmarshal.

API:
- NewTypedCache[T](cache, unmarshal, opts...) — wraps a TreeCache
- GetTyped(path) → (*T, *Stat, error) — lock-free typed read
- AllTyped() → iterator over all typed entries
- Len() → number of typed entries
- WithTypedCacheListener[T](l) — chains an inner TreeCacheListener

Thread safety: atomic.Pointer[sync.Map] gives lock-free reads. On
re-sync, a fresh map is built and swapped atomically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@epk
epk merged commit 211ad0e into master Mar 16, 2026
4 checks passed
@epk
epk deleted the typed-cache branch March 16, 2026 15:12
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