Skip to content

Commit 75ac7cf

Browse files
authored
Change LDAwaitFuture to not treat zero timeout as unlimited timeout
Treating a timeout of zero as unlimited caused a change in behavior when initializing the SDK. This update restores the behavior init had when zero was passed as the timeout argument from pre-2.8.0. Also improves handling of spurious wakeups, and includes test cases for LDAwaitFuture.
1 parent bf50576 commit 75ac7cf

File tree

2 files changed

+133
-7
lines changed

2 files changed

+133
-7
lines changed
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) {

0 commit comments

Comments
 (0)