Skip to content

Commit 58c7566

Browse files
authored
Fix a Firestore transaction crash if getDocument was called after terminateWithCompletion (#8760)
1 parent dc366f2 commit 58c7566

File tree

5 files changed

+68
-7
lines changed

5 files changed

+68
-7
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] Fix a crash if `[FIRTransaction getDocument]` was called after
3+
`[FIRFirestore terminateWithCompletion]` (#8760).
4+
15
# v8.6.0
26
- [changed] Internal refactor to improve serialization performance.
37
- [changed] `DocumentSnapshot` objects consider the document's key and data for

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,47 @@ - (void)testClientCallsAfterTerminationFail {
13161316
NSException, @"The client has already been terminated.");
13171317
}
13181318

1319+
- (void)testTransactionGetAfterTerminationFail {
1320+
FIRDocumentReference *doc = [self documentRef];
1321+
FIRFirestore *firestore = doc.firestore;
1322+
XCTestExpectation *expectation1 = [self expectationWithDescription:@"TransactionBlockStart"];
1323+
XCTestExpectation *expectation2 = [self expectationWithDescription:@"FirestoreTerminated"];
1324+
XCTestExpectation *expectation3 = [self expectationWithDescription:@"TransactionBlockDone"];
1325+
__block NSError *capturedError = nil;
1326+
1327+
[firestore
1328+
runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
1329+
// Tell the test thread that it can now call `terminateWithCompletion`.
1330+
[expectation1 fulfill];
1331+
// Wait for `terminateWithCompletion` to complete.
1332+
[self awaitExpectation:expectation2];
1333+
// Call `[transaction getDocument]` to make sure that it correctly reports the error.
1334+
[transaction getDocument:doc error:error];
1335+
// Save the `NSError` into a variable on which the test thread will perform assertions.
1336+
capturedError = *error;
1337+
// Tell the test thread that it can now perform assertions on the captured `NSError` object.
1338+
[expectation3 fulfill];
1339+
return @"I should have failed since the transaction was terminated";
1340+
}
1341+
completion:^(id, NSError *){
1342+
}];
1343+
1344+
// Wait for the transaction callback to start.
1345+
[self awaitExpectation:expectation1];
1346+
1347+
// Terminate the Firestore instance.
1348+
[firestore terminateWithCompletion:[self completionForExpectation:expectation2]];
1349+
1350+
// Wait for the transaction callback to "publish" the `NSError` from `[transaction getDocument]`.
1351+
[self awaitExpectation:expectation3];
1352+
1353+
// Verify that `[transaction getDocument]` set its `NSError` argument to a non-nil error.
1354+
XCTAssertNotNil(capturedError);
1355+
XCTAssertEqual(capturedError.code, FIRFirestoreErrorCodeFailedPrecondition);
1356+
XCTAssertEqualObjects(capturedError.userInfo[NSLocalizedDescriptionKey],
1357+
@"The client has already been terminated.");
1358+
}
1359+
13191360
- (void)testMaintainsPersistenceAfterRestarting {
13201361
FIRDocumentReference *doc = [self documentRef];
13211362
FIRFirestore *firestore = doc.firestore;

Firestore/core/src/core/transaction.cc

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "Firestore/core/src/core/transaction.h"
1818

1919
#include <algorithm>
20+
#include <memory>
2021
#include <unordered_set>
2122
#include <utility>
2223

@@ -47,8 +48,8 @@ namespace firebase {
4748
namespace firestore {
4849
namespace core {
4950

50-
Transaction::Transaction(Datastore* datastore)
51-
: datastore_{NOT_NULL(datastore)} {
51+
Transaction::Transaction(std::shared_ptr<Datastore> datastore)
52+
: datastore_{datastore} {
5253
}
5354

5455
Status Transaction::RecordVersion(const Document& doc) {
@@ -90,7 +91,14 @@ void Transaction::Lookup(const std::vector<DocumentKey>& keys,
9091
return;
9192
}
9293

93-
datastore_->LookupDocuments(
94+
std::shared_ptr<Datastore> datastore = datastore_.lock();
95+
if (!datastore) {
96+
callback(Status(Error::kErrorFailedPrecondition,
97+
"The client has already been terminated."));
98+
return;
99+
}
100+
101+
datastore->LookupDocuments(
94102
keys,
95103
[this, callback](const StatusOr<std::vector<Document>>& maybe_documents) {
96104
if (!maybe_documents.ok()) {
@@ -206,7 +214,15 @@ void Transaction::Commit(util::StatusCallback&& callback) {
206214
mutations_.push_back(VerifyMutation(key, CreatePrecondition(key)));
207215
}
208216
committed_ = true;
209-
datastore_->CommitMutations(mutations_, std::move(callback));
217+
218+
std::shared_ptr<Datastore> datastore = datastore_.lock();
219+
if (!datastore) {
220+
callback(Status(Error::kErrorFailedPrecondition,
221+
"The client has already been terminated."));
222+
return;
223+
}
224+
225+
datastore->CommitMutations(mutations_, std::move(callback));
210226
}
211227

212228
void Transaction::MarkPermanentlyFailed() {

Firestore/core/src/core/transaction.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class Transaction {
5454
std::function<void(const util::StatusOr<std::vector<model::Document>>&)>;
5555

5656
Transaction() = default;
57-
explicit Transaction(remote::Datastore* datastore);
57+
explicit Transaction(std::shared_ptr<remote::Datastore> datastore);
5858

5959
/**
6060
* Takes a set of keys and asynchronously attempts to fetch all the documents
@@ -129,7 +129,7 @@ class Transaction {
129129
absl::optional<model::SnapshotVersion> GetVersion(
130130
const model::DocumentKey& key) const;
131131

132-
remote::Datastore* datastore_ = nullptr;
132+
std::weak_ptr<remote::Datastore> datastore_;
133133

134134
std::vector<model::Mutation> mutations_;
135135
bool committed_ = false;

Firestore/core/src/remote/remote_store.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ bool RemoteStore::CanUseNetwork() const {
533533
}
534534

535535
std::shared_ptr<Transaction> RemoteStore::CreateTransaction() {
536-
return std::make_shared<Transaction>(datastore_.get());
536+
return std::make_shared<Transaction>(datastore_);
537537
}
538538

539539
DocumentKeySet RemoteStore::GetRemoteKeysForTarget(TargetId target_id) const {

0 commit comments

Comments
 (0)