Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frozen Realm doesn't reference count correctly #7504

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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