Skip to content

Commit f1294ce

Browse files
authored
Fix a crash that happens when Firestore is being terminated when the last shared pointer to it is in a listener (#7421)
If a user calls terminate and immediately disposes their reference to Firestore, it's possible that while termination is in process, the last remaining reference (more precisely, shared pointer) to Firestore is in a listener. When that listener is destroyed as part of the termination, it leads to `api::Firestore` being destroyed. Importantly, termination happens on the worker queue. The destructor of `api::Firestore` calls `Dispose` which presumes it's not called from the worker queue and tries to enqueue work, leading to a failing assertion and a crash. The solution is simply to remove the sequential order checks when the queue enters/is in restricted mode. There is a legitimate case where the Firestore destructor can run on the worker queue, as the original issue shows. The complexity of #7412 also indicates that a simpler solution is preferable. Fixes #6909.
1 parent ec48dc4 commit f1294ce

File tree

3 files changed

+24
-3
lines changed

3 files changed

+24
-3
lines changed

Firestore/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Unreleased
2+
- [fixed] Fixed a crash that would happen when the app is being deleted and
3+
immediately disposed of and there's an active listener (#6909).
4+
15
# v7.5.0
26
- [changed] A write to a document that contains FieldValue transforms is no
37
longer split up into two separate operations. This reduces the number of

Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,26 @@ - (void)testAppDeleteLeadsToFirestoreTermination {
14411441
XCTAssertTrue(firestore.wrapped->client()->is_terminated());
14421442
}
14431443

1444+
// Ensures b/172958106 doesn't regress.
1445+
- (void)testDeleteAppWorksWhenLastReferenceToFirestoreIsInListener {
1446+
FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
1447+
FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
1448+
1449+
FIRDocumentReference *doc = [firestore documentWithPath:@"abc/123"];
1450+
// Make sure there is a listener.
1451+
[doc addSnapshotListener:^(FIRDocumentSnapshot *, NSError *){
1452+
}];
1453+
1454+
XCTestExpectation *expectation = [self expectationWithDescription:@"App is deleted"];
1455+
[app deleteApp:^(BOOL) {
1456+
[expectation fulfill];
1457+
}];
1458+
// Let go of the last app reference.
1459+
app = nil;
1460+
1461+
[self awaitExpectations];
1462+
}
1463+
14441464
- (void)testTerminateCanBeCalledMultipleTimes {
14451465
FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
14461466
FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];

Firestore/core/src/util/async_queue.cc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ AsyncQueue::~AsyncQueue() {
4545

4646
void AsyncQueue::EnterRestrictedMode() {
4747
std::lock_guard<std::mutex> lock(mutex_);
48-
VerifySequentialOrder();
4948
if (mode_ == Mode::kDisposed) return;
5049

5150
mode_ = Mode::kRestricted;
@@ -97,9 +96,7 @@ bool AsyncQueue::Enqueue(const Operation& operation) {
9796
}
9897

9998
bool AsyncQueue::EnqueueEvenWhileRestricted(const Operation& operation) {
100-
// Still guarding the lock to ensure sequential scheduling.
10199
std::lock_guard<std::mutex> lock(mutex_);
102-
VerifySequentialOrder();
103100
if (mode_ == Mode::kDisposed) return false;
104101

105102
executor_->Execute(Wrap(operation));

0 commit comments

Comments
 (0)