diff --git a/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/LDClient.java b/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/LDClient.java
index 18f314ca..593673ff 100644
--- a/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/LDClient.java
+++ b/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/LDClient.java
@@ -31,6 +31,7 @@
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -72,6 +73,10 @@ public final class LDClient implements LDClientInterface {
private static final int EXCESSIVE_INIT_WAIT_MILLIS = 60000;
+ // The largest start wait the SDK can represent in milliseconds. A longer wait is clamped to it rather than
+ // converted, because converting it would overflow and throw (and would do so after the data system starts).
+ private static final Duration MAX_MILLIS_START_WAIT = Duration.ofMillis(Long.MAX_VALUE);
+
/**
* Creates a new client instance that connects to LaunchDarkly with the default configuration.
*
@@ -259,15 +264,21 @@ public LDClient(String sdkKey, LDConfig config) {
// Start the data system
Future startFuture = dataSystem.start();
if (!config.startWait.isZero() && !config.startWait.isNegative()) {
+ // Convert the start wait to milliseconds, clamping a wait too large to represent to the maximum rather
+ // than converting it, which would overflow and throw. Only the positive boundary matters here, since
+ // zero and negative waits are excluded above.
+ long startWaitMillis = config.startWait.compareTo(MAX_MILLIS_START_WAIT) >= 0
+ ? Long.MAX_VALUE
+ : config.startWait.toMillis();
if (!dataSystem.isInitialized()) {
baseLogger.info("Waiting up to {} milliseconds for LaunchDarkly client to start...",
- config.startWait.toMillis());
- if (config.startWait.toMillis() > EXCESSIVE_INIT_WAIT_MILLIS) {
- baseLogger.warn("LaunchDarkly client created with start wait time of {} milliseconds. We recommend a timeout of less than {} milliseconds.", config.startWait.toMillis(), EXCESSIVE_INIT_WAIT_MILLIS);
+ startWaitMillis);
+ if (startWaitMillis > EXCESSIVE_INIT_WAIT_MILLIS) {
+ baseLogger.warn("LaunchDarkly client created with start wait time of {} milliseconds. We recommend a timeout of less than {} milliseconds.", startWaitMillis, EXCESSIVE_INIT_WAIT_MILLIS);
}
}
try {
- startFuture.get(config.startWait.toMillis(), TimeUnit.MILLISECONDS);
+ startFuture.get(startWaitMillis, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
baseLogger.error("Timeout encountered waiting for LaunchDarkly client initialization");
} catch (Exception e) {
diff --git a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java
index e313aa68..a53c80f2 100644
--- a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java
+++ b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientTest.java
@@ -331,6 +331,25 @@ public void willWaitForDataSourceIfWaitMillisIsGreaterThanZero() throws Exceptio
mocks.verifyAll();
}
+ @Test
+ public void startWaitTooLargeForMillisIsClampedRatherThanOverflowing() throws Exception {
+ // A start wait time too large to represent in milliseconds must be clamped, not converted directly:
+ // converting it throws ArithmeticException, and it would do so after the data source has started, leaving
+ // the data source running with no reference available to close it.
+ LDConfig.Builder config = new LDConfig.Builder()
+ .startWait(Duration.ofSeconds(Long.MAX_VALUE));
+
+ expect(dataSource.start()).andReturn(initFuture);
+ expect(initFuture.get(Long.MAX_VALUE, TimeUnit.MILLISECONDS)).andReturn(null);
+ expect(dataSource.isInitialized()).andReturn(false).anyTimes();
+ mocks.replayAll();
+
+ client = createMockClient(config);
+ assertFalse(client.isInitialized());
+
+ mocks.verifyAll();
+ }
+
@Test
public void noWaitForDataSourceIfWaitMillisIsNegative() throws Exception {
LDConfig.Builder config = new LDConfig.Builder()