@react-native-firebase/database 26.3.3 → 26.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/RNFBDatabase.podspec +3 -1
- package/android/build.gradle +2 -0
- package/android/src/main/java/io/invertase/firebase/database/DatabaseAbortable.java +22 -0
- package/android/src/main/java/io/invertase/firebase/database/DatabaseQueryHandle.java +24 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistry.java +117 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistry.java +78 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistry.java +71 -0
- package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseQuery.java +18 -22
- package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseTransaction.java +12 -25
- package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQuery.java +30 -33
- package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseTransactionHandler.java +3 -3
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistryTest.java +160 -0
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistryTest.java +434 -0
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistryTest.java +199 -0
- package/dist/module/version.js +1 -1
- package/dist/typescript/lib/index.d.ts +1 -1
- package/dist/typescript/lib/version.d.ts +1 -1
- package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.h +37 -0
- package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.m +84 -0
- package/ios/RNFBDatabase/RNFBDatabaseQuery.h +3 -2
- package/ios/RNFBDatabase/RNFBDatabaseQuery.m +14 -13
- package/ios/RNFBDatabase/RNFBDatabaseQueryHelper.m +24 -21
- package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.h +37 -0
- package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.m +105 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseListenerRegistryTests.m +90 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseQueryRegistryTests.m +390 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/project.pbxproj +261 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBDatabaseUnitTests.xcscheme +69 -0
- package/lib/version.ts +1 -1
- package/package.json +5 -4
package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistryTest.java
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
package io.invertase.firebase.database;
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this library except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import static org.junit.Assert.assertEquals;
|
|
21
|
+
import static org.junit.Assert.assertFalse;
|
|
22
|
+
import static org.junit.Assert.assertNull;
|
|
23
|
+
import static org.junit.Assert.assertSame;
|
|
24
|
+
import static org.junit.Assert.assertTrue;
|
|
25
|
+
import static org.junit.Assert.fail;
|
|
26
|
+
|
|
27
|
+
import io.invertase.firebase.common.RNFBHandleCollisionException;
|
|
28
|
+
import org.junit.Test;
|
|
29
|
+
|
|
30
|
+
public class RNFBDatabaseListenerRegistryTest {
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
public void putValueGetTake_happyPath() throws Exception {
|
|
34
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
35
|
+
Object listener = new Object();
|
|
36
|
+
registry.putValue("k", listener);
|
|
37
|
+
assertSame(listener, registry.getValue("k"));
|
|
38
|
+
assertSame(listener, registry.takeValue("k"));
|
|
39
|
+
assertNull(registry.getValue("k"));
|
|
40
|
+
assertFalse(registry.hasListeners());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test
|
|
44
|
+
public void putChildGetTake_happyPath() throws Exception {
|
|
45
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
46
|
+
Object listener = new Object();
|
|
47
|
+
registry.putChild("k", listener);
|
|
48
|
+
assertSame(listener, registry.getChild("k"));
|
|
49
|
+
assertSame(listener, registry.takeChild("k"));
|
|
50
|
+
assertNull(registry.getChild("k"));
|
|
51
|
+
assertFalse(registry.hasListeners());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Test
|
|
55
|
+
public void putValue_occupiedId_throwsCollision() throws Exception {
|
|
56
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
57
|
+
Object first = new Object();
|
|
58
|
+
registry.putValue("k", first);
|
|
59
|
+
try {
|
|
60
|
+
registry.putValue("k", new Object());
|
|
61
|
+
fail("expected RNFBHandleCollisionException");
|
|
62
|
+
} catch (RNFBHandleCollisionException e) {
|
|
63
|
+
assertTrue(e.getMessage().contains("k"));
|
|
64
|
+
assertSame(first, registry.getValue("k"));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Test
|
|
69
|
+
public void putChild_occupiedId_throwsCollision() throws Exception {
|
|
70
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
71
|
+
Object first = new Object();
|
|
72
|
+
registry.putChild("k", first);
|
|
73
|
+
try {
|
|
74
|
+
registry.putChild("k", new Object());
|
|
75
|
+
fail("expected RNFBHandleCollisionException");
|
|
76
|
+
} catch (RNFBHandleCollisionException e) {
|
|
77
|
+
assertTrue(e.getMessage().contains("k"));
|
|
78
|
+
assertSame(first, registry.getChild("k"));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Test
|
|
83
|
+
public void putValue_nullListener_throws() throws Exception {
|
|
84
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
85
|
+
try {
|
|
86
|
+
registry.putValue("k", null);
|
|
87
|
+
fail("expected NullPointerException");
|
|
88
|
+
} catch (NullPointerException e) {
|
|
89
|
+
assertEquals("listener", e.getMessage());
|
|
90
|
+
assertFalse(registry.hasListeners());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@Test
|
|
95
|
+
public void putChild_nullListener_throws() throws Exception {
|
|
96
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
97
|
+
try {
|
|
98
|
+
registry.putChild("k", null);
|
|
99
|
+
fail("expected NullPointerException");
|
|
100
|
+
} catch (NullPointerException e) {
|
|
101
|
+
assertEquals("listener", e.getMessage());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@Test
|
|
106
|
+
public void takeValue_missingKey_isNoOp() {
|
|
107
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
108
|
+
assertNull(registry.takeValue("missing"));
|
|
109
|
+
assertFalse(registry.hasListeners());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@Test
|
|
113
|
+
public void takeChild_missingKey_isNoOp() {
|
|
114
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
115
|
+
assertNull(registry.takeChild("missing"));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Test
|
|
119
|
+
public void takeAllValuesAndChildren_empty_isNoOp() {
|
|
120
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
121
|
+
assertTrue(registry.takeAllValues().isEmpty());
|
|
122
|
+
assertTrue(registry.takeAllChildren().isEmpty());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Test
|
|
126
|
+
public void hasEventListener_valueOrChild() throws Exception {
|
|
127
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
128
|
+
assertFalse(registry.hasEventListener("k"));
|
|
129
|
+
registry.putValue("k", new Object());
|
|
130
|
+
assertTrue(registry.hasEventListener("k"));
|
|
131
|
+
registry.takeValue("k");
|
|
132
|
+
registry.putChild("k", new Object());
|
|
133
|
+
assertTrue(registry.hasEventListener("k"));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Test
|
|
137
|
+
public void hasListeners_tracksOccupancyAcrossMaps() throws Exception {
|
|
138
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
139
|
+
registry.putValue("v", new Object());
|
|
140
|
+
registry.putChild("c", new Object());
|
|
141
|
+
assertTrue(registry.hasListeners());
|
|
142
|
+
registry.takeAllValues();
|
|
143
|
+
assertTrue(registry.hasListeners());
|
|
144
|
+
registry.takeAllChildren();
|
|
145
|
+
assertFalse(registry.hasListeners());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@Test
|
|
149
|
+
public void takeAll_combinesValueAndChildMaps() throws Exception {
|
|
150
|
+
RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
|
|
151
|
+
Object value = new Object();
|
|
152
|
+
Object child = new Object();
|
|
153
|
+
registry.putValue("v", value);
|
|
154
|
+
registry.putChild("c", child);
|
|
155
|
+
assertEquals(2, registry.takeAll().size());
|
|
156
|
+
assertFalse(registry.hasListeners());
|
|
157
|
+
assertNull(registry.getValue("v"));
|
|
158
|
+
assertNull(registry.getChild("c"));
|
|
159
|
+
}
|
|
160
|
+
}
|
package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistryTest.java
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
package io.invertase.firebase.database;
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this library except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import static org.junit.Assert.assertEquals;
|
|
21
|
+
import static org.junit.Assert.assertFalse;
|
|
22
|
+
import static org.junit.Assert.assertNull;
|
|
23
|
+
import static org.junit.Assert.assertSame;
|
|
24
|
+
import static org.junit.Assert.assertTrue;
|
|
25
|
+
import static org.junit.Assert.fail;
|
|
26
|
+
|
|
27
|
+
import io.invertase.firebase.common.RNFBHandleCollisionException;
|
|
28
|
+
import java.util.concurrent.CountDownLatch;
|
|
29
|
+
import java.util.concurrent.TimeUnit;
|
|
30
|
+
import java.util.concurrent.atomic.AtomicInteger;
|
|
31
|
+
import org.junit.Test;
|
|
32
|
+
|
|
33
|
+
public class RNFBDatabaseQueryRegistryTest {
|
|
34
|
+
|
|
35
|
+
private static final class FakeQuery implements DatabaseQueryHandle {
|
|
36
|
+
int removeCount;
|
|
37
|
+
volatile boolean listeners;
|
|
38
|
+
|
|
39
|
+
@Override
|
|
40
|
+
public void removeAllEventListeners() {
|
|
41
|
+
removeCount++;
|
|
42
|
+
listeners = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Override
|
|
46
|
+
public Boolean hasListeners() {
|
|
47
|
+
return listeners;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private static final class GatedFakeQuery implements DatabaseQueryHandle {
|
|
52
|
+
volatile boolean listeners;
|
|
53
|
+
final CountDownLatch hasListenersEntered = new CountDownLatch(1);
|
|
54
|
+
final CountDownLatch allowHasListenersReturn = new CountDownLatch(1);
|
|
55
|
+
|
|
56
|
+
@Override
|
|
57
|
+
public void removeAllEventListeners() {
|
|
58
|
+
listeners = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Override
|
|
62
|
+
public Boolean hasListeners() {
|
|
63
|
+
hasListenersEntered.countDown();
|
|
64
|
+
try {
|
|
65
|
+
assertTrue(allowHasListenersReturn.await(5, TimeUnit.SECONDS));
|
|
66
|
+
} catch (InterruptedException e) {
|
|
67
|
+
Thread.currentThread().interrupt();
|
|
68
|
+
throw new RuntimeException(e);
|
|
69
|
+
}
|
|
70
|
+
return listeners;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private static final class PutBackGatedQuery implements DatabaseQueryHandle {
|
|
75
|
+
volatile boolean listeners;
|
|
76
|
+
int removeCount;
|
|
77
|
+
final CountDownLatch firstEntered = new CountDownLatch(1);
|
|
78
|
+
final CountDownLatch allowFirstReturn = new CountDownLatch(1);
|
|
79
|
+
final CountDownLatch secondEntered = new CountDownLatch(1);
|
|
80
|
+
final CountDownLatch allowSecondReturn = new CountDownLatch(1);
|
|
81
|
+
final AtomicInteger calls = new AtomicInteger();
|
|
82
|
+
|
|
83
|
+
@Override
|
|
84
|
+
public void removeAllEventListeners() {
|
|
85
|
+
removeCount++;
|
|
86
|
+
listeners = false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Override
|
|
90
|
+
public Boolean hasListeners() {
|
|
91
|
+
int n = calls.incrementAndGet();
|
|
92
|
+
if (n == 1) {
|
|
93
|
+
firstEntered.countDown();
|
|
94
|
+
try {
|
|
95
|
+
assertTrue(allowFirstReturn.await(5, TimeUnit.SECONDS));
|
|
96
|
+
} catch (InterruptedException e) {
|
|
97
|
+
Thread.currentThread().interrupt();
|
|
98
|
+
throw new RuntimeException(e);
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
secondEntered.countDown();
|
|
103
|
+
try {
|
|
104
|
+
assertTrue(allowSecondReturn.await(5, TimeUnit.SECONDS));
|
|
105
|
+
} catch (InterruptedException e) {
|
|
106
|
+
Thread.currentThread().interrupt();
|
|
107
|
+
throw new RuntimeException(e);
|
|
108
|
+
}
|
|
109
|
+
return listeners;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@Test
|
|
114
|
+
public void takeIfIdle_concurrentOffOnRace_retainsMappingWhenListenersAddedDuringCheck()
|
|
115
|
+
throws Exception {
|
|
116
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
117
|
+
GatedFakeQuery query = new GatedFakeQuery();
|
|
118
|
+
query.listeners = false;
|
|
119
|
+
registry.put("q", query);
|
|
120
|
+
|
|
121
|
+
CountDownLatch offDone = new CountDownLatch(1);
|
|
122
|
+
Thread offThread =
|
|
123
|
+
new Thread(
|
|
124
|
+
() -> {
|
|
125
|
+
try {
|
|
126
|
+
registry.takeIfIdle("q");
|
|
127
|
+
} finally {
|
|
128
|
+
offDone.countDown();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
offThread.start();
|
|
133
|
+
assertTrue(query.hasListenersEntered.await(5, TimeUnit.SECONDS));
|
|
134
|
+
query.listeners = true;
|
|
135
|
+
query.allowHasListenersReturn.countDown();
|
|
136
|
+
assertTrue(offDone.await(5, TimeUnit.SECONDS));
|
|
137
|
+
|
|
138
|
+
assertSame(query, registry.get("q"));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Proves hasListeners runs outside the HandleMap lock: while hasListeners is blocked, a
|
|
143
|
+
* concurrent put on another key must complete (would deadlock if takeIfIdle held the map lock).
|
|
144
|
+
*/
|
|
145
|
+
@Test
|
|
146
|
+
public void takeIfIdle_doesNotHoldMapLockDuringHasListeners() throws Exception {
|
|
147
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
148
|
+
GatedFakeQuery query = new GatedFakeQuery();
|
|
149
|
+
query.listeners = false;
|
|
150
|
+
registry.put("q", query);
|
|
151
|
+
|
|
152
|
+
CountDownLatch putDone = new CountDownLatch(1);
|
|
153
|
+
Thread takeThread = new Thread(() -> registry.takeIfIdle("q"));
|
|
154
|
+
takeThread.start();
|
|
155
|
+
assertTrue(query.hasListenersEntered.await(5, TimeUnit.SECONDS));
|
|
156
|
+
|
|
157
|
+
Thread putThread =
|
|
158
|
+
new Thread(
|
|
159
|
+
() -> {
|
|
160
|
+
try {
|
|
161
|
+
registry.put("other", new FakeQuery());
|
|
162
|
+
putDone.countDown();
|
|
163
|
+
} catch (RNFBHandleCollisionException e) {
|
|
164
|
+
throw new RuntimeException(e);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
putThread.start();
|
|
168
|
+
assertTrue(
|
|
169
|
+
"HandleMap lock must not be held while hasListeners runs",
|
|
170
|
+
putDone.await(2, TimeUnit.SECONDS));
|
|
171
|
+
|
|
172
|
+
// Keep the mapping after unlock; this test only asserts lock nesting.
|
|
173
|
+
query.listeners = true;
|
|
174
|
+
query.allowHasListenersReturn.countDown();
|
|
175
|
+
takeThread.join(5000);
|
|
176
|
+
putThread.join(5000);
|
|
177
|
+
assertSame(query, registry.get("q"));
|
|
178
|
+
assertTrue(registry.get("other") instanceof FakeQuery);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Outside check saw idle; after identity-take, listeners appeared — put-back retains mapping. */
|
|
182
|
+
@Test
|
|
183
|
+
public void takeIfIdle_putBackWhenListenersAppearAfterOutsideIdleCheck() throws Exception {
|
|
184
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
185
|
+
PutBackGatedQuery query = new PutBackGatedQuery();
|
|
186
|
+
query.listeners = false;
|
|
187
|
+
registry.put("q", query);
|
|
188
|
+
|
|
189
|
+
CountDownLatch takeDone = new CountDownLatch(1);
|
|
190
|
+
Thread takeThread =
|
|
191
|
+
new Thread(
|
|
192
|
+
() -> {
|
|
193
|
+
try {
|
|
194
|
+
registry.takeIfIdle("q");
|
|
195
|
+
} finally {
|
|
196
|
+
takeDone.countDown();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
takeThread.start();
|
|
200
|
+
|
|
201
|
+
assertTrue(query.firstEntered.await(5, TimeUnit.SECONDS));
|
|
202
|
+
query.allowFirstReturn.countDown();
|
|
203
|
+
assertTrue(query.secondEntered.await(5, TimeUnit.SECONDS));
|
|
204
|
+
query.listeners = true;
|
|
205
|
+
query.allowSecondReturn.countDown();
|
|
206
|
+
assertTrue(takeDone.await(5, TimeUnit.SECONDS));
|
|
207
|
+
|
|
208
|
+
assertSame(query, registry.get("q"));
|
|
209
|
+
assertEquals(2, query.calls.get());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* After identity-take, listeners appear but a concurrent put claimed the slot — put-back fails;
|
|
214
|
+
* orphan must clear listeners so SDK callbacks are not left outside the registry.
|
|
215
|
+
*/
|
|
216
|
+
@Test
|
|
217
|
+
public void takeIfIdle_putBackFails_clearsOrphanListeners() throws Exception {
|
|
218
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
219
|
+
PutBackGatedQuery orphan = new PutBackGatedQuery();
|
|
220
|
+
orphan.listeners = false;
|
|
221
|
+
registry.put("q", orphan);
|
|
222
|
+
|
|
223
|
+
FakeQuery replacement = new FakeQuery();
|
|
224
|
+
replacement.listeners = true;
|
|
225
|
+
|
|
226
|
+
CountDownLatch takeDone = new CountDownLatch(1);
|
|
227
|
+
Thread takeThread =
|
|
228
|
+
new Thread(
|
|
229
|
+
() -> {
|
|
230
|
+
try {
|
|
231
|
+
registry.takeIfIdle("q");
|
|
232
|
+
} finally {
|
|
233
|
+
takeDone.countDown();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
takeThread.start();
|
|
237
|
+
|
|
238
|
+
assertTrue(orphan.firstEntered.await(5, TimeUnit.SECONDS));
|
|
239
|
+
orphan.allowFirstReturn.countDown();
|
|
240
|
+
assertTrue(orphan.secondEntered.await(5, TimeUnit.SECONDS));
|
|
241
|
+
registry.put("q", replacement);
|
|
242
|
+
orphan.listeners = true;
|
|
243
|
+
orphan.allowSecondReturn.countDown();
|
|
244
|
+
assertTrue(takeDone.await(5, TimeUnit.SECONDS));
|
|
245
|
+
|
|
246
|
+
assertSame(replacement, registry.get("q"));
|
|
247
|
+
assertEquals(1, orphan.removeCount);
|
|
248
|
+
assertFalse(orphan.listeners);
|
|
249
|
+
assertEquals(0, replacement.removeCount);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@Test
|
|
253
|
+
public void takeIfIdle_concurrentOffOnRace_stressRetainsWhenListenersAlreadyActive()
|
|
254
|
+
throws Exception {
|
|
255
|
+
for (int attempt = 0; attempt < 200; attempt++) {
|
|
256
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
257
|
+
FakeQuery query = new FakeQuery();
|
|
258
|
+
query.listeners = true;
|
|
259
|
+
registry.put("q", query);
|
|
260
|
+
|
|
261
|
+
CountDownLatch start = new CountDownLatch(1);
|
|
262
|
+
CountDownLatch done = new CountDownLatch(2);
|
|
263
|
+
|
|
264
|
+
Thread offThread =
|
|
265
|
+
new Thread(
|
|
266
|
+
() -> {
|
|
267
|
+
try {
|
|
268
|
+
start.await();
|
|
269
|
+
registry.takeIfIdle("q");
|
|
270
|
+
} catch (InterruptedException e) {
|
|
271
|
+
Thread.currentThread().interrupt();
|
|
272
|
+
} finally {
|
|
273
|
+
done.countDown();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
Thread onThread =
|
|
277
|
+
new Thread(
|
|
278
|
+
() -> {
|
|
279
|
+
try {
|
|
280
|
+
start.await();
|
|
281
|
+
query.listeners = true;
|
|
282
|
+
} catch (InterruptedException e) {
|
|
283
|
+
Thread.currentThread().interrupt();
|
|
284
|
+
} finally {
|
|
285
|
+
done.countDown();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
offThread.start();
|
|
290
|
+
onThread.start();
|
|
291
|
+
start.countDown();
|
|
292
|
+
assertTrue(done.await(5, TimeUnit.SECONDS));
|
|
293
|
+
assertSame(query, registry.get("q"));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@Test
|
|
298
|
+
public void takeIfIdle_concurrentTakeIfIdle_onIdleQuery_leavesMapEmpty() throws Exception {
|
|
299
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
300
|
+
FakeQuery query = new FakeQuery();
|
|
301
|
+
query.listeners = false;
|
|
302
|
+
registry.put("q", query);
|
|
303
|
+
|
|
304
|
+
CountDownLatch start = new CountDownLatch(1);
|
|
305
|
+
CountDownLatch done = new CountDownLatch(2);
|
|
306
|
+
|
|
307
|
+
Thread t1 =
|
|
308
|
+
new Thread(
|
|
309
|
+
() -> {
|
|
310
|
+
try {
|
|
311
|
+
start.await();
|
|
312
|
+
registry.takeIfIdle("q");
|
|
313
|
+
} catch (InterruptedException e) {
|
|
314
|
+
Thread.currentThread().interrupt();
|
|
315
|
+
} finally {
|
|
316
|
+
done.countDown();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
Thread t2 =
|
|
320
|
+
new Thread(
|
|
321
|
+
() -> {
|
|
322
|
+
try {
|
|
323
|
+
start.await();
|
|
324
|
+
registry.takeIfIdle("q");
|
|
325
|
+
} catch (InterruptedException e) {
|
|
326
|
+
Thread.currentThread().interrupt();
|
|
327
|
+
} finally {
|
|
328
|
+
done.countDown();
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
t1.start();
|
|
333
|
+
t2.start();
|
|
334
|
+
start.countDown();
|
|
335
|
+
assertTrue(done.await(5, TimeUnit.SECONDS));
|
|
336
|
+
|
|
337
|
+
assertNull(registry.get("q"));
|
|
338
|
+
assertEquals(0, query.removeCount);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@Test
|
|
342
|
+
public void putGetTake_happyPath() throws Exception {
|
|
343
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
344
|
+
FakeQuery query = new FakeQuery();
|
|
345
|
+
registry.put("q1", query);
|
|
346
|
+
assertSame(query, registry.get("q1"));
|
|
347
|
+
assertSame(query, registry.take("q1"));
|
|
348
|
+
assertNull(registry.get("q1"));
|
|
349
|
+
assertEquals(0, query.removeCount);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@Test
|
|
353
|
+
public void put_occupiedId_throwsCollision() throws Exception {
|
|
354
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
355
|
+
FakeQuery first = new FakeQuery();
|
|
356
|
+
registry.put("q1", first);
|
|
357
|
+
try {
|
|
358
|
+
registry.put("q1", new FakeQuery());
|
|
359
|
+
fail("expected RNFBHandleCollisionException");
|
|
360
|
+
} catch (RNFBHandleCollisionException e) {
|
|
361
|
+
assertTrue(e.getMessage().contains("q1"));
|
|
362
|
+
assertSame(first, registry.get("q1"));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@Test
|
|
367
|
+
public void takeAllAndRemove_removesSnapshotAndLeavesEmpty() throws Exception {
|
|
368
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
369
|
+
FakeQuery a = new FakeQuery();
|
|
370
|
+
FakeQuery b = new FakeQuery();
|
|
371
|
+
registry.put("a", a);
|
|
372
|
+
registry.put("b", b);
|
|
373
|
+
registry.takeAllAndRemove();
|
|
374
|
+
assertEquals(1, a.removeCount);
|
|
375
|
+
assertEquals(1, b.removeCount);
|
|
376
|
+
assertNull(registry.get("a"));
|
|
377
|
+
assertNull(registry.get("b"));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
@Test
|
|
381
|
+
public void takeAllAndRemove_empty_isNoOp() {
|
|
382
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
383
|
+
registry.takeAllAndRemove();
|
|
384
|
+
assertNull(registry.get("a"));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@Test
|
|
388
|
+
public void takeAllAndRemove_nullQuery_isNoOp() throws Exception {
|
|
389
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
390
|
+
registry.put("n", null);
|
|
391
|
+
registry.takeAllAndRemove();
|
|
392
|
+
assertNull(registry.get("n"));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@Test
|
|
396
|
+
public void put_afterTake_allowsReuse() throws Exception {
|
|
397
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
398
|
+
FakeQuery first = new FakeQuery();
|
|
399
|
+
FakeQuery second = new FakeQuery();
|
|
400
|
+
registry.put("q1", first);
|
|
401
|
+
assertSame(first, registry.take("q1"));
|
|
402
|
+
registry.put("q1", second);
|
|
403
|
+
assertSame(second, registry.get("q1"));
|
|
404
|
+
assertFalse(first.removeCount != 0);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@Test
|
|
408
|
+
public void takeIfIdle_whenNoListeners_takes() throws Exception {
|
|
409
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
410
|
+
FakeQuery query = new FakeQuery();
|
|
411
|
+
query.listeners = false;
|
|
412
|
+
registry.put("q", query);
|
|
413
|
+
registry.takeIfIdle("q");
|
|
414
|
+
assertNull(registry.get("q"));
|
|
415
|
+
assertEquals(0, query.removeCount);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
@Test
|
|
419
|
+
public void takeIfIdle_whenHasListeners_leavesMapping() throws Exception {
|
|
420
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
421
|
+
FakeQuery query = new FakeQuery();
|
|
422
|
+
query.listeners = true;
|
|
423
|
+
registry.put("q", query);
|
|
424
|
+
registry.takeIfIdle("q");
|
|
425
|
+
assertSame(query, registry.get("q"));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
@Test
|
|
429
|
+
public void takeIfIdle_whenMissing_isNoOp() {
|
|
430
|
+
RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
|
|
431
|
+
registry.takeIfIdle("missing");
|
|
432
|
+
assertNull(registry.get("missing"));
|
|
433
|
+
}
|
|
434
|
+
}
|