Skip to content

Commit ccc11db

Browse files
Merge pull request #321 from ably/feature/ably-instance-store-concurrency
`AblyInstanceStore` concurrency
2 parents 46c6947 + 1e1c6b9 commit ccc11db

File tree

2 files changed

+94
-32
lines changed

2 files changed

+94
-32
lines changed

android/src/main/java/io/ably/flutter/plugin/AblyInstanceStore.java

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import android.content.Context;
44
import android.util.LongSparseArray;
55

6+
import java.util.concurrent.atomic.AtomicLong;
7+
68
import io.ably.lib.push.Push;
79
import io.ably.lib.push.PushChannel;
810
import io.ably.lib.realtime.AblyRealtime;
@@ -23,9 +25,7 @@
2325
* client by calling @{AblyInstanceStore#getRealtime(final long handle)}.
2426
*/
2527
class AblyInstanceStore {
26-
2728
private static final AblyInstanceStore instance = new AblyInstanceStore();
28-
private long nextHandle = 1;
2929

3030
// Android Studio warns against using HashMap with integer keys, and
3131
// suggests using LongSparseArray. More information at https://stackoverflow.com/a/31413003
@@ -34,38 +34,100 @@ class AblyInstanceStore {
3434
private final LongSparseArray<AblyRest> restInstances = new LongSparseArray<>();
3535
private final LongSparseArray<AblyRealtime> realtimeInstances = new LongSparseArray<>();
3636
private final LongSparseArray<AsyncPaginatedResult<Object>> paginatedResults = new LongSparseArray<>();
37+
private final AtomicLong nextHandle = new AtomicLong(1);
3738

3839
static synchronized AblyInstanceStore getInstance() {
3940
return instance;
4041
}
4142

4243
/**
43-
* Returns a handle representing the next client that will be created. This handle can be used
44-
* to get the client **after** it is instantiated using [createRest] or [createRealtime].
44+
* A reserved client handle. Safe to be used from any thread.
45+
*
46+
* Instances support the creation of a single Rest or Realtime instance, where only one of the
47+
* create methods may be called and it may only be called once.
4548
*/
46-
long getHandleForNextClient() {
47-
return nextHandle;
49+
interface ClientHandle {
50+
/**
51+
* Get the handle that will be used to store this client when it is created, or the handle
52+
* that was used to store it when it was created.
53+
* This property may be read at any time, from any thread.
54+
*/
55+
long getHandle();
56+
57+
/**
58+
* Create an {@link AblyRest} instance and store it using this handle.
59+
* @param clientOptions The Ably client options for the new Rest instance.
60+
* @param applicationContext The Android application context to supply to the new Rest
61+
* instance using its {@link AblyRest#setAndroidContext(Context)} method.
62+
* @return The handle used to store the instance. Same as {@link #getHandle()}.
63+
* @throws IllegalStateException If this handle has already been used to create a Rest or
64+
* Realtime instance.
65+
* @throws AblyException If the {@link AblyRest} instance creation failed.
66+
*/
67+
long createRest(ClientOptions clientOptions, Context applicationContext) throws AblyException;
68+
69+
/**
70+
* Create an {@link AblyRealtime} instance and store it using this handle.
71+
* @param clientOptions The Ably client options for the new Realtime instance.
72+
* @param applicationContext The Android application context to supply to the new Realtime
73+
* instance using its {@link AblyRealtime#setAndroidContext(Context)} method.
74+
* @return The handle used to store the instance. Same as {@link #getHandle()}.
75+
* @throws IllegalStateException If this handle has already been used to create a Rest or
76+
* Realtime instance.
77+
* @throws AblyException If the {@link AblyRealtime} instance creation failed.
78+
*/
79+
long createRealtime(ClientOptions clientOptions, Context applicationContext) throws AblyException;
4880
}
4981

50-
long createRest(final ClientOptions clientOptions, Context applicationContext) throws AblyException {
51-
final AblyRest rest = new AblyRest(clientOptions);
52-
rest.setAndroidContext(applicationContext);
53-
restInstances.put(nextHandle, rest);
54-
return nextHandle++;
82+
private class ReservedClientHandle implements ClientHandle {
83+
private final long handle;
84+
private volatile boolean used = false;
85+
86+
ReservedClientHandle(final long handle) {
87+
this.handle = handle;
88+
}
89+
90+
@Override
91+
public long getHandle() {
92+
return handle;
93+
}
94+
95+
@Override
96+
public synchronized long createRest(final ClientOptions clientOptions, final Context applicationContext) throws AblyException {
97+
final long handle = use();
98+
final AblyRest rest = new AblyRest(clientOptions);
99+
rest.setAndroidContext(applicationContext);
100+
restInstances.put(handle, rest);
101+
return handle;
102+
}
103+
104+
@Override
105+
public synchronized long createRealtime(final ClientOptions clientOptions, final Context applicationContext) throws AblyException {
106+
final long handle = use();
107+
final AblyRealtime realtime = new AblyRealtime(clientOptions);
108+
realtime.setAndroidContext(applicationContext);
109+
realtimeInstances.put(handle, realtime);
110+
return handle;
111+
}
112+
113+
synchronized long use() {
114+
if (used) {
115+
throw new IllegalStateException("Reserved handle has already been used to create a client instance (handle=" + handle + ").");
116+
}
117+
used = true;
118+
return handle;
119+
}
55120
}
56121

57-
AblyRest getRest(final long handle) {
58-
return restInstances.get(handle);
122+
synchronized ClientHandle reserveClientHandle() {
123+
return new ReservedClientHandle(nextHandle.getAndIncrement());
59124
}
60125

61-
long createRealtime(final ClientOptions clientOptions, Context applicationContext) throws AblyException {
62-
final AblyRealtime realtime = new AblyRealtime(clientOptions);
63-
realtime.setAndroidContext(applicationContext);
64-
realtimeInstances.put(nextHandle, realtime);
65-
return nextHandle++;
126+
synchronized AblyRest getRest(final long handle) {
127+
return restInstances.get(handle);
66128
}
67129

68-
AblyRealtime getRealtime(final long handle) {
130+
synchronized AblyRealtime getRealtime(final long handle) {
69131
return realtimeInstances.get(handle);
70132
}
71133

@@ -79,38 +141,38 @@ AblyRealtime getRealtime(final long handle) {
79141
* @param handle integer handle to either AblyRealtime or AblyRest
80142
* @return AblyBase
81143
*/
82-
AblyBase getAblyClient(final long handle) {
144+
synchronized AblyBase getAblyClient(final long handle) {
83145
AblyRealtime realtime = getRealtime(handle);
84146
return (realtime != null) ? realtime : getRest(handle);
85147
}
86148

87-
Push getPush(final long handle) {
149+
synchronized Push getPush(final long handle) {
88150
AblyRealtime realtime = getRealtime(handle);
89151
return (realtime != null) ? realtime.push : getRest(handle).push;
90152
}
91153

92-
PushChannel getPushChannel(final long handle, final String channelName) {
154+
synchronized PushChannel getPushChannel(final long handle, final String channelName) {
93155
return getAblyClient(handle)
94156
.channels
95157
.get(channelName).push;
96158
}
97159

98-
long setPaginatedResult(AsyncPaginatedResult result, Integer handle) {
160+
synchronized long setPaginatedResult(AsyncPaginatedResult result, Integer handle) {
99161
long longHandle;
100162
if (handle == null) {
101-
longHandle = nextHandle++;
163+
longHandle = nextHandle.getAndIncrement();
102164
} else {
103165
longHandle = handle.longValue();
104166
}
105167
paginatedResults.put(longHandle, result);
106168
return longHandle;
107169
}
108170

109-
AsyncPaginatedResult<Object> getPaginatedResult(long handle) {
171+
synchronized AsyncPaginatedResult<Object> getPaginatedResult(long handle) {
110172
return paginatedResults.get(handle);
111173
}
112174

113-
void reset() {
175+
synchronized void reset() {
114176
for (int i = 0; i < realtimeInstances.size(); i++) {
115177
long key = realtimeInstances.keyAt(i);
116178
AblyRealtime r = realtimeInstances.get(key);

android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,13 @@ private void createRest(@NonNull MethodCall call, @NonNull MethodChannel.Result
183183
final AblyFlutterMessage<PlatformClientOptions> message = (AblyFlutterMessage<PlatformClientOptions>) call.arguments;
184184
this.<PlatformClientOptions>ablyDo(message, (ablyLibrary, clientOptions) -> {
185185
try {
186-
final long handle = ablyLibrary.getHandleForNextClient();
186+
final AblyInstanceStore.ClientHandle clientHandle = ablyLibrary.reserveClientHandle();
187187
if (clientOptions.hasAuthCallback) {
188188
clientOptions.options.authCallback = (Auth.TokenParams params) -> {
189189
final Object[] token = {null};
190190
final CountDownLatch latch = new CountDownLatch(1);
191191
new Handler(Looper.getMainLooper()).post(() -> {
192-
AblyFlutterMessage<Auth.TokenParams> channelMessage = new AblyFlutterMessage<>(params, handle);
192+
AblyFlutterMessage<Auth.TokenParams> channelMessage = new AblyFlutterMessage<>(params, clientHandle.getHandle());
193193
methodChannel.invokeMethod(PlatformConstants.PlatformMethod.authCallback, channelMessage, new MethodChannel.Result() {
194194
@Override
195195
public void success(@Nullable Object result) {
@@ -220,7 +220,7 @@ public void notImplemented() {
220220
return token[0];
221221
};
222222
}
223-
result.success(ablyLibrary.createRest(clientOptions.options, applicationContext));
223+
result.success(clientHandle.createRest(clientOptions.options, applicationContext));
224224
} catch (final AblyException e) {
225225
handleAblyException(result, e);
226226
}
@@ -448,13 +448,13 @@ private void createRealtime(@NonNull MethodCall call, @NonNull MethodChannel.Res
448448
final AblyFlutterMessage<PlatformClientOptions> message = (AblyFlutterMessage<PlatformClientOptions>) call.arguments;
449449
this.<PlatformClientOptions>ablyDo(message, (ablyLibrary, clientOptions) -> {
450450
try {
451-
final long handle = ablyLibrary.getHandleForNextClient();
451+
final AblyInstanceStore.ClientHandle clientHandle = ablyLibrary.reserveClientHandle();
452452
if (clientOptions.hasAuthCallback) {
453453
clientOptions.options.authCallback = (Auth.TokenParams params) -> {
454454
final Object[] token = {null};
455455
final CountDownLatch latch = new CountDownLatch(1);
456456
new Handler(Looper.getMainLooper()).post(() -> {
457-
AblyFlutterMessage<Auth.TokenParams> channelMessage = new AblyFlutterMessage<>(params, handle);
457+
AblyFlutterMessage<Auth.TokenParams> channelMessage = new AblyFlutterMessage<>(params, clientHandle.getHandle());
458458
methodChannel.invokeMethod(PlatformConstants.PlatformMethod.realtimeAuthCallback, channelMessage, new MethodChannel.Result() {
459459
@Override
460460
public void success(@Nullable Object result) {
@@ -487,7 +487,7 @@ public void notImplemented() {
487487
return token[0];
488488
};
489489
}
490-
result.success(ablyLibrary.createRealtime(clientOptions.options, applicationContext));
490+
result.success(clientHandle.createRealtime(clientOptions.options, applicationContext));
491491
} catch (final AblyException e) {
492492
handleAblyException(result, e);
493493
}

0 commit comments

Comments
 (0)