Repository navigation
Conversation
FirestoreQueryObservable stores the closure returned by `createListener(with:)` in `setupListener`, and that closure captured `self` strongly. The resulting cycle (self -> setupListener -> self) kept `deinit` from ever running, so `removeListener()` was never called: every `@FirestoreQuery` leaked its observable, and the observable's snapshot listener kept running, and reading documents, after the view that owned it went away. Capture `self` weakly in the returned closure, as the snapshot handlers passed to it already do, and add tests that check that the observable is deallocated and removes its listener registration once the last strong reference to it goes away.
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses a memory leak in @FirestoreQuery by capturing self weakly inside the listener creation closure. It also adds a comprehensive unit test suite to verify the lifetime and deallocation of FirestoreQueryObservable. The review feedback correctly points out a compilation error in the new test file where weak references are declared using let instead of var.
|
[paulb777-bot] Requesting an automated review, as on the other PRs from this batch. /gemini review |
There was a problem hiding this comment.
Code Review
This pull request fixes a memory leak in @FirestoreQuery by capturing self weakly inside the listener creation closure in FirestoreQueryObservable.swift. It also adds comprehensive unit tests to verify the lifetime and deallocation behavior of the observable. The review comments correctly point out that weak references in the new tests must be declared as mutable variables (weak var) instead of constants (weak let) to avoid compiler errors.
Problem
FirestoreQueryObservablestores the closure returned bycreateListener(with:)inself.setupListener, and that closure capturedselfstrongly. The resulting cycle(
self→setupListener→self) meansdeinit, which callsremoveListener(), never runs.Every
@FirestoreQuerytherefore leaks its observable. The observable's snapshot listener staysregistered, and keeps receiving and reading documents, after the view that owned it is gone. The
snapshot handlers passed to
createListeneralready captureselfweakly, so this closure wasthe only strong capture.
Change
Capture
selfweakly in the closure returned bycreateListener(with:)(2 lines). The closureis only invoked through the observable (from its initializers and when
configurationchanges),so the
guardnever fires while the observable is alive.Testing
Added
Firestore/Swift/Tests/Unit/PropertyWrapper/FirestoreQueryObservableLifetimeTests.swift.It uses Swift Testing and is part of the
FirebaseFirestoreTestsSPM target. The tests swapQuery.addSnapshotListener(_:)for a fake that hands out recordingListenerRegistrations.Each test drops the last strong reference to the observable, then checks that the observable is
deallocated and that its registration was removed. There are three cases:
Resultresults;configurationchange has replaced the listener.and
removeCount == 0.a row (
-test-iterations 30 -run-tests-until-failure), 270/270../scripts/style.shis clean.-test-iterationsstress, "Stream handles (nil, nil) eventsgracefully" in
AsyncSequenceTests.swiftoccasionally fails, even with the new suite skipped.Those suites aren't
.serialized, and their tests swap the same method concurrently.#16826 touches the same file on different lines (it merges cleanly). The two CHANGELOG entries
will need a trivial merge.