Skip to content

Commit e52f4ef

Browse files
authored
Release 2.8.5 (#82)
## [2.8.5] - 2019-07-29 ### Added: - Added a CircleCI badge to the project readme. ### Fixed - Fix a bug introduced in 2.8.0 that could cause the SDK to enter a bad state where it would no longer connect to the flag stream if `identify()` was called rapidly. - Reverted an unintentional behavior change introduced in 2.8.0 when `LDClient.init` is given zero as the timeout argument. Before 2.8.0, this would not wait for initialization and return the client immediately. For 2.8.0-2.8.4 this was changed to wait indefinitely for initialization, 2.8.5 restores the earlier behavior.
1 parent c67be5d commit e52f4ef

File tree

8 files changed

+155
-21
lines changed

8 files changed

+155
-21
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
name: Download Dependencies
2828
command: ./gradlew androidDependencies
2929
- run: sudo mkdir -p $CIRCLE_TEST_REPORTS
30+
- run: sudo apt-get update
3031
- run: sudo apt-get -y -qq install awscli
3132
- run: sudo mkdir -p /usr/local/android-sdk-linux/licenses
3233
- run: sdkmanager "system-images;android-24;default;armeabi-v7a"

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33

44
All notable changes to the LaunchDarkly Android SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
55

6+
## [2.8.5] - 2019-07-29
7+
### Added:
8+
- Added a CircleCI badge to the project readme.
9+
### Fixed
10+
- Fix a bug introduced in 2.8.0 that could cause the SDK to enter a bad state where it would no longer connect to the flag stream if `identify()` was called rapidly.
11+
- Reverted an unintentional behavior change introduced in 2.8.0 when `LDClient.init` is given zero as the timeout argument. Before 2.8.0, this would not wait for initialization and return the client immediately. For 2.8.0-2.8.4 this was changed to wait indefinitely for initialization, 2.8.5 restores the earlier behavior.
12+
613
## [2.8.4] - 2019-06-14
714
### Fixed
815
- Deadlock when waiting on main thread for `identify` call.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# LaunchDarkly SDK for Android
22

3+
[![CircleCI](https://circleci.com/gh/launchdarkly/android-client-sdk.svg?style=svg)](https://circleci.com/gh/launchdarkly/android-client-sdk)
4+
35
## LaunchDarkly overview
46

57
[LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/docs/getting-started) using LaunchDarkly today!

example/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dependencies {
4646
implementation 'com.android.support:appcompat-v7:26.1.0'
4747
implementation project(path: ':launchdarkly-android-client-sdk')
4848
// Comment the previous line and uncomment this one to depend on the published artifact:
49-
//implementation 'com.launchdarkly:launchdarkly-android-client-sdk:2.8.4'
49+
//implementation 'com.launchdarkly:launchdarkly-android-client-sdk:2.8.5'
5050

5151
implementation 'com.jakewharton.timber:timber:4.7.1'
5252

launchdarkly-android-client-sdk/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ apply plugin: 'io.codearte.nexus-staging'
77

88
allprojects {
99
group = 'com.launchdarkly'
10-
version = '2.8.4'
10+
version = '2.8.5'
1111
sourceCompatibility = 1.7
1212
targetCompatibility = 1.7
1313
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.launchdarkly.android;
2+
3+
import android.support.test.runner.AndroidJUnit4;
4+
5+
import org.junit.Rule;
6+
import org.junit.Test;
7+
import org.junit.runner.RunWith;
8+
9+
import java.util.concurrent.Callable;
10+
import java.util.concurrent.ExecutionException;
11+
import java.util.concurrent.TimeUnit;
12+
import java.util.concurrent.TimeoutException;
13+
14+
import static org.junit.Assert.assertFalse;
15+
import static org.junit.Assert.assertSame;
16+
17+
@RunWith(AndroidJUnit4.class)
18+
public class LDAwaitFutureTest {
19+
20+
@Rule
21+
public TimberLoggingRule timberLoggingRule = new TimberLoggingRule();
22+
23+
@Test
24+
public void defaultCancelledValueIsFalse() {
25+
LDAwaitFuture<Void> future = new LDAwaitFuture<>();
26+
assertFalse(future.isCancelled());
27+
}
28+
29+
@Test
30+
public void futureStartsIncomplete() {
31+
LDAwaitFuture<Void> future = new LDAwaitFuture<>();
32+
assertFalse(future.isDone());
33+
}
34+
35+
@Test(timeout = 500L)
36+
public void futureThrowsTimeoutWhenNotSet() throws ExecutionException, InterruptedException {
37+
LDAwaitFuture<Void> future = new LDAwaitFuture<>();
38+
try {
39+
future.get(250, TimeUnit.MILLISECONDS);
40+
} catch (TimeoutException ignored) {
41+
}
42+
}
43+
44+
@Test(timeout = 500L)
45+
public void futureThrowsTimeoutExceptionWithZeroTimeout() throws ExecutionException,
46+
InterruptedException {
47+
LDAwaitFuture<Void> future = new LDAwaitFuture<>();
48+
try {
49+
future.get(0, TimeUnit.SECONDS);
50+
} catch (TimeoutException ignored) {
51+
}
52+
}
53+
54+
@Test(timeout = 500L)
55+
public void futureDoesNotTimeoutOnSuccessfulFuture() throws InterruptedException,
56+
ExecutionException, TimeoutException {
57+
LDAwaitFuture<Void> future = new LDAwaitFuture<>();
58+
future.set(null);
59+
future.get(0, TimeUnit.SECONDS);
60+
}
61+
62+
@Test(timeout = 500L)
63+
public void futureThrowsExecutionExceptionOnFailedFuture() throws InterruptedException,
64+
TimeoutException {
65+
LDAwaitFuture<Void> future = new LDAwaitFuture<>();
66+
Throwable t = new Throwable();
67+
future.setException(t);
68+
try {
69+
future.get(0, TimeUnit.SECONDS);
70+
} catch (ExecutionException ex) {
71+
assertSame(t, ex.getCause());
72+
}
73+
}
74+
75+
@Test(timeout = 500L)
76+
public void futureGetsSuccessfulFuture() throws InterruptedException, ExecutionException {
77+
LDAwaitFuture<Void> future = new LDAwaitFuture<>();
78+
future.set(null);
79+
future.get();
80+
}
81+
82+
@Test(timeout = 500L)
83+
public void futureWakesWaiterOnSuccess() throws Exception {
84+
final LDAwaitFuture<Void> future = new LDAwaitFuture<>();
85+
new Callable<Void>() {
86+
@Override
87+
public Void call() throws Exception {
88+
Thread.sleep(250);
89+
future.set(null);
90+
return null;
91+
}
92+
}.call();
93+
future.get();
94+
}
95+
96+
@Test(timeout = 500L)
97+
public void futureWakesWaiterOnFailure() throws Exception {
98+
final Throwable t = new Throwable();
99+
final LDAwaitFuture<Void> future = new LDAwaitFuture<>();
100+
new Callable<Void>() {
101+
@Override
102+
public Void call() throws Exception {
103+
Thread.sleep(250);
104+
future.setException(t);
105+
return null;
106+
}
107+
}.call();
108+
try {
109+
future.get();
110+
} catch (ExecutionException ex) {
111+
assertSame(t, ex.getCause());
112+
}
113+
}
114+
115+
@Test(timeout = 500L)
116+
public void futureReturnsSetValue() throws ExecutionException, InterruptedException {
117+
Object testObject = new Object();
118+
LDAwaitFuture<Object> future = new LDAwaitFuture<>();
119+
future.set(testObject);
120+
assertSame(testObject, future.get());
121+
}
122+
}

launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/android/LDAwaitFuture.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class LDAwaitFuture<T> implements Future<T> {
1515
private volatile boolean completed = false;
1616
private final Object notifier = new Object();
1717

18-
LDAwaitFuture() {}
18+
LDAwaitFuture() {
19+
}
1920

2021
synchronized void set(T result) {
2122
if (!completed) {
@@ -29,7 +30,7 @@ synchronized void set(T result) {
2930
}
3031
}
3132

32-
synchronized void setException(Throwable error) {
33+
synchronized void setException(@NonNull Throwable error) {
3334
if (!completed) {
3435
this.error = error;
3536
synchronized (notifier) {
@@ -59,7 +60,7 @@ public boolean isDone() {
5960
@Override
6061
public T get() throws ExecutionException, InterruptedException {
6162
synchronized (notifier) {
62-
if (!completed) {
63+
while (!completed) {
6364
notifier.wait();
6465
}
6566
}
@@ -70,11 +71,14 @@ public T get() throws ExecutionException, InterruptedException {
7071
}
7172

7273
@Override
73-
public T get(long timeout, @NonNull TimeUnit unit) throws ExecutionException, TimeoutException, InterruptedException {
74+
public T get(long timeout, @NonNull TimeUnit unit) throws ExecutionException,
75+
TimeoutException, InterruptedException {
76+
long remaining = unit.toNanos(timeout);
77+
long doneAt = remaining + System.nanoTime();
7478
synchronized (notifier) {
75-
if (!completed) {
76-
long millis = TimeUnit.MILLISECONDS.convert(timeout, unit);
77-
notifier.wait(millis);
79+
while (!completed & remaining > 0) {
80+
TimeUnit.NANOSECONDS.timedWait(notifier, remaining);
81+
remaining = doneAt - System.nanoTime();
7882
}
7983
}
8084
if (!completed) {

launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/android/StreamUpdateProcessor.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,19 +171,17 @@ public Void call() {
171171

172172
synchronized void stop(final Util.ResultCallback<Void> onCompleteListener) {
173173
Timber.d("Stopping.");
174-
if (es != null) {
175-
// We do this in a separate thread because closing the stream involves a network
176-
// operation and we don't want to do a network operation on the main thread.
177-
executor.execute(new Runnable() {
178-
@Override
179-
public void run() {
180-
stopSync();
181-
if (onCompleteListener != null) {
182-
onCompleteListener.onSuccess(null);
183-
}
174+
// We do this in a separate thread because closing the stream involves a network
175+
// operation and we don't want to do a network operation on the main thread.
176+
executor.execute(new Runnable() {
177+
@Override
178+
public void run() {
179+
stopSync();
180+
if (onCompleteListener != null) {
181+
onCompleteListener.onSuccess(null);
184182
}
185-
});
186-
}
183+
}
184+
});
187185
}
188186

189187
private synchronized void stopSync() {

0 commit comments

Comments
 (0)