Skip to content

Commit

Permalink
Fix async transaction problem when async query already exist.
Browse files Browse the repository at this point in the history
1. Fix async transaction problelm when async query already exist.
2. Add the warning when closing a Realm with pending transactions.

Fix #1983
  • Loading branch information
dalinaum committed May 14, 2016
1 parent eaa3541 commit 734d713
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 121 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.90.2

### Bug fixes

* `OnSuccess.OnSuccess()` might not be called with the correct Realm version for async transaction (#1893).

## 0.90.1

* Updated Realm Core to 0.100.2.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public void onSuccess() {
@Override
public void onError(Throwable error) {
// Ensure we are giving developers quality messages in the logs.
assertEquals("Could not cancel transaction, not currently in a transaction.", testLogger.message);
assertEquals("Could not cancel transaction, not currently in a transaction.", testLogger.previousMessage);
RealmLog.remove(testLogger);
looperThread.testComplete();
}
Expand Down Expand Up @@ -281,6 +281,88 @@ public void execute(Realm realm) {
}, transactionCallback);
}

// Test case for https://github.com/realm/realm-java/issues/1893
// Ensure that onSuccess is called with the correct Realm version for async transaction.
@Test
@RunTestInLooperThread
public void executeTransactionAsync_asyncQuery() {
final Realm realm = looperThread.realm;
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllAsync();
assertEquals(0, results.size());

realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.createObject(AllTypes.class);
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
assertEquals(realm.where(AllTypes.class).count(), 1);
looperThread.testComplete();
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
fail();
}
});
}

// Test case for https://github.com/realm/realm-java/issues/1893
// Ensure that Realm.close() logs a warning if you close a Realm with pending transactions.
@Test
@UiThreadTest
public void executeTransactionAsync_closeWithPendingTransaction() throws NoSuchFieldException, IllegalAccessException {
RealmConfiguration config = configFactory.createConfiguration();
Realm realm = Realm.getInstance(config);

final CountDownLatch closeLatch = new CountDownLatch(1);
final CountDownLatch waitAsyncTransaction = new CountDownLatch(1);

realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
TestHelper.awaitOrFail(closeLatch);
realm.close(); // It is for avoiding java.lang.IllegalStateException: It's not allowed to delete the file associated with an open Realm.
waitAsyncTransaction.countDown();
}
});

final TestHelper.TestLogger testLogger = new TestHelper.TestLogger();
RealmLog.add(testLogger);
realm.close();
RealmLog.remove(testLogger);
String[] logs = testLogger.message.split("will ");
assertEquals("be closed with pending async transactions or callbacks.", logs[1]);

closeLatch.countDown();
TestHelper.awaitOrFail(waitAsyncTransaction);
}

// Test case for https://github.com/realm/realm-java/issues/1893
// Ensure that Realm.close() logs a warning if you close a Realm with pending callbacks.
@Test
@UiThreadTest
public void executeTransactionAsync_closeWithPendingTransactionCallback() {
RealmConfiguration config = configFactory.createConfiguration();
Realm realm = Realm.getInstance(config);

realm.asyncTransactionCallbacks.add(new Runnable() {
@Override
public void run() {
}
});

final TestHelper.TestLogger testLogger = new TestHelper.TestLogger();
RealmLog.add(testLogger);
realm.close();
RealmLog.remove(testLogger);

String[] logs = testLogger.message.split("will ");
assertEquals("be closed with pending async transactions or callbacks.", logs[1]);
}

// ************************************
// *** promises based async queries ***
// ************************************
Expand Down Expand Up @@ -1948,7 +2030,7 @@ public void onChange(RealmResults<AllTypes> object) {
// 3. The async query should now (hopefully) fail with a BadVersion
result.load();
}

// handlerController#emptyAsyncRealmObject is accessed from different threads
// make sure that we iterate over it safely without any race condition (ConcurrentModification)
@Test
Expand Down Expand Up @@ -2004,7 +2086,7 @@ public void run() {
@Test
@UiThreadTest
public void concurrentModificationRealmObjects() {
RealmConfiguration config = configFactory.createConfiguration();
RealmConfiguration config = configFactory.createConfiguration();
final Realm realm = Realm.getInstance(config);
Dog dog1 = new Dog();
dog1.setName("Dog 1");
Expand All @@ -2017,8 +2099,8 @@ public void concurrentModificationRealmObjects() {
dog2 = realm.copyToRealm(dog2);
realm.commitTransaction();

final WeakReference<RealmObjectProxy> weakReference1 = new WeakReference<RealmObjectProxy>((RealmObjectProxy) dog1);
final WeakReference<RealmObjectProxy> weakReference2 = new WeakReference<RealmObjectProxy>((RealmObjectProxy) dog2);
final WeakReference<RealmObjectProxy> weakReference1 = new WeakReference<RealmObjectProxy>((RealmObjectProxy)dog1);
final WeakReference<RealmObjectProxy> weakReference2 = new WeakReference<RealmObjectProxy>((RealmObjectProxy)dog2);

realm.handlerController.realmObjects.put(weakReference1, Boolean.TRUE);

Expand All @@ -2045,36 +2127,6 @@ public void run() {
realm.close();
}

@Test
@RunTestInLooperThread
public void testAsyncTransactionWorksWithAsyncQuery() {
RealmResults<AllTypes> results = looperThread.realm.where(AllTypes.class).findAllAsync();
results.addChangeListener(new RealmChangeListener() {
@Override
public void onChange() {
//assertEquals(looperThread.realm.where(AllTypes.class).count(), 1);
}
});
looperThread.keepStrongReference.add(results);
looperThread.realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.createObject(AllTypes.class);
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
assertEquals(looperThread.realm.where(AllTypes.class).count(), 1);
looperThread.testComplete();
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
fail();
}
});
}

