Skip to content

Commit 6eb3de8

Browse files
authored
Fix notifications on Linux 5.5 and above (#7488)
1 parent d923fc6 commit 6eb3de8

File tree

5 files changed

+179
-103
lines changed

5 files changed

+179
-103
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
## 10.5.1 (YYYY-MM-DD)
2+
3+
### Enhancements
4+
* None.
5+
6+
### Fixes
7+
* [RealmApp] Errors related to "uncaught exception in notifier thread: N5realm11KeyNotFoundE: No such object". This could happen in a sync'd app when a linked object was deleted by another client.
8+
* Notifications now trigger correctly on Linux kernel 5.5 and above. So far this only impacted the preview emulator image for Android 12. (Issue[#7321](https://github.com/realm/realm-java/issues/7321))
9+
10+
11+
### Compatibility
12+
* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1.
13+
* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series.
14+
* Realm Studio 10.0.0 or above is required to open Realms created by this version.
15+
16+
### Internal
17+
* Updated to Realm Core 10.7.2, commit ae14e6be382a72a9252d4e6710c2710c1ea4e688.
18+
19+
120
## 10.5.0 (2021-05-07)
221

322
### Breaking Changes

realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java

Lines changed: 128 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,25 @@
3030
import android.os.Messenger;
3131
import android.os.RemoteException;
3232
import androidx.test.platform.app.InstrumentationRegistry;
33-
import androidx.test.annotation.UiThreadTest;
3433
import androidx.test.ext.junit.runners.AndroidJUnit4;
3534

3635
import org.junit.After;
3736
import org.junit.Before;
38-
import org.junit.Ignore;
3937
import org.junit.Test;
4038
import org.junit.runner.RunWith;
4139

4240
import java.util.List;
4341
import java.util.concurrent.CountDownLatch;
4442
import java.util.concurrent.TimeUnit;
43+
import java.util.concurrent.atomic.AtomicInteger;
4544

4645
import io.realm.entities.AllTypes;
4746
import io.realm.entities.AllTypesModelModule;
47+
import io.realm.log.RealmLog;
48+
import io.realm.rule.BlockingLooperThread;
4849
import io.realm.services.RemoteProcessService;
50+
import kotlin.Unit;
51+
import kotlin.jvm.functions.Function0;
4952

5053
import static org.junit.Assert.assertEquals;
5154
import static org.junit.Assert.assertTrue;
@@ -64,7 +67,6 @@
6467
// 1. Open two Realms
6568
// B. Open three Realms
6669
// 2. assertTrue("OK, remote process win. You can open more Realms than I do in the main local process", false);
67-
@Ignore // FIXME Needs to be upgraded to support JUnit4: https://github.com/realm/realm-java/issues/6452
6870
@RunWith(AndroidJUnit4.class)
6971
public class RealmInterprocessTest {
7072

@@ -76,7 +78,9 @@ public class RealmInterprocessTest {
7678
@Override
7779
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
7880
remoteMessenger = new Messenger(iBinder);
79-
serviceStartLatch.countDown();
81+
if (serviceStartLatch != null) {
82+
serviceStartLatch.countDown();
83+
}
8084
}
8185

8286
@Override
@@ -88,45 +92,14 @@ public void onServiceDisconnected(ComponentName componentName) {
8892
}
8993
};
9094

91-
// It is necessary to overload this method.
92-
// AndroidTestRunner does call Looper.prepare() and we can have a looper in the case. The problem is all the test
93-
// cases are running in a single thread!!! And after Looper.quit() called, it cannot start again. That means we
94-
// can only have one case in this class LoL.
95-
// By overloading this method, we create a new thread and looper to run the real case. And use latch to wait until
96-
// it is finished. Then we can get rid of creating the thread in the test method, using array to store exception, many
97-
// levels of nested code. Make the test case more nature.
98-
// @Override
99-
// public void runBare() throws Throwable {
100-
// final Throwable[] throwableArray = new Throwable[1];
101-
// final CountDownLatch latch = new CountDownLatch(1);
102-
// Thread thread = new Thread(new Runnable() {
103-
// @Override
104-
// public void run() {
105-
// Looper.prepare();
106-
// try {
107-
// RealmInterprocessTest.super.runBare();
108-
// } catch (Throwable throwable) {
109-
// throwableArray[0] = throwable;
110-
// } finally {
111-
// latch.countDown();
112-
// }
113-
// }
114-
// });
115-
//
116-
// thread.start();
117-
// TestHelper.awaitOrFail(latch);
118-
//
119-
// if (throwableArray[0] != null) {
120-
// throw throwableArray[0];
121-
// }
122-
// }
95+
BlockingLooperThread looperThread = new BlockingLooperThread();
12396

12497
// Helper handler to make it easy to interact with remote service process.
12598
@SuppressLint("HandlerLeak") // SuppressLint bug, doesn't work
12699
private class InterprocessHandler extends Handler {
127100
// Timeout Watchdog. In case the service crashed or expected response is not returned.
128101
// It is very important to feed the dog after the expected message arrived.
129-
private final static int timeout = 5000;
102+
private final static int timeout = 10_000;
130103
private volatile boolean isTimeout = true;
131104
private Runnable timeoutRunnable = new Runnable() {
132105
@Override
@@ -170,6 +143,7 @@ public void handleMessage(Message msg) {
170143

171144
@Before
172145
public void setUp() throws Exception {
146+
Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext());
173147
Realm.deleteRealm(getConfiguration());
174148

175149
// Starts the testing service.
@@ -186,10 +160,6 @@ private RealmConfiguration getConfiguration() {
186160
@After
187161
public void tearDown() throws Exception {
188162
int counter = 10;
189-
if (testRealm != null) {
190-
testRealm.close();
191-
}
192-
193163
getContext().unbindService(serviceConnection);
194164
remoteMessenger = null;
195165

@@ -253,81 +223,139 @@ private ActivityManager.RunningAppProcessInfo getRemoteProcessInfo() {
253223
// A. Opens a realm, closes it, then calls Runtime.getRuntime().exit(0).
254224
// 1. Waits 3 seconds to see if the service process existed.
255225
@Test
256-
@UiThreadTest
257226
public void exitProcess() {
258-
new InterprocessHandler(new Runnable() {
227+
looperThread.runBlocking("testThread", true, new Function0<Unit>() {
259228
@Override
260-
public void run() {
261-
// Step A
262-
triggerServiceStep(RemoteProcessService.stepExitProcess_A);
263-
}
264-
}) {
265-
266-
@SuppressWarnings("ConstantConditions")
267-
final int servicePid = getServiceInfo().pid;
268-
269-
@Override
270-
public void handleMessage(Message msg) {
271-
super.handleMessage(msg);
272-
if (msg.what == RemoteProcessService.stepExitProcess_A.message) {
273-
// Step 1
274-
clearTimeoutFlag();
275-
try {
276-
// Timeout is 5 seconds. 3 (6x500ms) seconds should be enough to quit the process.
277-
for (int i = 1; i <= 6; i++) {
278-
// We need to retrieve the service's pid again since the system might restart it automatically.
279-
ActivityManager.RunningAppProcessInfo processInfo = getRemoteProcessInfo();
280-
if (processInfo != null && processInfo.pid == servicePid && i >= 6) {
281-
// The process is still alive.
282-
fail("Process is still alive");
283-
} else if (processInfo == null || processInfo.pid != servicePid) {
284-
// The process is gone.
285-
break;
229+
public Unit invoke() {
230+
new InterprocessHandler(new Runnable() {
231+
@Override
232+
public void run() {
233+
// Step A
234+
triggerServiceStep(RemoteProcessService.stepExitProcess_A);
235+
}
236+
}) {
237+
238+
@SuppressWarnings("ConstantConditions")
239+
final int servicePid = getServiceInfo().pid;
240+
241+
@Override
242+
public void handleMessage(Message msg) {
243+
super.handleMessage(msg);
244+
if (msg.what == RemoteProcessService.stepExitProcess_A.message) {
245+
// Step 1
246+
clearTimeoutFlag();
247+
try {
248+
// Timeout is 5 seconds. 3 (6x500ms) seconds should be enough to quit the process.
249+
for (int i = 1; i <= 6; i++) {
250+
// We need to retrieve the service's pid again since the system might restart it automatically.
251+
ActivityManager.RunningAppProcessInfo processInfo = getRemoteProcessInfo();
252+
if (processInfo != null && processInfo.pid == servicePid && i >= 6) {
253+
// The process is still alive.
254+
fail("Process is still alive");
255+
} else if (processInfo == null || processInfo.pid != servicePid) {
256+
// The process is gone.
257+
break;
258+
}
259+
Thread.sleep(500);
260+
}
261+
} catch (InterruptedException e) {
262+
e.printStackTrace();
263+
assertTrue(false);
286264
}
287-
Thread.sleep(500);
265+
looperThread.testComplete();
288266
}
289-
} catch (InterruptedException e) {
290-
e.printStackTrace();
291-
assertTrue(false);
292267
}
293-
done();
294-
}
268+
};
269+
return Unit.INSTANCE;
295270
}
296-
};
297-
Looper.loop();
271+
});
298272
}
299273

300274
// 1. Main process creates Realm, write one object.
301275
// A. Service process opens Realm, check if there is one and only one object.
302276
@Test
303-
@UiThreadTest
304277
public void createInitialRealm() throws InterruptedException {
305-
new InterprocessHandler(new Runnable() {
278+
looperThread.runBlocking("testThread", true, new Function0<Unit>() {
306279
@Override
307-
public void run() {
308-
// Step 1
309-
testRealm = Realm.getInstance(getConfiguration());
310-
assertEquals(0, testRealm.where(AllTypes.class).count());
311-
testRealm.beginTransaction();
312-
testRealm.createObject(AllTypes.class);
313-
testRealm.commitTransaction();
314-
315-
// Step A
316-
triggerServiceStep(RemoteProcessService.stepCreateInitialRealm_A);
280+
public Unit invoke() {
281+
new InterprocessHandler(new Runnable() {
282+
@Override
283+
public void run() {
284+
// Step 1
285+
testRealm = Realm.getInstance(getConfiguration());
286+
looperThread.closeAfterTest(testRealm);
287+
assertEquals(0, testRealm.where(AllTypes.class).count());
288+
testRealm.beginTransaction();
289+
testRealm.createObject(AllTypes.class);
290+
testRealm.commitTransaction();
291+
292+
// Step A
293+
triggerServiceStep(RemoteProcessService.stepCreateInitialRealm_A);
294+
}
295+
}) {
296+
297+
@Override
298+
public void handleMessage(Message msg) {
299+
super.handleMessage(msg);
300+
if (msg.what == RemoteProcessService.stepCreateInitialRealm_A.message) {
301+
clearTimeoutFlag();
302+
looperThread.testComplete();
303+
} else {
304+
assertTrue(false);
305+
}
306+
}
307+
};
308+
return Unit.INSTANCE;
317309
}
318-
}) {
310+
});
311+
}
312+
313+
@Test
314+
public void interprocessNotifications() {
315+
looperThread.runBlocking("testThread", true, new Function0<Unit>() {
316+
private RealmResults<AllTypes> results;
317+
private AtomicInteger updateCount = new AtomicInteger(0);
319318

320319
@Override
321-
public void handleMessage(Message msg) {
322-
super.handleMessage(msg);
323-
if (msg.what == RemoteProcessService.stepCreateInitialRealm_A.message) {
324-
clearTimeoutFlag();
325-
done();
326-
} else {
327-
assertTrue(false);
328-
}
320+
public Unit invoke() {
321+
new InterprocessHandler(new Runnable() {
322+
@Override
323+
public void run() {
324+
testRealm = Realm.getInstance(getConfiguration());
325+
looperThread.closeAfterTest(testRealm);
326+
results = testRealm.where(AllTypes.class).findAll();
327+
assertEquals(0, results.size());
328+
results.addChangeListener(new RealmChangeListener<RealmResults<AllTypes>>() {
329+
@Override
330+
public void onChange(RealmResults<AllTypes> allTypes) {
331+
switch(updateCount.incrementAndGet()) {
332+
case 1: {
333+
assertEquals(1, results.size());
334+
break;
335+
}
336+
case 2: {
337+
assertEquals(2, results.size());
338+
looperThread.testComplete();
339+
break;
340+
}
341+
}
342+
}
343+
});
344+
triggerServiceStep(RemoteProcessService.stepCreateObjects);
345+
}
346+
}) {
347+
@Override
348+
public void handleMessage(Message msg) {
349+
super.handleMessage(msg);
350+
if (msg.what == RemoteProcessService.stepCreateObjects.message) {
351+
clearTimeoutFlag();
352+
} else {
353+
assertTrue(false);
354+
}
355+
}
356+
};
357+
return Unit.INSTANCE;
329358
}
330-
};
331-
Looper.loop();
359+
});
332360
}
333361
}

realm/realm-library/src/main/cpp/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,11 @@ endif()
197197
#FIXME maybe-uninitialized is reported by table_view.cpp:272:15:
198198
# 'best.m_nanoseconds' was declared here
199199
# -Wno-missing-field-initializers disable in object store as well.
200+
# FIXME See https://github.com/realm/realm-core/issues/4732
200201
set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wmissing-declarations \
201202
-Wempty-body -Wparentheses -Wunknown-pragmas -Wunreachable-code \
202203
-Wno-missing-field-initializers -Wno-unevaluated-expression -Wno-unreachable-code \
203-
-Wno-c99-extensions")
204+
-Wno-c99-extensions -Wno-dtor-name")
204205
set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -fdata-sections -pthread -frtti -fvisibility=hidden -fsigned-char -fno-stack-protector -std=c++17")
205206
if (REALM_ENABLE_SYNC)
206207
set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC=1")
Submodule realm-core updated 67 files

realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import android.os.Message;
2525
import android.os.Messenger;
2626
import android.os.RemoteException;
27+
import android.os.SystemClock;
2728

2829
import java.util.HashMap;
2930
import java.util.Map;
@@ -152,8 +153,35 @@ void run() {
152153
}
153154
};
154155

156+
public final static Step stepCreateObjects = new Step(30) {
157+
158+
@Override
159+
void run() {
160+
thiz.testRealm = Realm.getInstance(getConfiguration());
161+
thiz.testRealm.executeTransaction(new Realm.Transaction() {
162+
@Override
163+
public void execute(Realm realm) {
164+
realm.createObject(AllTypes.class);
165+
}
166+
});
167+
SystemClock.sleep(1000);
168+
thiz.testRealm.executeTransaction(new Realm.Transaction() {
169+
@Override
170+
public void execute(Realm realm) {
171+
realm.createObject(AllTypes.class);
172+
}
173+
});
174+
thiz.testRealm.close();
175+
response(null);
176+
Runtime.getRuntime().exit(0);
177+
}
178+
};
179+
155180
private static RealmConfiguration getConfiguration() {
156-
return new RealmConfiguration.Builder().modules(new AllTypesModelModule()).build();
181+
return new RealmConfiguration.Builder()
182+
.allowQueriesOnUiThread(true)
183+
.allowWritesOnUiThread(true)
184+
.modules(new AllTypesModelModule()).build();
157185
}
158186

159187
}

0 commit comments

Comments
 (0)