Conversation
With reentrancy enabled, each dispatched method call gets its own state change tracker, but activation, reminders and timers run on the default tracker because no reentrancy id reaches them. A key read during activation stays cached there with change kind none forever, while method calls write the same key through their own trackers. A reminder callback that later reads that key is served the stale activation value. An app that skips its write because the value looks unchanged loses that write silently: nothing is logged anywhere, because no write is ever issued. Drop the default tracker's clean copies of keys written through a reentrancy-scoped tracker, so the next read reloads them from the runtime. Reported in dapr/dapr#10532, where a reminder callback's read-modify-write of an actor state key never persisted while the identical write from an ordinary method call did, and only with reentrancy enabled. Should be backported. Signed-off-by: joshvanl <me@joshvanl.dev>
…ant save Instead of dropping the default tracker's clean copy of a key that a reentrant call saved, replace it with the saved value and ttl so the next read from activation, a reminder or a timer is served from cache rather than costing an extra state store read. Removed keys are still dropped and entries with pending changes are left alone. The cached value is passed through the state serializer first, so it has the same shape a fresh read would return (for example a tuple comes back as a list). This mirrors dapr/dotnet-sdk#1912. Signed-off-by: Casper Nielsen <casper@diagrid.io> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tch a fresh read Two cases fall back to dapr#1227's eviction instead of an in-place refresh: a saved None value, which the state provider leaves out of the write, and a state serializer that fails to decode the value after the save has already committed. The save no longer raises after a successful write. Signed-off-by: Casper Nielsen <casper@diagrid.io> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1237 +/- ##
==========================================
+ Coverage 83.89% 83.93% +0.03%
==========================================
Files 123 123
Lines 10265 10284 +19
==========================================
+ Hits 8612 8632 +20
+ Misses 1653 1652 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on #1227. Merge it after that. Until then this diff also shows #1227's commits; this PR's own commits are 83d0e03 and c3b3387.
Description
With actor reentrancy on, each reentrant call gets its own state tracker, while activation, reminders and timers use the default tracker. #1227 fixes stale reads by dropping the default tracker's clean copies of keys a reentrant call saved. The next default read then has to go back to the state store for those keys.
This PR refreshes those entries in place instead, so the next read from activation, a reminder or a timer is served from cache with no state store round trip.
What changed, in
dapr/actor/runtime/state_manager.py(_invalidate_default_trackeris renamed to_refresh_default_tracker):none) is updated:StateProvider.round_trip_state_valueserializes the value and deserializes it asobjectwith the provider's own state serializer. No network call is made. For example a tuple(3, 4)is cached as[3, 4], same as a read from the store.Nonevalue, because the provider leaves it out of the write, so a fresh read may find nothing;save_stateno longer raises after a successful save.Behaviour change: none for callers, other than fewer state store reads after reentrant saves.
This ports dapr/dotnet-sdk#1912. Thanks to @olitomlinson for pointing out the .NET changes on #1227.
Issue reference
Related to dapr/dapr#10532 (reminders reading stale state with reentrancy) and #1227.
Checklist
Ran:
pytest tests/actor(195 passed),ruff check,ruff format --check,mypy(no issues).🤖 Generated with Claude Code