11#include " firestore/src/android/jni_runnable_android.h"
22
3+ #include " app/memory/atomic.h"
4+ #include " app/src/mutex.h"
35#include " firestore/src/jni/declaration.h"
46#include " firestore/src/jni/object.h"
57#include " firestore/src/jni/ownership.h"
@@ -18,6 +20,7 @@ using jni::Global;
1820using jni::Local;
1921using jni::Method;
2022using jni::Object;
23+ using jni::StaticField;
2124using jni::StaticMethod;
2225using jni::Task;
2326using jni::Throwable;
@@ -27,14 +30,18 @@ Method<Object> kLooperGetThread("getThread", "()Ljava/lang/Thread;");
2730Method<void > kRunnableRun (" run" , " ()V" );
2831StaticMethod<Object> kCurrentThread (" currentThread" , " ()Ljava/lang/Thread;" );
2932Method<jlong> kThreadGetId (" getId" , " ()J" );
33+ Method<Object> kThreadGetState (" getState" , " ()Ljava/lang/Thread$State;" );
34+ StaticField<Object> kThreadStateBlocked (" BLOCKED" , " Ljava/lang/Thread$State;" );
3035
3136class JniRunnableTest : public FirestoreAndroidIntegrationTest {
3237 public:
3338 void SetUp () override {
3439 FirestoreAndroidIntegrationTest::SetUp ();
3540 loader ().LoadClass (" android/os/Looper" , kGetMainLooper , kLooperGetThread );
3641 loader ().LoadClass (" java/lang/Runnable" , kRunnableRun );
37- loader ().LoadClass (" java/lang/Thread" , kCurrentThread , kThreadGetId );
42+ loader ().LoadClass (" java/lang/Thread" , kCurrentThread , kThreadGetId ,
43+ kThreadGetState );
44+ loader ().LoadClass (" java/lang/Thread$State" , kThreadStateBlocked );
3845 ASSERT_TRUE (loader ().ok ());
3946 }
4047};
@@ -56,6 +63,16 @@ jlong GetMainThreadId(Env& env) {
5663 return env.Call (main_thread, kThreadGetId );
5764}
5865
66+ /* *
67+ * Returns whether or not the given thread is in the "blocked" state.
68+ * See java.lang.Thread.State.BLOCKED.
69+ */
70+ bool IsThreadBlocked (Env& env, Object& thread) {
71+ Local<Object> actual_state = env.Call (thread, kThreadGetState );
72+ Local<Object> expected_state = env.Get (kThreadStateBlocked );
73+ return Object::Equals (env, expected_state, actual_state);
74+ }
75+
5976TEST_F (JniRunnableTest, JavaRunCallsCppRun) {
6077 Env env;
6178 bool invoked = false ;
@@ -145,6 +162,27 @@ TEST_F(JniRunnableTest, DetachDetachesEvenIfAnExceptionIsPending) {
145162 EXPECT_TRUE (env.ok ());
146163}
147164
165+ // Verify that b/181129657 does not regress; that is, calling `Detach()` from
166+ // `Run()` should not deadlock.
167+ TEST_F (JniRunnableTest, DetachCanBeCalledFromRun) {
168+ Env env;
169+ int run_count = 0 ;
170+ auto runnable = MakeJniRunnable (env, [&run_count](JniRunnableBase& runnable) {
171+ ++run_count;
172+ Env env;
173+ runnable.Detach (env);
174+ });
175+ Local<Object> java_runnable = runnable.GetJavaRunnable ();
176+
177+ // Call `run()` twice to verify that the call to `Detach()` successfully
178+ // detaches and the second `run()` invocation does not call C++ `Run()`.
179+ env.Call (java_runnable, kRunnableRun );
180+ env.Call (java_runnable, kRunnableRun );
181+
182+ EXPECT_TRUE (env.ok ());
183+ EXPECT_EQ (run_count, 1 );
184+ }
185+
148186TEST_F (JniRunnableTest, DestructionCausesJavaRunToDoNothing) {
149187 Env env;
150188 bool invoked = false ;
@@ -191,29 +229,21 @@ TEST_F(JniRunnableTest, RunOnMainThreadTaskFailsIfRunThrowsException) {
191229}
192230
193231TEST_F (JniRunnableTest, RunOnMainThreadRunsSynchronouslyFromMainThread) {
194- class ChainedMainThreadJniRunnable : public JniRunnableBase {
195- public:
196- using JniRunnableBase::JniRunnableBase;
197-
198- void Run () override {
199- Env env;
200- EXPECT_EQ (GetCurrentThreadId (env), GetMainThreadId (env));
201- if (is_nested_call_) {
202- return ;
203- }
204- is_nested_call_ = true ;
205- Local<Task> task = RunOnMainThread (env);
206- EXPECT_TRUE (task.IsComplete (env));
207- EXPECT_TRUE (task.IsSuccessful (env));
208- is_nested_call_ = false ;
209- }
210-
211- private:
212- bool is_nested_call_ = false ;
213- };
214-
215232 Env env;
216- ChainedMainThreadJniRunnable runnable (env);
233+ bool is_recursive_call = false ;
234+ auto runnable =
235+ MakeJniRunnable (env, [&is_recursive_call](JniRunnableBase& runnable) {
236+ Env env;
237+ EXPECT_EQ (GetCurrentThreadId (env), GetMainThreadId (env));
238+ if (is_recursive_call) {
239+ return ;
240+ }
241+ is_recursive_call = true ;
242+ Local<Task> task = runnable.RunOnMainThread (env);
243+ EXPECT_TRUE (task.IsComplete (env));
244+ EXPECT_TRUE (task.IsSuccessful (env));
245+ is_recursive_call = false ;
246+ });
217247
218248 Local<Task> task = runnable.RunOnMainThread (env);
219249
@@ -252,6 +282,59 @@ TEST_F(JniRunnableTest, RunOnNewThreadTaskFailsIfRunThrowsException) {
252282 EXPECT_TRUE (env.IsSameObject (exception, thrown_exception));
253283}
254284
285+ TEST_F (JniRunnableTest, DetachReturnsAfterLastRunOnAnotherThreadCompletes) {
286+ Env env;
287+ compat::Atomic<int32_t > runnable1_run_invoke_count;
288+ runnable1_run_invoke_count.store (0 );
289+ Mutex detach_thread_mutex;
290+ Global<Object> detach_thread;
291+
292+ auto runnable1 = MakeJniRunnable (
293+ env, [&runnable1_run_invoke_count, &detach_thread, &detach_thread_mutex] {
294+ runnable1_run_invoke_count.fetch_add (1 );
295+ Env env;
296+ // Wait for `detach()` to be called and start blocking; then, return to
297+ // allow `detach()` to unblock and do its job.
298+ while (env.ok ()) {
299+ MutexLock lock (detach_thread_mutex);
300+ if (detach_thread && IsThreadBlocked (env, detach_thread)) {
301+ break ;
302+ }
303+ }
304+ EXPECT_TRUE (env.ok ()) << " IsThreadBlocked() failed with an exception" ;
305+ });
306+
307+ auto runnable2 =
308+ MakeJniRunnable (env, [&runnable1, &detach_thread, &detach_thread_mutex] {
309+ Env env;
310+ {
311+ MutexLock lock (detach_thread_mutex);
312+ detach_thread = env.Call (kCurrentThread );
313+ }
314+ runnable1.Detach (env);
315+ EXPECT_TRUE (env.ok ()) << " Detach() failed with an exception" ;
316+ });
317+
318+ // Wait for the `runnable1.Run()` to start to ensure that the lock is held.
319+ Local<Task> task1 = runnable1.RunOnNewThread (env);
320+ while (true ) {
321+ if (runnable1_run_invoke_count.load () != 0 ) {
322+ break ;
323+ }
324+ }
325+
326+ // Start a new thread to call `runnable1.Detach()`.
327+ Local<Task> task2 = runnable2.RunOnNewThread (env);
328+
329+ Await (env, task1);
330+ Await (env, task2);
331+
332+ // Invoke `run()` again and ensure that `Detach()` successfully did its job;
333+ // that is, verify that `Run()` is not invoked.
334+ env.Call (runnable1.GetJavaRunnable (), kRunnableRun );
335+ EXPECT_EQ (runnable1_run_invoke_count.load (), 1 );
336+ }
337+
255338} // namespace
256339} // namespace firestore
257340} // namespace firebase
0 commit comments