1
1
#include " firestore/src/android/jni_runnable_android.h"
2
2
3
+ #include " app/memory/atomic.h"
4
+ #include " app/src/mutex.h"
3
5
#include " firestore/src/jni/declaration.h"
4
6
#include " firestore/src/jni/object.h"
5
7
#include " firestore/src/jni/ownership.h"
@@ -18,6 +20,7 @@ using jni::Global;
18
20
using jni::Local;
19
21
using jni::Method;
20
22
using jni::Object;
23
+ using jni::StaticField;
21
24
using jni::StaticMethod;
22
25
using jni::Task;
23
26
using jni::Throwable;
@@ -27,14 +30,18 @@ Method<Object> kLooperGetThread("getThread", "()Ljava/lang/Thread;");
27
30
Method<void > kRunnableRun (" run" , " ()V" );
28
31
StaticMethod<Object> kCurrentThread (" currentThread" , " ()Ljava/lang/Thread;" );
29
32
Method<jlong> kThreadGetId (" getId" , " ()J" );
33
+ Method<Object> kThreadGetState (" getState" , " ()Ljava/lang/Thread$State;" );
34
+ StaticField<Object> kThreadStateBlocked (" BLOCKED" , " Ljava/lang/Thread$State;" );
30
35
31
36
class JniRunnableTest : public FirestoreAndroidIntegrationTest {
32
37
public:
33
38
void SetUp () override {
34
39
FirestoreAndroidIntegrationTest::SetUp ();
35
40
loader ().LoadClass (" android/os/Looper" , kGetMainLooper , kLooperGetThread );
36
41
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 );
38
45
ASSERT_TRUE (loader ().ok ());
39
46
}
40
47
};
@@ -56,6 +63,16 @@ jlong GetMainThreadId(Env& env) {
56
63
return env.Call (main_thread, kThreadGetId );
57
64
}
58
65
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
+
59
76
TEST_F (JniRunnableTest, JavaRunCallsCppRun) {
60
77
Env env;
61
78
bool invoked = false ;
@@ -145,6 +162,27 @@ TEST_F(JniRunnableTest, DetachDetachesEvenIfAnExceptionIsPending) {
145
162
EXPECT_TRUE (env.ok ());
146
163
}
147
164
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
+
148
186
TEST_F (JniRunnableTest, DestructionCausesJavaRunToDoNothing) {
149
187
Env env;
150
188
bool invoked = false ;
@@ -191,29 +229,21 @@ TEST_F(JniRunnableTest, RunOnMainThreadTaskFailsIfRunThrowsException) {
191
229
}
192
230
193
231
TEST_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
-
215
232
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
+ });
217
247
218
248
Local<Task> task = runnable.RunOnMainThread (env);
219
249
@@ -252,6 +282,59 @@ TEST_F(JniRunnableTest, RunOnNewThreadTaskFailsIfRunThrowsException) {
252
282
EXPECT_TRUE (env.IsSameObject (exception , thrown_exception));
253
283
}
254
284
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
+
255
338
} // namespace
256
339
} // namespace firestore
257
340
} // namespace firebase
0 commit comments