Repository navigation
Conversation
tyler-reitz
left a comment
There was a problem hiding this comment.
Requesting changes for one thing: the fix needs rxjs 7.1.0 or later, and dependencies allows ^6.6.3 || ^7.0.1.
The function form of resetOnRefCountZero arrived in 7.1.0. On 7.0.x the function is treated as a truthy true and resets immediately; on 6.x share() takes no arguments and ignores the config. With this exact config on 6.6.3 and 7.0.1, onEvict never fires, so those installs keep the leak with no error. CI only installs 7.8.1.
Your call which way: raise the floor to ^7.1.0, which breaks rxjs 6 users and so belongs on v5 (where Closes won't fire), or keep the range and say in the body that the fix applies to 7.1+ only.
Everything else checks out. Each new test goes red against main's src/ and when its fix is reverted. Non-blocking:
- Three paths have no test: the 1s grace window (a 0ms delay stays green),
releaseWarmupWhenSettled(an immediatereturnstays green), and the identity guard inonEvict(a plaindelete(id)stays green). The guard is right: a late subscriber holding an evicted subject re-arms its timer, which would otherwise delete the newer entry. - Errored entries used to stay cached and now clear after 1s, in suspense mode too since the timeout is unconditional. That's what #742 asks for, but it's a behavior change under
fix:. - The TODO in the
valuegetter ("burn the cache entry until the timeout fires") is stale now.
| // - `resetOnRefCountZero`: unsubscribe from Firebase and evict the cache entry once no subscribers remain for `RELEASE_DELAY_MS`. | ||
| share({ | ||
| connector: () => new ReplaySubject<T>(1), | ||
| resetOnError: true, |
There was a problem hiding this comment.
share never sees an error here: catchError(() => empty()) upstream turns it into a completion, so this option never applies (flipping it to false leaves the suite green). Errored entries are evicted only by the setTimeout(onEvict) in tap's error handler. Worth saying so here, so nobody deletes that timeout as redundant.
| share({ | ||
| connector: () => new ReplaySubject<T>(1), | ||
| resetOnError: true, | ||
| resetOnComplete: false, |
There was a problem hiding this comment.
Since errors arrive as completions, this covers every errored stream too, and a completion cancels any pending refcount timer and stops it being rescheduled. So entries for streams that complete normally (the first() in useFirestoreDocOnce and useFirestoreDocDataOnce) never leave preloadedObservables: the listener is gone, but the entry stays, which on a server is still per-user growth (#790). Intended?
Summary
Fixes two cache lifecycle defects in
SuspenseSubjectandpreloadObservable:SuspenseSubjectpipedinnerObservablethroughshareReplay(1)(refCount: false), which keeps the underlying Firebase listener attached indefinitely even after all subscribers unmount.this._warmupSubscriptionwas never unsubscribed inside_subscribe(), keepingrefCount >= 1permanently._suspenseEnabledwastrue,_timeoutHandlerwas a no-op (setTimeout(() => {}, this._timeoutWindow)), leaking abandoned<Suspense>renders forever.#742,#485):permission-deniedbefore sign-in or on sign-out), the erroredSuspenseSubjectremained inpreloadedObservablesunder itsobservableId, causing subsequent mounts or<ErrorBoundary>retries to immediately replay the stale error.Changes
shareReplay(1)withshare({ connector: () => new ReplaySubject<T>(1), resetOnError: true, resetOnComplete: false, resetOnRefCountZero: () => timer(RELEASE_DELAY_MS).pipe(tap(onEvict)) })so the shared Firebase listener stays warm across React<StrictMode>double-mounts (RELEASE_DELAY_MS = 1000ms) and detaches + evicts frompreloadedObservablesonce no subscribers remain.this._warmupSubscriptionin_subscribe()after attaching the real subscriber so ownership hands off without detaching the underlying listener._suspenseEnabledno-op timeout withreleaseWarmupWhenSettled, which waits untilfirstEmissionhas settled (this._hasValue || this._error, preserving#525's guarantee for slow initial reads) before releasing unclaimed warm-up subscriptions.setTimeout(onEvict, RELEASE_DELAY_MS)on stream error so remounts and<ErrorBoundary>retries start a fresh subscription.Closes #742
Closes #485