| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
There was a problem hiding this comment.
Here are some automated review suggestions for this pull request.
Reviewed commit: 54be017de7
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Sorry, something went wrong.
| go func() { | ||
| <-ctx.Done() | ||
| cache.Close() | ||
| }() |
There was a problem hiding this comment.
Stop the context watcher when Close is called
When NewMemoryConnector is given a non-cancelable context such as context.Background() or a caller later cleans up with MemoryConnector.Close() without canceling the original context, this new goroutine remains parked on ctx.Done() forever. Several direct memory connector users/tests construct it with context.Background() and rely on Close(), so this change replaces the two Ristretto goroutine leak with a new one-goroutine leak per connector; make the watcher share an internal close signal or have Close() cancel the lifecycle it starts.
Useful? React with 👍 / 👎.
Sorry, something went wrong.
There was a problem hiding this comment.
Good catch — fixed in 384636c: Close() now cancels the watcher's own context (separate from the caller-supplied ctx), and both paths route through a shared sync.Once since ristretto.Cache.Close() panics if it ever runs twice concurrently. Added a regression test (TestMemoryConnector_ClosesRistrettoGoroutinesOnExplicitClose) covering the context.Background() + explicit Close() case.
Sorry, something went wrong.
Codex review on erpc#1066 correctly flagged that the ctx-watcher goroutine added to close ristretto's own background goroutines would itself leak forever for callers that build a MemoryConnector with a non-cancelable ctx (context.Background(), which several call sites in this repo use) and rely on calling Close() directly instead of cancelling a parent context. Give Close() its own cancel func for the watcher's context, and route both the watcher and Close() through a shared sync.Once so cache.Close() (which panics if it ever runs twice concurrently — it closes channels) only ever executes once regardless of which path fires first. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MemoryConnector.Close() closes the underlying ristretto cache, but nothing in erpc ever calls it — SharedStateRegistry and EvmJsonRpcCache have no Close() of their own, so the only way a connector's ctx is ever observed is via the parent context being cancelled, which ristretto's own processItems/defaultPolicy goroutines don't watch. Every connector created over a process's lifetime leaks 2 goroutines permanently. This is most visible in erpc's own test suite, where each test builds its own short-lived ERPC/network stack (and therefore its own memory connectors): running the full erpc package accumulates tens of thousands of live ristretto goroutines over the run, degrading scheduling/GC enough to blow through the test timeout. Tie the cache's lifetime to the connector's ctx directly in NewMemoryConnector so cache.Close() runs on ctx cancellation regardless of whether any caller ever reaches for MemoryConnector.Close() explicitly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codex review on erpc#1066 correctly flagged that the ctx-watcher goroutine added to close ristretto's own background goroutines would itself leak forever for callers that build a MemoryConnector with a non-cancelable ctx (context.Background(), which several call sites in this repo use) and rely on calling Close() directly instead of cancelling a parent context. Give Close() its own cancel func for the watcher's context, and route both the watcher and Close() through a shared sync.Once so cache.Close() (which panics if it ever runs twice concurrently — it closes channels) only ever executes once regardless of which path fires first. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closing Ristretto's cache concurrently with an in-flight Set/Get/Delete is unsafe on its own — Cache.Close() closes internal channels, so a concurrent Set() racing it can either data-race or panic with "send on closed channel". The previous sync.Once wrapper only made the *closing* idempotent; it did nothing to stop a live cache operation from running into an already-(or-concurrently-)closed cache. This is exactly what erpc's own -race CI run found: architecture/svm's TestSvmStatePoller_ConcurrentSuggestionsDuringPoll_StaySlotMonotonic cancels a poller's ctx while a background goroutine is still calling Set() via counterInt64.updateRemoteState — previously silent (Close() was dead code, so the leak just grew instead), now a real crash risk once ctx-driven closing is wired up. Replace the sync.Once with an RWMutex (closeMu) + closed bool: every Set/Get/Delete takes a read lock and checks closed before touching the cache; Close() takes the write lock, so it can't proceed until every in-flight op has released its read lock, and no new op can start once closed is set. Added TestMemoryConnector_SetRacingContextCancelDoesNotPanic, which reproduces the crash under -race on the prior version. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
Summary
Test plan
Rebased onto latest main (which now includes #1067) to drop an earlier, more superficial fix I'd made to the same SVM miss-reason bug — #1067 fixes it properly (including a second bug it uncovered) and supersedes that part of this PR entirely. This PR is now scoped purely to the Ristretto goroutine leak / concurrency fix.