Skip to content

Commit b376ee1

Browse files
committed
Unit test for race condition in subscribe/connected
1 parent 26043f9 commit b376ee1

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

ParseLiveQuery/src/main/java/com/parse/ParseLiveQueryClientImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public <T extends ParseObject> SubscriptionHandling<T> subscribe(ParseQuery<T> q
8888
Subscription<T> subscription = new Subscription<>(requestId, query);
8989
subscriptions.append(requestId, subscription);
9090

91+
// TODO: differentiate between state=CONNECTED, vs received op=connected response
9192
if (inAnyState(WebSocketClient.State.CONNECTED)) {
9293
sendSubscription(subscription);
9394
} else if (userInitiatedDisconnect) {

ParseLiveQuery/src/test/java/com/parse/TestParseLiveQueryClient.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.parse;
22

3+
import org.assertj.core.api.Assertions;
34
import org.json.JSONException;
45
import org.json.JSONObject;
56
import org.junit.After;
@@ -82,6 +83,42 @@ public void tearDown() throws Exception {
8283
ParsePlugins.reset();
8384
}
8485

86+
@Test
87+
public void testSubscribeBetweenSocketConnectAndConnectResponse() throws Exception {
88+
ParseQuery<ParseObject> queryA = ParseQuery.getQuery("objA");
89+
ParseQuery<ParseObject> queryB = ParseQuery.getQuery("objB");
90+
clearConnection();
91+
92+
// This will trigger connectIfNeeded(), which calls reconnect()
93+
SubscriptionHandling<ParseObject> subA = parseLiveQueryClient.subscribe(queryA);
94+
95+
verify(webSocketClient, times(1)).open();
96+
verify(webSocketClient, never()).send(anyString());
97+
98+
// Now the socket is open
99+
webSocketClientCallback.onOpen();
100+
when(webSocketClient.getState()).thenReturn(WebSocketClient.State.CONNECTED);
101+
// and we send op=connect
102+
verify(webSocketClient, times(1)).send(contains("\"op\":\"connect\""));
103+
104+
// Now if we subscribe to queryB, we SHOULD NOT send the subscribe yet, until we get op=connected
105+
SubscriptionHandling<ParseObject> subB = parseLiveQueryClient.subscribe(queryB); // TODO: fix this state
106+
verify(webSocketClient, never()).send(contains("\"op\":\"subscribe\""));
107+
108+
// on op=connected, _then_ we should send both subscriptions
109+
webSocketClientCallback.onMessage(createConnectedMessage().toString());
110+
verify(webSocketClient, times(2)).send(contains("\"op\":\"subscribe\""));
111+
112+
// 1. Subscribe to queryA
113+
// - it is not connected yet, so it will trigger reconnect.
114+
// 2. Socket opens & connects; initiate op=connect
115+
// 3. subscribe to queryB
116+
// - SOCKET is connected, but we haven't received op=connected yet.
117+
// - BUG: it will call sendSubscription now
118+
// 4. Server responds to #2 with op=connected
119+
// 5. On op=connected, we replay pending subscriptions, including the one that was already sent in #3
120+
}
121+
85122
@Test
86123
public void testSubscribeWhenSubscribedToCallback() throws Exception {
87124
SubscriptionHandling.HandleSubscribeCallback<ParseObject> subscribeMockCallback = mock(SubscriptionHandling.HandleSubscribeCallback.class);
@@ -459,6 +496,11 @@ private void validateSameObject(SubscriptionHandling.HandleEventCallback<ParseOb
459496
assertEquals(originalParseObject.getObjectId(), newParseObject.getObjectId());
460497
}
461498

499+
private void clearConnection() {
500+
webSocketClient = null;
501+
webSocketClientCallback = null;
502+
}
503+
462504
private void reconnect() {
463505
parseLiveQueryClient.reconnect();
464506
webSocketClientCallback.onOpen();

0 commit comments

Comments
 (0)