Skip to content

Commit 7679167

Browse files
authored
Port cl/361792354: Call Terminate() from ~FirebaseFirestore() so that the cached Java instance will be cleared. (#329)
1 parent e7deaac commit 7679167

File tree

5 files changed

+119
-17
lines changed

5 files changed

+119
-17
lines changed

firestore/src/android/firestore_android.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ void InitializeUserCallbackExecutor(Loader& loader) {
137137
kExecutorShutdown);
138138
}
139139

140+
constexpr char kFirestoreTasksClassName[] = PROGUARD_KEEP_CLASS
141+
"com/google/firebase/firestore/internal/cpp/FirestoreTasks";
142+
StaticMethod<void> kAwaitCompletion("awaitCompletion",
143+
"(Lcom/google/android/gms/tasks/Task;)V");
144+
145+
void InitializeFirestoreTasks(Loader& loader) {
146+
loader.LoadClass(kFirestoreTasksClassName, kAwaitCompletion);
147+
}
148+
140149
/**
141150
* A map of Java Firestore instance to C++ Firestore instance.
142151
*/
@@ -258,6 +267,7 @@ bool FirestoreInternal::Initialize(App* app) {
258267
jni::Map::Initialize(loader);
259268

260269
InitializeFirestore(loader);
270+
InitializeFirestoreTasks(loader);
261271
InitializeUserCallbackExecutor(loader);
262272

263273
BlobInternal::Initialize(loader);
@@ -337,6 +347,13 @@ FirestoreInternal::~FirestoreInternal() {
337347
ClearListeners();
338348

339349
Env env = GetEnv();
350+
351+
// Call `terminate()` on the Java `FirebaseFirestore` object and wait for it
352+
// to complete to guarantee that the next instance of `FirestoreInternal` will
353+
// be backed by a new Java `FirebaseFirestore` instance.
354+
Local<Task> terminate_task = env.Call(obj_, kTerminate);
355+
env.Call(kAwaitCompletion, terminate_task);
356+
340357
ShutdownUserCallbackExecutor(env);
341358

342359
promises_.reset(nullptr);

firestore/src/tests/firestore_integration_test.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Firestore* FirestoreIntegrationTest::TestFirestore(
107107
const std::string& name) const {
108108
for (const auto& entry : firestores_) {
109109
const FirestoreInfo& firestore_info = entry.second;
110-
if (firestore_info.cached() && firestore_info.name() == name) {
110+
if (firestore_info.name() == name) {
111111
return firestore_info.firestore();
112112
}
113113
}
@@ -135,6 +135,14 @@ void FirestoreIntegrationTest::DeleteFirestore(Firestore* firestore) {
135135
firestores_.erase(found);
136136
}
137137

138+
void FirestoreIntegrationTest::DisownFirestore(Firestore* firestore) {
139+
auto found = firestores_.find(firestore);
140+
FIREBASE_ASSERT_MESSAGE(found != firestores_.end(),
141+
"The given Firestore was not found.");
142+
found->second.ReleaseFirestore();
143+
firestores_.erase(found);
144+
}
145+
138146
void FirestoreIntegrationTest::DeleteApp(App* app) {
139147
auto found = apps_.find(app);
140148
FIREBASE_ASSERT_MESSAGE(found != apps_.end(), "The given App was not found.");

firestore/src/tests/firestore_integration_test.h

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class TestEventListener : public EventListener<T> {
152152
};
153153

154154
// Base class for Firestore integration tests.
155-
// Note it keeps a cached of created Firestore instances, and is thread-unsafe.
155+
// Note it keeps a cache of created Firestore instances, and is thread-unsafe.
156156
class FirestoreIntegrationTest : public testing::Test {
157157
friend class TransactionTester;
158158

@@ -170,18 +170,32 @@ class FirestoreIntegrationTest : public testing::Test {
170170
// Returns a Firestore instance for an app with the given name.
171171
// If this method is invoked again with the same `name`, then the same pointer
172172
// will be returned. The only exception is if the `Firestore` was removed
173-
// from the cache by a call to `DeleteFirestore()` or `DeleteApp()` with the
174-
// `App` of the returned `Firestore`.
173+
// from the cache by a call to `DeleteFirestore()` or `DisownFirestore()`, or
174+
// if `DeleteApp()` is called with the `App` of the returned `Firestore`.
175175
Firestore* TestFirestore(const std::string& name = kDefaultAppName) const;
176176

177177
// Deletes the given `Firestore` instance, which must have been returned by a
178-
// previous invocation of `TestFirestore()`. If the given instance was in the
179-
// cache, then it will be removed from the cache. Note that all `Firestore`
178+
// previous invocation of `TestFirestore()`, and removes it from the cache of
179+
// instances returned from `TestFirestore()`. Note that all `Firestore`
180180
// instances returned from `TestFirestore()` will be automatically deleted at
181181
// the end of the test case; therefore, this method is only needed if the test
182-
// requires that the instance be deleted earlier than that.
182+
// requires that the instance be deleted earlier than that. If the given
183+
// `Firestore` instance has already been removed from the cache, such as by a
184+
// previous invocation of this method, then the behavior of this method is
185+
// undefined.
183186
void DeleteFirestore(Firestore* firestore);
184187

188+
// Relinquishes ownership of the given `Firestore` instance, which must have
189+
// been returned by a previous invocation of `TestFirestore()`, and removes it
190+
// from the cache of instances returned from `TestFirestore()`. Note that all
191+
// `Firestore` instances returned from `TestFirestore()` will be automatically
192+
// deleted at the end of the test case; therefore, this method is only needed
193+
// if the test requires that the instance be excluded from this automatic
194+
// deletion. If the given `Firestore` instance has already been removed from
195+
// the cache, such as by a previous invocation of this method, then the
196+
// behavior of this method is undefined.
197+
void DisownFirestore(Firestore* firestore);
198+
185199
// Deletes the given `App` instance. The given `App` must have been the `App`
186200
// associated with a `Firestore` instance returned by a previous invocation of
187201
// `TestFirestore()`. Normally the `App` is deleted at the end of the test, so
@@ -304,13 +318,11 @@ class FirestoreIntegrationTest : public testing::Test {
304318

305319
const std::string& name() const { return name_; }
306320
Firestore* firestore() const { return firestore_.get(); }
307-
bool cached() const { return cached_; }
308-
void ClearCached() { cached_ = false; }
321+
void ReleaseFirestore() { firestore_.release(); }
309322

310323
private:
311324
std::string name_;
312325
UniquePtr<Firestore> firestore_;
313-
bool cached_ = true;
314326
};
315327

316328
// The Firestore and App instance caches.

firestore/src/tests/firestore_test.cc

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
#if defined(__ANDROID__)
1313
#include "firestore/src/android/exception_android.h"
14+
#include "firestore/src/android/jni_runnable_android.h"
15+
#include "firestore/src/jni/env.h"
16+
#include "firestore/src/jni/ownership.h"
17+
#include "firestore/src/jni/task.h"
18+
#include "firestore/src/tests/android/firestore_integration_test_android.h"
1419
#endif // defined(__ANDROID__)
1520

1621
#include "app/memory/unique_ptr.h"
@@ -1429,13 +1434,6 @@ TEST_F(FirestoreIntegrationTest, CanClearPersistenceOnANewFirestoreInstance) {
14291434
const std::string path = document.path();
14301435
WriteDocument(document, MapFieldValue{{"foo", FieldValue::Integer(42)}});
14311436

1432-
#if defined(__ANDROID__)
1433-
// TODO(b/168628900) Remove this call to Terminate() once deleting the
1434-
// Firestore* instance removes the underlying Java object from the instance
1435-
// cache in Android.
1436-
EXPECT_THAT(db->Terminate(), FutureSucceeds());
1437-
#endif
1438-
14391437
// Call DeleteFirestore() to ensure that both the App and Firestore instances
14401438
// are deleted, which emulates the way an end user would experience their
14411439
// application being killed and later re-launched by the user.
@@ -1543,6 +1541,21 @@ TEST_F(FirestoreIntegrationTest, FirestoreCanBeDeletedFromTransaction) {
15431541
}
15441542
#endif // #if !defined(__ANDROID__)
15451543

1544+
#if defined(__ANDROID__)
1545+
TEST_F(FirestoreAndroidIntegrationTest,
1546+
CanDeleteFirestoreInstanceOnJavaMainThread) {
1547+
jni::Env env;
1548+
Firestore* db = TestFirestore();
1549+
auto runnable = MakeJniRunnable(env, [db] { delete db; });
1550+
1551+
jni::Local<jni::Task> task = runnable.RunOnMainThread(env);
1552+
1553+
Await(env, task);
1554+
EXPECT_TRUE(task.IsSuccessful(env));
1555+
DisownFirestore(db); // Avoid double-deletion of the `db`.
1556+
}
1557+
#endif // defined(__ANDROID__)
1558+
15461559
#endif // defined(FIRESTORE_STUB_BUILD)
15471560

15481561
} // namespace firestore
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.google.firebase.firestore.internal.cpp;
2+
3+
import com.google.android.gms.tasks.OnCompleteListener;
4+
import com.google.android.gms.tasks.Task;
5+
import java.util.concurrent.CountDownLatch;
6+
import java.util.concurrent.ExecutorService;
7+
import java.util.concurrent.Executors;
8+
9+
/** Helper methods for working with {@link Task} objects from C++. */
10+
public final class FirestoreTasks {
11+
12+
private FirestoreTasks() {}
13+
14+
/**
15+
* Blocks the calling thread until the given {@link Task} has completed.
16+
*
17+
* <p>This method is identical to {@link com.google.android.gms.tasks.Tasks#await} except that it
18+
* does <em>not</em> throw an exception if invoked from the main thread. Since it is technically
19+
* possible to wait for a {@link Task} from any thread in C++, throwing is undesirable if called
20+
* from the main thread.
21+
*
22+
* <p>The result of the given {@link Task} (i.e. success or failure) is not considered by this
23+
* method; whenever the task completes, either successfully or unsuccessfully, this method will
24+
* return.
25+
*
26+
* @param task The task whose completion to await.
27+
* @throws InterruptedException if waiting for the task to complete is interrupted.
28+
*/
29+
public static <T> void awaitCompletion(Task<T> task) throws InterruptedException {
30+
CountDownLatch countDownLatch = new CountDownLatch(1);
31+
ExecutorService executor = Executors.newSingleThreadExecutor();
32+
try {
33+
task.addOnCompleteListener(executor, new CountDownOnCompleteListener<T>(countDownLatch));
34+
countDownLatch.await();
35+
} finally {
36+
executor.shutdown();
37+
}
38+
}
39+
40+
private static final class CountDownOnCompleteListener<T> implements OnCompleteListener<T> {
41+
private final CountDownLatch countDownLatch;
42+
43+
CountDownOnCompleteListener(CountDownLatch countDownLatch) {
44+
this.countDownLatch = countDownLatch;
45+
}
46+
47+
@Override
48+
public void onComplete(Task<T> task) {
49+
countDownLatch.countDown();
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)