Skip to content

Commit 979c6b5

Browse files
authored
Release 5.1.1 (#736)
2 parents 98888b1 + f0fda3d commit 979c6b5

File tree

13 files changed

+340
-32
lines changed

13 files changed

+340
-32
lines changed

.github/workflows/instrumented.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ on:
44
pull_request:
55
branches:
66
- 'development'
7+
- 'master'
8+
- '*_baseline'
79

810
jobs:
911
test:
10-
runs-on: ubuntu-latest
12+
runs-on: ubuntu-20.04
1113

1214
strategy:
1315
fail-fast: false

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
5.1.1 (Feb 11, 2025)
2+
- Fixed issue when calling destroy() before the SDK has initialized.
3+
14
5.1.0 (Jan 17, 2025)
25
- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs.
36

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ apply plugin: 'kotlin-android'
1717
apply from: 'spec.gradle'
1818

1919
ext {
20-
splitVersion = '5.1.0'
20+
splitVersion = '5.1.1'
2121
}
2222

2323
android {
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package tests.integration.init;
2+
3+
import static org.junit.Assert.assertFalse;
4+
import static org.junit.Assert.assertTrue;
5+
import static org.junit.Assert.fail;
6+
import static helper.IntegrationHelper.buildFactory;
7+
import static helper.IntegrationHelper.emptyAllSegments;
8+
import static helper.IntegrationHelper.getSinceFromUri;
9+
10+
import android.content.Context;
11+
12+
import androidx.test.platform.app.InstrumentationRegistry;
13+
14+
import org.junit.Before;
15+
import org.junit.Test;
16+
17+
import java.util.concurrent.CountDownLatch;
18+
import java.util.concurrent.TimeUnit;
19+
import java.util.concurrent.atomic.AtomicBoolean;
20+
21+
import helper.DatabaseHelper;
22+
import helper.FileHelper;
23+
import helper.IntegrationHelper;
24+
import io.split.android.client.ServiceEndpoints;
25+
import io.split.android.client.SplitClient;
26+
import io.split.android.client.SplitClientConfig;
27+
import io.split.android.client.SplitFactory;
28+
import io.split.android.client.api.Key;
29+
import io.split.android.client.dtos.SplitChange;
30+
import io.split.android.client.events.SplitEvent;
31+
import io.split.android.client.utils.Json;
32+
import io.split.android.client.utils.logger.Logger;
33+
import io.split.android.client.utils.logger.SplitLogLevel;
34+
import okhttp3.mockwebserver.Dispatcher;
35+
import okhttp3.mockwebserver.MockResponse;
36+
import okhttp3.mockwebserver.MockWebServer;
37+
import okhttp3.mockwebserver.RecordedRequest;
38+
import tests.integration.shared.TestingHelper;
39+
40+
public class InitializationTest {
41+
42+
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
43+
private CountDownLatch mRequestCountdownLatch;
44+
private MockWebServer mWebServer;
45+
46+
private AtomicBoolean mEventSent;
47+
48+
@Before
49+
public void setUp() {
50+
setupServer();
51+
mRequestCountdownLatch = new CountDownLatch(1);
52+
mEventSent = new AtomicBoolean(false);
53+
}
54+
55+
@Test
56+
public void immediateClientRecreation() throws InterruptedException {
57+
SplitFactory factory = getFactory(false);
58+
SplitClient client = factory.client();
59+
client.track("some_event");
60+
61+
CountDownLatch latch = new CountDownLatch(1);
62+
client.on(SplitEvent.SDK_READY, new TestingHelper.TestEventTask(latch));
63+
64+
// Wait for the client to be ready
65+
boolean readyAwait = latch.await(5, TimeUnit.SECONDS);
66+
67+
// Destroy it
68+
client.destroy();
69+
70+
// Create a new client; it should be ready since it was created immediately
71+
CountDownLatch secondReadyLatch = new CountDownLatch(1);
72+
factory.client(new Key("new_key")).on(SplitEvent.SDK_READY, new TestingHelper.TestEventTask(secondReadyLatch));
73+
boolean awaitReady2 = secondReadyLatch.await(5, TimeUnit.SECONDS);
74+
75+
// Wait for events to be posted
76+
Thread.sleep(500);
77+
78+
assertTrue(readyAwait);
79+
assertTrue(awaitReady2);
80+
assertFalse(mEventSent.get());
81+
}
82+
83+
@Test
84+
public void destroyOnFactoryCallsDestroyWithActiveClients() throws InterruptedException {
85+
SplitFactory factory = getFactory(false);
86+
SplitClient client = factory.client();
87+
client.track("some_event");
88+
89+
CountDownLatch latch = new CountDownLatch(1);
90+
client.on(SplitEvent.SDK_READY, new TestingHelper.TestEventTask(latch));
91+
92+
// Wait for the client to be ready
93+
boolean readyAwait = latch.await(5, TimeUnit.SECONDS);
94+
95+
CountDownLatch secondReadyLatch = new CountDownLatch(1);
96+
factory.client(new Key("new_key")).on(SplitEvent.SDK_READY, new TestingHelper.TestEventTask(secondReadyLatch));
97+
// Wait for second client to be ready
98+
boolean awaitReady2 = secondReadyLatch.await(5, TimeUnit.SECONDS);
99+
100+
// Destroy the factory
101+
factory.destroy();
102+
// Wait for events to be posted
103+
Thread.sleep(500);
104+
105+
// Verify event was posted to indirectly verify that the factory was destroyed
106+
boolean factoryWasDestroyed = mEventSent.get();
107+
108+
assertTrue(readyAwait);
109+
assertTrue(awaitReady2);
110+
assertTrue(factoryWasDestroyed);
111+
}
112+
113+
private SplitFactory getFactory(boolean ready) throws InterruptedException {
114+
SplitFactory splitFactory = getSplitFactory();
115+
CountDownLatch latch = new CountDownLatch(1);
116+
mRequestCountdownLatch.countDown();
117+
118+
splitFactory.client().on(SplitEvent.SDK_READY, new TestingHelper.TestEventTask(latch));
119+
if (ready) {
120+
boolean await = latch.await(5, TimeUnit.SECONDS);
121+
122+
if (!await) {
123+
fail("Client was not ready");
124+
}
125+
}
126+
127+
return splitFactory;
128+
}
129+
130+
private SplitFactory getSplitFactory() {
131+
final String url = mWebServer.url("/").url().toString();
132+
ServiceEndpoints endpoints = ServiceEndpoints.builder()
133+
.apiEndpoint(url)
134+
.eventsEndpoint(url)
135+
.telemetryServiceEndpoint(url)
136+
.build();
137+
SplitClientConfig config = new SplitClientConfig.Builder()
138+
.trafficType("user")
139+
.serviceEndpoints(endpoints)
140+
.streamingEnabled(false)
141+
.featuresRefreshRate(9999)
142+
.segmentsRefreshRate(9999)
143+
.impressionsRefreshRate(9999)
144+
.logLevel(SplitLogLevel.VERBOSE)
145+
.streamingEnabled(false)
146+
.build();
147+
148+
return buildFactory(IntegrationHelper.dummyApiKey(), IntegrationHelper.dummyUserKey(), config,
149+
mContext, null, DatabaseHelper.getTestDatabase(mContext));
150+
}
151+
152+
private void setupServer() {
153+
mWebServer = new MockWebServer();
154+
155+
final Dispatcher dispatcher = new Dispatcher() {
156+
157+
@Override
158+
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
159+
mRequestCountdownLatch.await();
160+
Thread.sleep(200);
161+
Logger.e("Path is: " + request.getPath());
162+
if (request.getPath().contains("/" + IntegrationHelper.ServicePath.MEMBERSHIPS)) {
163+
return new MockResponse().setResponseCode(200).setBody(emptyAllSegments());
164+
} else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.SPLIT_CHANGES)) {
165+
String sinceFromUri = getSinceFromUri(request.getRequestUrl().uri());
166+
if (sinceFromUri.equals("-1")) {
167+
return new MockResponse().setResponseCode(200).setBody(loadSplitChanges());
168+
} else {
169+
return new MockResponse().setResponseCode(200)
170+
.setBody(IntegrationHelper.emptySplitChanges(1506703262916L, 1506703262916L));
171+
}
172+
} else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.EVENTS)) {
173+
mEventSent.set(true);
174+
return new MockResponse().setResponseCode(200);
175+
} else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.COUNT)) {
176+
return new MockResponse().setResponseCode(200);
177+
} else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.IMPRESSIONS)) {
178+
return new MockResponse().setResponseCode(200);
179+
} else if (request.getPath().contains("/" + IntegrationHelper.ServicePath.UNIQUE_KEYS)) {
180+
return new MockResponse().setResponseCode(200);
181+
} else {
182+
return new MockResponse().setResponseCode(404);
183+
}
184+
}
185+
};
186+
mWebServer.setDispatcher(dispatcher);
187+
}
188+
189+
private String loadSplitChanges() {
190+
FileHelper fileHelper = new FileHelper();
191+
String change = fileHelper.loadFileContent(mContext, "split_changes_1.json");
192+
SplitChange parsedChange = Json.fromJson(change, SplitChange.class);
193+
parsedChange.since = parsedChange.till;
194+
return Json.toJson(parsedChange);
195+
}
196+
}

