Skip to content

Commit 1b78742

Browse files
author
alecbarber
committed
Implement reference counting for frozen realms.
1 parent 74f4a53 commit 1b78742

File tree

2 files changed

+135
-10
lines changed

2 files changed

+135
-10
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Copyright 2021 Alec Barber.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* TODO: Transfer copyright to Realm Inc.
17+
*/
18+
19+
package io.realm
20+
21+
import android.util.Log
22+
import androidx.test.ext.junit.runners.AndroidJUnit4
23+
import androidx.test.platform.app.InstrumentationRegistry
24+
import io.realm.entities.Cat
25+
import io.realm.entities.Dog
26+
import io.realm.entities.DogPrimaryKey
27+
import io.realm.entities.Owner
28+
import org.junit.After
29+
import org.junit.Before
30+
import org.junit.Rule
31+
import org.junit.Test
32+
import org.junit.runner.RunWith
33+
import kotlin.test.assertEquals
34+
import kotlin.test.assertFalse
35+
import kotlin.test.assertTrue
36+
37+
@RunWith(AndroidJUnit4::class)
38+
class FrozenRealmTests {
39+
40+
@get:Rule
41+
val configFactory = TestRealmConfigurationFactory()
42+
43+
private lateinit var realmConfiguration: RealmConfiguration
44+
private lateinit var realm: Realm
45+
46+
@Before
47+
fun setUp() {
48+
Realm.init(InstrumentationRegistry.getInstrumentation().targetContext)
49+
Log.i("FrozenRealmTests", "Realm initialised")
50+
realmConfiguration = configFactory.createConfigurationBuilder()
51+
.schema(Dog::class.java, Cat::class.java, DogPrimaryKey::class.java, Owner::class.java)
52+
.build()
53+
Log.i("FrozenRealmTests", "Realm configured")
54+
realm = Realm.getInstance(realmConfiguration)
55+
Log.i("FrozenRealmTests", "Realm created")
56+
}
57+
58+
@After
59+
fun tearDown() {
60+
realm.close()
61+
assertEquals(Realm.getGlobalInstanceCount(realmConfiguration), 0)
62+
}
63+
64+
@Test
65+
fun freezeTwice_closeTwice_noModification() {
66+
// Freeze the realm twice at the same version
67+
val realm1 = realm.freeze()
68+
val realm2 = realm.freeze()
69+
70+
// If we close one frozen instance, the other instance should be unaffected
71+
realm2.close()
72+
assertFalse { realm1.isClosed }
73+
realm1.close()
74+
// Check that the only surviving instance is `realm`
75+
assertEquals(Realm.getGlobalInstanceCount(realmConfiguration), 1)
76+
}
77+
78+
@Test
79+
fun freezeTwice_closeTwice_writeTransaction() {
80+
// Freeze the realm twice at different versions
81+
val realm1 = realm.freeze()
82+
realm.executeTransaction {
83+
realm.copyToRealm(Dog("Woof", 3))
84+
}
85+
val realm2 = realm.freeze()
86+
87+
// If we close one frozen instance, the other instance should be unaffected
88+
realm1.close()
89+
assertFalse { realm2.isClosed }
90+
realm2.close()
91+
// Check that the only surviving instance is `realm`
92+
assertEquals(Realm.getGlobalInstanceCount(realmConfiguration), 1)
93+
}
94+
95+
@Test
96+
fun closeUnderlying_openFrozenRealms() {
97+
val realm1 = realm.freeze()
98+
99+
// If we close the last global instance of the live realm, the frozen realm should be closed
100+
realm.close()
101+
assertTrue { realm1.isClosed }
102+
103+
// Check that there are no global instances left
104+
assertEquals(Realm.getGlobalInstanceCount(realmConfiguration), 0)
105+
}
106+
}

realm/realm-library/src/main/java/io/realm/RealmCache.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,7 @@ private abstract static class ReferenceCounter {
7777
abstract boolean hasInstanceAvailableForThread();
7878

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

8582
// Returns the Realm instance for the caller thread
8683
abstract BaseRealm getRealmInstance();
@@ -95,11 +92,9 @@ public void incrementThreadCount(int increment) {
9592
abstract int getThreadLocalCount();
9693

9794
// Updates the number of references handed out for a given thread
98-
public void setThreadCount(int refCount) {
99-
localCount.set(refCount);
100-
}
95+
public abstract void setThreadCount(int refCount);
10196

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

114+
@Override
115+
public void incrementThreadCount(int increment) {
116+
globalCount.addAndGet(increment);
117+
}
118+
119119
@Override
120120
BaseRealm getRealmInstance() {
121121
return cachedRealm;
@@ -127,8 +127,9 @@ void onRealmCreated(BaseRealm realm) {
127127
cachedRealm = realm;
128128

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

133134
}
134135

@@ -154,6 +155,13 @@ int getThreadLocalCount() {
154155
// of a thread local count doesn't make sense. Just return the global count instead.
155156
return globalCount.get();
156157
}
158+
159+
@Override
160+
public void setThreadCount(int refCount) {
161+
// Since the concept of a thread local count doesn't make sense, we instead set the
162+
// global count here.
163+
globalCount.set(refCount);
164+
}
157165
}
158166

159167
// Reference counter for Realms that are thread confined
@@ -166,6 +174,12 @@ public boolean hasInstanceAvailableForThread() {
166174
return localRealm.get() != null;
167175
}
168176

177+
@Override
178+
public void incrementThreadCount(int increment) {
179+
Integer currentCount = localCount.get();
180+
localCount.set(currentCount != null ? currentCount + increment : increment);
181+
}
182+
169183
@Override
170184
public BaseRealm getRealmInstance() {
171185
return localRealm.get();
@@ -201,6 +215,11 @@ public int getThreadLocalCount() {
201215
Integer refCount = localCount.get();
202216
return (refCount != null) ? refCount : 0;
203217
}
218+
219+
@Override
220+
public void setThreadCount(int refCount) {
221+
localCount.set(refCount);
222+
}
204223
}
205224

206225
private enum RealmCacheType {

0 commit comments

Comments
 (0)