fix(unreal): suppress spurious OnInsert on overlapping subscription refcount bump#4903
Open
SamuelE-Arvika wants to merge 1 commit intoclockworklabs:masterfrom
Conversation
…efcount bump ClientCache::ApplyDiff phase 2 unconditionally appended every incoming row to Diff.Inserts, even when the row was already cached and only the refcount needed to bump. BroadcastDiff then fired OnInsert for every entry, so any table subscribed via two overlapping queries (e.g. a global SELECT plus a per-row WHERE) re-fired its insert handler on every later subscription apply. Mirror the delete path: only emit a Diff.Inserts entry when the row-bytes key transitions from absent to refcount=1. Real updates are unaffected — update bytes differ from old bytes, take the !Entry path, and DeriveUpdatesByPrimaryKey still reconciles them into OnUpdate.
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.
Description of Changes
UClientCache::ApplyDiff(sdks/unreal/.../DBCache/ClientCache.h) has an asymmetry between its insert and delete paths.Phase 1 (deletes) correctly only emits a
Diff.Deletesentry when the row's refcount transitions to 0 — overlapping subscriptions just decrement.Phase 2 (inserts) always appends to
Diff.Inserts, regardless of whether the row was already cached:BroadcastDiffthen firesOnInsertfor everyDiff.Insertsentry, so any table subscribed by two overlapping queries (e.g. a globalSELECT * FROM tplus a per-rowWHERE id = ...) re-fires its insert handler on every later subscription apply — once per cached row, every time. Game code that does work inOnInsert(positioning, spawning, snapping to terrain) re-runs and clobbers state that was meant to be set once.The intent is documented in
RowEntry.h: "Wrapper storing a row value with a reference count used by overlapping subscriptions." Phase 1 follows that design; phase 2 doesn't.Fix
Move the
Diff.Inserts.Addinto the!Entrybranch only, so it fires only on the absent → refcount=1 transition:Why real updates still work
Cache keys are BSATN row-bytes, not primary keys. A real update arrives as a
(delete old_bytes, insert new_bytes)pair whereold_bytes ≠ new_bytes— so the insert side still takes the!Entrybranch and gets aDiff.Insertsentry.FTableAppliedDiff::DeriveUpdatesByPrimaryKeythen pairs the delete and insert by PK intoUpdateInserts/UpdateDeletes, andOnUpdate(notOnInsert) fires, exactly as today.Edge cases:
!EntryOnInsert!EntryOnInsert+OnDeletereconciled toOnUpdateby PKelseOnInsert)elseAPI and ABI breaking changes
None. Purely internal cache bookkeeping. Existing
OnInsert/OnDelete/OnUpdatesemantics are preserved for all non-overlapping cases; the only behavior change is that overlapping subscriptions stop emitting duplicateOnInsertevents for already-cached rows — which matches the documentedRowEntryrefcount contract.Expected complexity level and risk
1. Two lines moved into a branch; comments updated. Mirrors logic already present and known-correct in phase 1.
Testing
Reproduced and validated downstream in an Unreal project. The repro setup is straightforward to replicate against any module:
twith ~150 rows.SELECT * FROM t, applied first.t.OnInsert, then every 10s submits a new overlapping subscription (e.g.SELECT * FROM tagain, or anySELECT * FROM t WHERE 'id' = Xcovering already-cached rows) and counts theOnInsertevents that arrive in that round.Expected: round 1 fires once per row that is new to the cache; subsequent rounds against already-cached rows fire 0.
Observed (~161 rows cached after initial load):
The genuine "empty cache → 161 entries" wave on the initial global subscription is unaffected — same
OnInsertcount both pre- and post-fix. Only the duplicate fires from later overlapping subscriptions on already-cached rows are eliminated.OnUpdatestill fires correctly when underlying rows actually change.OnUpdateand notOnInsert+OnDelete.OnInsertfiring on every subscription apply (rather than only on cache 0→1 transition) is not present — if it exists, it was relying on the bug.