src/main/java/io/split/android/client/SplitClientImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ public void destroy() {
7777
if (splitClientContainer.getAll().isEmpty()) {
7878
SplitFactory splitFactory = mSplitFactory.get();
7979
if (splitFactory != null) {
80+
if (splitFactory instanceof SplitFactoryImpl) {
81+
try {
82+
((SplitFactoryImpl) splitFactory).checkClients();
83+
} catch (ClassCastException ignored) {
84+
85+
}
86+
}
8087
splitFactory.destroy();
8188
}
8289
}

src/main/java/io/split/android/client/SplitFactoryHelper.java

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.concurrent.ScheduledThreadPoolExecutor;
2121
import java.util.concurrent.ThreadPoolExecutor;
2222
import java.util.concurrent.TimeUnit;
23+
import java.util.concurrent.locks.ReentrantLock;
2324

2425
import io.split.android.client.common.CompressionUtilProvider;
2526
import io.split.android.client.events.EventsManagerCoordinator;
@@ -485,6 +486,7 @@ static class Initializer implements Runnable {
485486

486487
private final RolloutCacheManager mRolloutCacheManager;
487488
private final SplitTaskExecutionListener mListener;
489+
private final ReentrantLock mInitLock;
488490

489491
Initializer(
490492
String apiToken,
@@ -497,23 +499,28 @@ static class Initializer implements Runnable {
497499
SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor,
498500
SplitStorageContainer storageContainer,
499501
SyncManager syncManager,
500-
SplitLifecycleManager lifecycleManager) {
502+
SplitLifecycleManager lifecycleManager,
503+
ReentrantLock initLock) {
501504

502505
this(new RolloutCacheManagerImpl(config,
503506
storageContainer,
504507
splitTaskFactory.createCleanUpDatabaseTask(System.currentTimeMillis() / 1000),
505508
splitTaskFactory.createEncryptionMigrationTask(apiToken, splitDatabase, config.encryptionEnabled(), splitCipher)),
506-
new Listener(eventsManagerCoordinator, splitTaskExecutor, splitSingleThreadTaskExecutor, syncManager, lifecycleManager));
509+
new Listener(eventsManagerCoordinator, splitTaskExecutor, splitSingleThreadTaskExecutor, syncManager, lifecycleManager, initLock),
510+
initLock);
507511
}
508512

509513
@VisibleForTesting
510-
Initializer(RolloutCacheManager rolloutCacheManager, SplitTaskExecutionListener listener) {
514+
Initializer(RolloutCacheManager rolloutCacheManager, SplitTaskExecutionListener listener, ReentrantLock initLock) {
511515
mRolloutCacheManager = rolloutCacheManager;
512516
mListener = listener;
517+
mInitLock = initLock;
513518
}
514519

515520
@Override
516521
public void run() {
522+
Logger.v("Running SDK initializer");
523+
mInitLock.lock();
517524
mRolloutCacheManager.validateCache(mListener);
518525
}
519526

@@ -524,30 +531,38 @@ static class Listener implements SplitTaskExecutionListener {
524531
private final SplitSingleThreadTaskExecutor mSplitSingleThreadTaskExecutor;
525532
private final SyncManager mSyncManager;
526533
private final SplitLifecycleManager mLifecycleManager;
534+
private final ReentrantLock mInitLock;
527535

528536
Listener(EventsManagerCoordinator eventsManagerCoordinator,
529537
SplitTaskExecutor splitTaskExecutor,
530538
SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor,
531539
SyncManager syncManager,
532-
SplitLifecycleManager lifecycleManager) {
540+
SplitLifecycleManager lifecycleManager,
541+
ReentrantLock initLock) {
533542
mEventsManagerCoordinator = eventsManagerCoordinator;
534543
mSplitTaskExecutor = splitTaskExecutor;
535544
mSplitSingleThreadTaskExecutor = splitSingleThreadTaskExecutor;
536545
mSyncManager = syncManager;
537546
mLifecycleManager = lifecycleManager;
547+
mInitLock = initLock;
538548
}
539549

540550
@Override
541551
public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) {
542-
mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE);
543-
544-
mSplitTaskExecutor.resume();
545-
mSplitSingleThreadTaskExecutor.resume();
546-
547-
mSyncManager.start();
548-
mLifecycleManager.register(mSyncManager);
549-
550-
Logger.i("Android SDK initialized!");
552+
try {
553+
mEventsManagerCoordinator.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE);
554+
555+
mSplitTaskExecutor.resume();
556+
mSplitSingleThreadTaskExecutor.resume();
557+
558+
mSyncManager.start();
559+
mLifecycleManager.register(mSyncManager);
560+
Logger.i("Android SDK initialized!");
561+
} catch (Exception e) {
562+
Logger.e("Error initializing Android SDK", e);
563+
} finally {
564+
mInitLock.unlock();
565+
}
551566
}
552567
}
553568
}

0 commit comments

Comments
 (0)