Skip to content

Commit

Permalink
Implement reference counting for frozen realms
Browse files Browse the repository at this point in the history
  • Loading branch information
alecbarber committed Feb 22, 2022
1 parent da73241 commit 84bbbcb
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,34 @@ public void freezeRealm() {
frozenRealm.close();
}

@Test
public void freezeRealmTwice_atSameVersion() {
// Freeze the realm twice at the same version
assertFalse(realm.isFrozen());
Realm frozenRealm1 = realm.freeze();
Realm frozenRealm2 = realm.freeze();

// If we close one frozen instance, the other instance should be unaffected
frozenRealm2.close();
assertFalse(frozenRealm1.isClosed());
frozenRealm1.close();
}

@Test
public void freezeRealmTwice_atDifferentVersions() {
// Freeze the realm twice at different versions
assertFalse(realm.isFrozen());
Realm frozenRealm1 = realm.freeze();
realm.executeTransaction((Realm) -> realm.copyToRealm(new Dog("Woof", 3)));
Realm frozenRealm2 = realm.freeze();

// If we close one frozen instance, the other instance should be unaffected
frozenRealm1.close();
assertTrue(frozenRealm1.isClosed());
assertFalse(frozenRealm2.isClosed());
frozenRealm2.close();
}

@Test
public void freezeDynamicRealm() {
DynamicRealm dynamicRealm = DynamicRealm.getInstance(realmConfig);
Expand Down
48 changes: 32 additions & 16 deletions realm/realm-library/src/main/java/io/realm/RealmCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,14 @@ interface Callback0 {

private abstract static class ReferenceCounter {

// How many references to this Realm instance in this thread.
protected final ThreadLocal<Integer> localCount = new ThreadLocal<>();
// How many threads have instances refer to this configuration.
protected AtomicInteger globalCount = new AtomicInteger(0);

// Returns `true` if an instance of the Realm is available on the caller thread.
abstract boolean hasInstanceAvailableForThread();

// Increment how many times an instance has been handed out for the current thread.
public void incrementThreadCount(int increment) {
Integer currentCount = localCount.get();
localCount.set(currentCount != null ? currentCount + increment : increment);
}
public abstract void incrementThreadCount(int increment);

// Returns the Realm instance for the caller thread
abstract BaseRealm getRealmInstance();
Expand All @@ -97,11 +92,9 @@ public void incrementThreadCount(int increment) {
abstract int getThreadLocalCount();

// Updates the number of references handed out for a given thread
public void setThreadCount(int refCount) {
localCount.set(refCount);
}
public abstract void setThreadCount(int refCount);

// Returns the number of gloal instances handed out. This is roughly equivalent
// Returns the number of global instances handed out. This is roughly equivalent
// to the number of threads currently using the Realm as each thread also does
// reference counting of Realm instances.
public int getGlobalCount() {
Expand All @@ -118,6 +111,11 @@ boolean hasInstanceAvailableForThread() {
return cachedRealm != null;
}

@Override
public void incrementThreadCount(int increment) {
globalCount.addAndGet(increment);
}

@Override
BaseRealm getRealmInstance() {
return cachedRealm;
Expand All @@ -128,19 +126,17 @@ void onRealmCreated(BaseRealm realm) {
// The Realm instance has been created without exceptions. Cache and reference count can be updated now.
cachedRealm = realm;

localCount.set(0);
// This is the first instance in current thread, increase the global count.
globalCount.incrementAndGet();
// The global count for a frozen Realm has the same role as the local count for a live
// Realm, so initialise it to 0.
globalCount.set(0);

}

@Override
public void clearThreadLocalCache() {
String canonicalPath = cachedRealm.getPath();

// The last instance in this thread.
// Clears local ref & counter.
localCount.set(null);
// The last instance of this frozen Realm. Clear reference.
cachedRealm = null;

// Clears global counter.
Expand All @@ -156,18 +152,33 @@ int getThreadLocalCount() {
// of a thread local count doesn't make sense. Just return the global count instead.
return globalCount.get();
}

@Override
public void setThreadCount(int refCount) {
// Since the concept of a thread local count doesn't make sense, we instead set the
// global count here.
globalCount.set(refCount);
}
}

// Reference counter for Realms that are thread confined
private static class ThreadConfinedReferenceCounter extends ReferenceCounter {
// The Realm instance in this thread.
private final ThreadLocal<BaseRealm> localRealm = new ThreadLocal<>();
// How many references to this Realm instance in this thread.
private final ThreadLocal<Integer> localCount = new ThreadLocal<>();

@Override
public boolean hasInstanceAvailableForThread() {
return localRealm.get() != null;
}

@Override
public void incrementThreadCount(int increment) {
Integer currentCount = localCount.get();
localCount.set(currentCount != null ? currentCount + increment : increment);
}

@Override
public BaseRealm getRealmInstance() {
return localRealm.get();
Expand Down Expand Up @@ -203,6 +214,11 @@ public int getThreadLocalCount() {
Integer refCount = localCount.get();
return (refCount != null) ? refCount : 0;
}

@Override
public void setThreadCount(int refCount) {
localCount.set(refCount);
}
}

private enum RealmCacheType {
Expand Down

0 comments on commit 84bbbcb

Please sign in to comment.