Skip to content

Commit fbdffa7

Browse files
committed
Fix: Multiple "current" ParseInstallation instances being created
This fixes a race condition where multiple `ParseInstallation` instances can be returned as "current".
1 parent 500a2cb commit fbdffa7

File tree

2 files changed

+55
-23
lines changed

2 files changed

+55
-23
lines changed

Parse/src/main/java/com/parse/CachedCurrentInstallationController.java

+26-23
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,10 @@ public Task<Void> then(Task<Void> task) throws Exception {
7070

7171
@Override
7272
public Task<ParseInstallation> getAsync() {
73-
final ParseInstallation cachedCurrent;
7473
synchronized (mutex) {
75-
cachedCurrent = currentInstallation;
76-
}
77-
78-
if (cachedCurrent != null) {
79-
return Task.forResult(cachedCurrent);
74+
if (currentInstallation != null) {
75+
return Task.forResult(currentInstallation);
76+
}
8077
}
8178

8279
return taskQueue.enqueue(new Continuation<Void, Task<ParseInstallation>>() {
@@ -85,26 +82,32 @@ public Task<ParseInstallation> then(Task<Void> toAwait) throws Exception {
8582
return toAwait.continueWithTask(new Continuation<Void, Task<ParseInstallation>>() {
8683
@Override
8784
public Task<ParseInstallation> then(Task<Void> task) throws Exception {
88-
return store.getAsync();
89-
}
90-
}).continueWith(new Continuation<ParseInstallation, ParseInstallation>() {
91-
@Override
92-
public ParseInstallation then(Task<ParseInstallation> task) throws Exception {
93-
ParseInstallation current = task.getResult();
94-
if (current == null) {
95-
current = ParseObject.create(ParseInstallation.class);
96-
current.updateDeviceInfo(installationId);
97-
} else {
98-
installationId.set(current.getInstallationId());
99-
PLog.v(TAG, "Successfully deserialized Installation object");
100-
}
101-
10285
synchronized (mutex) {
103-
currentInstallation = current;
86+
if (currentInstallation != null) {
87+
return Task.forResult(currentInstallation);
88+
}
10489
}
105-
return current;
90+
91+
return store.getAsync().continueWith(new Continuation<ParseInstallation, ParseInstallation>() {
92+
@Override
93+
public ParseInstallation then(Task<ParseInstallation> task) throws Exception {
94+
ParseInstallation current = task.getResult();
95+
if (current == null) {
96+
current = ParseObject.create(ParseInstallation.class);
97+
current.updateDeviceInfo(installationId);
98+
} else {
99+
installationId.set(current.getInstallationId());
100+
PLog.v(TAG, "Successfully deserialized Installation object");
101+
}
102+
103+
synchronized (mutex) {
104+
currentInstallation = current;
105+
}
106+
return current;
107+
}
108+
}, ParseExecutors.io());
106109
}
107-
}, ParseExecutors.io());
110+
});
108111
}
109112
});
110113
}

Parse/src/test/java/com/parse/CachedCurrentInstallationControllerTest.java

+29
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,35 @@ public void testGetAsyncWithNoInstallation() throws Exception {
144144
assertEquals("android", currentInstallation.get(KEY_DEVICE_TYPE));
145145
}
146146

147+
@Test
148+
public void testGetAsyncWithNoInstallationRaceCondition() throws ParseException {
149+
// Mock installationId
150+
InstallationId installationId = mock(InstallationId.class);
151+
when(installationId.get()).thenReturn("testInstallationId");
152+
//noinspection unchecked
153+
ParseObjectStore<ParseInstallation> store = mock(ParseObjectStore.class);
154+
Task<ParseInstallation>.TaskCompletionSource tcs = Task.create();
155+
when(store.getAsync()).thenReturn(tcs.getTask());
156+
157+
// Create test controller
158+
CachedCurrentInstallationController controller =
159+
new CachedCurrentInstallationController(store, installationId);
160+
161+
Task<ParseInstallation> taskA = controller.getAsync();
162+
Task<ParseInstallation> taskB = controller.getAsync();
163+
164+
tcs.setResult(null);
165+
ParseInstallation installationA = ParseTaskUtils.wait(taskA);
166+
ParseInstallation installationB = ParseTaskUtils.wait(taskB);
167+
168+
verify(store, times(1)).getAsync();
169+
assertSame(controller.currentInstallation, installationA);
170+
assertSame(controller.currentInstallation, installationB);
171+
// Make sure device info is updated
172+
assertEquals("testInstallationId", installationA.getInstallationId());
173+
assertEquals("android", installationA.get(KEY_DEVICE_TYPE));
174+
}
175+
147176
//endregion
148177

149178
//region testExistsAsync

0 commit comments

Comments
 (0)