// *** Helper methods ***

private void populateTestRealm(final Realm testRealm, int objects) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,12 @@ public void changeListener_syncIfNeeded_updatedFromOtherThread() {
assertEquals(10, results.size());

// 1. Delete first object from another thread.
realm.executeTransaction(new Realm.Transaction() {
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 0).findFirst().removeFromRealm();
}
}, new Realm.Transaction.Callback() {
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
// 2. RealmResults are refreshed before onSuccess is called
Expand Down
17 changes: 17 additions & 0 deletions realm/realm-library/src/androidTest/java/io/realm/TestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -246,60 +246,77 @@ public static String getRandomString(int length) {
*/
public static class TestLogger implements Logger {

public String previousMessage;
public String message;
public Throwable throwable;

private void backupMessage() {
if (this.message != null) {
previousMessage = this.message;
}
}

@Override
public void v(String message) {
backupMessage();
this.message = message;
}

@Override
public void v(String message, Throwable t) {
backupMessage();
this.message = message;
this.throwable = t;
}

@Override
public void d(String message) {
backupMessage();
this.message = message;
}

@Override
public void d(String message, Throwable t) {
backupMessage();
this.message = message;
this.throwable = t;
}

@Override
public void i(String message) {
backupMessage();
this.message = message;
}

@Override
public void i(String message, Throwable t) {
backupMessage();
this.message = message;
this.throwable = t;
}

@Override
public void w(String message) {
backupMessage();
this.message = message;
}

@Override
public void w(String message, Throwable t) {
backupMessage();
this.message = message;
this.throwable = t;
}

@Override
public void e(String message) {
backupMessage();
this.message = message;
}

@Override
public void e(String message, Throwable t) {
backupMessage();
this.message = message;
this.throwable = t;
}
Expand Down
7 changes: 7 additions & 0 deletions realm/realm-library/src/main/java/io/realm/BaseRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -66,6 +67,7 @@ abstract class BaseRealm implements Closeable {
RealmSchema schema;
Handler handler;
HandlerController handlerController;
List<Runnable> asyncTransactionCallbacks = new ArrayList<Runnable>();

static {
RealmLog.add(BuildConfig.DEBUG ? new DebugAndroidLogger() : new ReleaseAndroidLogger());
Expand Down Expand Up @@ -458,6 +460,11 @@ public void close() {
* Closes the Realm instances and all its resources without checking the {@link RealmCache}.
*/
void doClose() {
if (asyncTaskExecutor.hasPendingTransactions() || !asyncTransactionCallbacks.isEmpty()) {
String canonicalPath = getPath();
RealmLog.w("Realm " + canonicalPath + " will be closed with pending async transactions or callbacks.");
}

if (sharedGroupManager != null) {
sharedGroupManager.close();
sharedGroupManager = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,17 @@ private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) {

updateAsyncQueriesTask = null;
}

executeAsyncTransactionCallbacks();
}

void executeAsyncTransactionCallbacks() {
if (!realm.asyncTransactionCallbacks.isEmpty()) {
for (Runnable transactionCallback : realm.asyncTransactionCallbacks) {
realm.handler.post(transactionCallback);
}
realm.asyncTransactionCallbacks.clear();
}
}

private void completedAsyncRealmObject(QueryUpdateTask.Result result) {
Expand Down Expand Up @@ -584,7 +595,7 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) {
* @return {@code true} if there is at least one (non GC'ed) instance of {@link RealmResults} {@code false}
* otherwise.
*/
private boolean threadContainsAsyncQueries() {
boolean threadContainsAsyncQueries() {
boolean isEmpty = true;
Iterator<Map.Entry<WeakReference<RealmResults<? extends RealmModel>>, RealmQuery<?>>> iterator = asyncRealmResults.entrySet().iterator();
while (iterator.hasNext()) {
Expand Down
Loading

0 comments on commit 734d713

Please sign in to comment.