Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit d05629d

Browse files
LaunchDarklyReleaseBoteli-darklyLaunchDarklyReleaseBotaengelbergantonmos
authored
prepare 7.4.1 release (#327)
## [7.4.1] - 2024-05-13 ### Added: - Adds warning log if excessive start wait time is used. ### Fixed: - Improved preprocessing allocations to reduce memory footprint in rare flag configurations. --------- Co-authored-by: Eli Bishop <[email protected]> Co-authored-by: LaunchDarklyReleaseBot <[email protected]> Co-authored-by: Alex Engelberg <[email protected]> Co-authored-by: Anton Mostovoy <[email protected]> Co-authored-by: LaunchDarklyCI <[email protected]> Co-authored-by: LaunchDarklyCI <[email protected]> Co-authored-by: Gavin Whelan <[email protected]> Co-authored-by: ssrm <[email protected]> Co-authored-by: Harpo Roeder <[email protected]> Co-authored-by: Ben Woskow <[email protected]> Co-authored-by: Elliot <[email protected]> Co-authored-by: Robert J. Neal <[email protected]> Co-authored-by: Robert J. Neal <[email protected]> Co-authored-by: Sam Stokes <[email protected]> Co-authored-by: Ember Stevens <[email protected]> Co-authored-by: ember-stevens <[email protected]> Co-authored-by: Alex Engelberg <[email protected]> Co-authored-by: Louis Chan <[email protected]> Co-authored-by: Louis Chan <[email protected]> Co-authored-by: Todd Anderson <[email protected]> Co-authored-by: tanderson-ld <[email protected]> Co-authored-by: Matthew M. Keeler <[email protected]> Co-authored-by: ld-repository-standards[bot] <113625520+ld-repository-standards[bot]@users.noreply.github.com> Co-authored-by: Kane Parkinson <[email protected]> Co-authored-by: Ryan Lamb <[email protected]>
1 parent 88c0a1d commit d05629d

File tree

4 files changed

+99
-16
lines changed

4 files changed

+99
-16
lines changed

src/main/java/com/launchdarkly/sdk/server/DataModelPreprocessing.java

+51-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.launchdarkly.sdk.server;
22

3-
import com.google.common.collect.ImmutableList;
43
import com.google.common.collect.ImmutableSet;
54
import com.launchdarkly.sdk.EvaluationReason;
65
import com.launchdarkly.sdk.EvaluationReason.ErrorKind;
@@ -16,6 +15,7 @@
1615

1716
import java.time.Instant;
1817
import java.util.ArrayList;
18+
import java.util.Collections;
1919
import java.util.List;
2020
import java.util.Set;
2121
import java.util.function.Function;
@@ -62,10 +62,10 @@ EvalResult getResult(boolean inExperiment) {
6262
}
6363

6464
static final class EvalResultFactoryMultiVariations {
65-
private final ImmutableList<EvalResultsForSingleVariation> variations;
65+
private final List<EvalResultsForSingleVariation> variations;
6666

6767
EvalResultFactoryMultiVariations(
68-
ImmutableList<EvalResultsForSingleVariation> variations
68+
List<EvalResultsForSingleVariation> variations
6969
) {
7070
this.variations = variations;
7171
}
@@ -74,6 +74,13 @@ EvalResult forVariation(int index, boolean inExperiment) {
7474
if (index < 0 || index >= variations.size()) {
7575
return EvalResult.error(ErrorKind.MALFORMED_FLAG);
7676
}
77+
78+
if (variations.get(index) == null) {
79+
// getting here indicates that the preprocessor incorrectly processed data and another piece of code is
80+
// asking for a variation that was not populated ahead of time. This is an unexpected bug if it happens.
81+
return EvalResult.error(ErrorKind.EXCEPTION);
82+
}
83+
7784
return variations.get(index).getResult(inExperiment);
7885
}
7986
}
@@ -140,7 +147,7 @@ static final class ValueData {
140147
static void preprocessFlag(FeatureFlag f) {
141148
f.preprocessed = new FlagPreprocessed(
142149
EvaluatorHelpers.offResult(f),
143-
precomputeMultiVariationResults(f, EvaluationReason.fallthrough(false),
150+
precomputeMultiVariationResultsForFlag(f, EvaluationReason.fallthrough(false),
144151
EvaluationReason.fallthrough(true), f.isTrackEventsFallthrough())
145152
);
146153

@@ -183,7 +190,7 @@ static void preprocessTarget(Target t, FeatureFlag f) {
183190
static void preprocessFlagRule(Rule r, int ruleIndex, FeatureFlag f) {
184191
EvaluationReason ruleMatchReason = EvaluationReason.ruleMatch(ruleIndex, r.getId(), false);
185192
EvaluationReason ruleMatchReasonInExperiment = EvaluationReason.ruleMatch(ruleIndex, r.getId(), true);
186-
r.preprocessed = new FlagRulePreprocessed(precomputeMultiVariationResults(f,
193+
r.preprocessed = new FlagRulePreprocessed(precomputeMultiVariationResultsForRule(f, r,
187194
ruleMatchReason, ruleMatchReasonInExperiment, r.isTrackEvents()));
188195

189196
for (Clause c: r.getClauses()) {
@@ -255,18 +262,52 @@ private static ClausePreprocessed preprocessClauseValues(
255262
return new ClausePreprocessed(null, valuesExtra);
256263
}
257264

258-
private static EvalResultFactoryMultiVariations precomputeMultiVariationResults(
265+
private static EvalResultFactoryMultiVariations precomputeMultiVariationResultsForFlag(
259266
FeatureFlag f,
260267
EvaluationReason regularReason,
261268
EvaluationReason inExperimentReason,
262269
boolean alwaysInExperiment
263270
) {
264-
ImmutableList.Builder<EvalResultsForSingleVariation> builder =
265-
ImmutableList.builderWithExpectedSize(f.getVariations().size());
271+
ArrayList<EvalResultsForSingleVariation> variations = new ArrayList<>(f.getVariations().size());
266272
for (int i = 0; i < f.getVariations().size(); i++) {
267-
builder.add(new EvalResultsForSingleVariation(f.getVariations().get(i), i,
273+
variations.add(new EvalResultsForSingleVariation(f.getVariations().get(i), i,
268274
regularReason, inExperimentReason, alwaysInExperiment));
269275
}
270-
return new EvalResultFactoryMultiVariations(builder.build());
276+
return new EvalResultFactoryMultiVariations(Collections.unmodifiableList(variations));
277+
}
278+
279+
private static EvalResultFactoryMultiVariations precomputeMultiVariationResultsForRule(
280+
FeatureFlag f,
281+
Rule r,
282+
EvaluationReason regularReason,
283+
EvaluationReason inExperimentReason,
284+
boolean alwaysInExperiment
285+
) {
286+
// Here we create a list of nulls and then insert into that list variations from the rule at their associated index.
287+
// This allows the evaluator to then index into the array in constant time. Alternative options are to use a map or
288+
// a sparse array. The map has high memory footprint for most customer situations, so it was not used. There is no
289+
// standard implementation for sparse array, and it is also not always constant time. Most customers don't have
290+
// many variations per flag and so these arrays should not be large on average. This approach was part of a bugfix
291+
// and this approach cuts the memory footprint enough to meet the need.
292+
List<EvalResultsForSingleVariation> variations = new ArrayList<>(Collections.nCopies(f.getVariations().size(), null));
293+
if (r.getVariation() != null) {
294+
int index = r.getVariation();
295+
if (index >= 0 && index < f.getVariations().size()) {
296+
variations.set(index, new EvalResultsForSingleVariation(f.getVariations().get(index), index,
297+
regularReason, inExperimentReason, alwaysInExperiment));
298+
}
299+
}
300+
301+
if (r.getRollout() != null && r.getRollout().getVariations() != null) {
302+
for (DataModel.WeightedVariation wv : r.getRollout().getVariations()) {
303+
int index = wv.getVariation();
304+
if (index >= 0 && index < f.getVariations().size()) {
305+
variations.set(index, new EvalResultsForSingleVariation(f.getVariations().get(index), index,
306+
regularReason, inExperimentReason, alwaysInExperiment));
307+
}
308+
}
309+
}
310+
311+
return new EvalResultFactoryMultiVariations(Collections.unmodifiableList(variations));
271312
}
272313
}

src/main/java/com/launchdarkly/sdk/server/DataSourceUpdatesImpl.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,11 @@ private void onTimeout() {
346346
onOutageErrorLog.accept(errorsDesc);
347347
}
348348
logger.error(
349-
"LaunchDarkly data source outage - updates have been unavailable for at least {} with the following errors: {}",
349+
"A streaming connection to LaunchDarkly has not been established within {} after the connection was interrupted. " +
350+
"The following errors were encountered: {}",
350351
Util.describeDuration(loggingTimeout),
351352
errorsDesc
352-
);
353+
);
353354
}
354355
}
355356

src/main/java/com/launchdarkly/sdk/server/LDClient.java

+5
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ public final class LDClient implements LDClientInterface {
6767
private final LDLogger baseLogger;
6868
private final LDLogger evaluationLogger;
6969

70+
private static final int EXCESSIVE_INIT_WAIT_MILLIS = 60000;
71+
7072
/**
7173
* Creates a new client instance that connects to LaunchDarkly with the default configuration.
7274
* <p>
@@ -239,6 +241,9 @@ public LDClient(String sdkKey, LDConfig config) {
239241
if (!(dataSource instanceof ComponentsImpl.NullDataSource)) {
240242
baseLogger.info("Waiting up to {} milliseconds for LaunchDarkly client to start...",
241243
config.startWait.toMillis());
244+
if (config.startWait.toMillis() > EXCESSIVE_INIT_WAIT_MILLIS) {
245+
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);
246+
}
242247
}
243248
try {
244249
startFuture.get(config.startWait.toMillis(), TimeUnit.MILLISECONDS);

src/test/java/com/launchdarkly/sdk/server/DataModelPreprocessingTest.java

+40-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.time.Instant;
2121
import java.time.ZonedDateTime;
22+
import java.util.ArrayList;
2223
import java.util.List;
2324

2425
import static com.launchdarkly.sdk.server.ModelBuilders.clause;
@@ -136,9 +137,44 @@ public void preprocessFlagAddsPrecomputedPrerequisiteFailedResults() {
136137
}
137138

138139
@Test
139-
public void preprocessFlagAddsPrecomputedResultsToFlagRules() {
140+
public void preprocessFlagAddsPrecomputedResultsToFlagRulesWithRollout() {
141+
142+
List<DataModel.WeightedVariation> variations = new ArrayList<>();
143+
variations.add(new DataModel.WeightedVariation(0, 50000, false));
144+
variations.add(new DataModel.WeightedVariation(1, 50000, false));
145+
DataModel.RolloutKind kind = DataModel.RolloutKind.rollout;
146+
Integer seed = 123;
147+
DataModel.Rollout rollout = new DataModel.Rollout(null, variations, null, kind, seed);
148+
140149
FeatureFlag f = new FeatureFlag("key", 0, false, null, null, null, null,
141-
ImmutableList.of(new Rule("ruleid0", ImmutableList.of(), null, null, false)),
150+
ImmutableList.of(new Rule("ruleid0", ImmutableList.of(), null, rollout, false)),
151+
null, null,
152+
ImmutableList.of(aValue, bValue),
153+
false, false, false, null, false, null, null, false);
154+
155+
f.afterDeserialized();
156+
157+
Rule rule = f.getRules().get(0);
158+
assertThat(rule.preprocessed, notNullValue());
159+
assertThat(rule.preprocessed.allPossibleResults, notNullValue());
160+
EvaluationReason regularReason = EvaluationReason.ruleMatch(0, "ruleid0", false);
161+
EvaluationReason inExperimentReason = EvaluationReason.ruleMatch(0, "ruleid0", true);
162+
163+
assertThat(rule.preprocessed.allPossibleResults.forVariation(0, false),
164+
equalTo(EvalResult.of(aValue, 0, regularReason)));
165+
assertThat(rule.preprocessed.allPossibleResults.forVariation(0, true),
166+
equalTo(EvalResult.of(aValue, 0, inExperimentReason)));
167+
168+
assertThat(rule.preprocessed.allPossibleResults.forVariation(1, false),
169+
equalTo(EvalResult.of(bValue, 1, regularReason)));
170+
assertThat(rule.preprocessed.allPossibleResults.forVariation(1, true),
171+
equalTo(EvalResult.of(bValue, 1, inExperimentReason)));
172+
}
173+
174+
@Test
175+
public void preprocessFlagAddsPrecomputedResultsToFlagRulesWithJustVariation() {
176+
FeatureFlag f = new FeatureFlag("key", 0, false, null, null, null, null,
177+
ImmutableList.of(new Rule("ruleid0", ImmutableList.of(), 0, null, false)),
142178
null, null,
143179
ImmutableList.of(aValue, bValue),
144180
false, false, false, null, false, null, null, false);
@@ -157,9 +193,9 @@ public void preprocessFlagAddsPrecomputedResultsToFlagRules() {
157193
equalTo(EvalResult.of(aValue, 0, inExperimentReason)));
158194

159195
assertThat(rule.preprocessed.allPossibleResults.forVariation(1, false),
160-
equalTo(EvalResult.of(bValue, 1, regularReason)));
196+
equalTo(EvalResult.error(EvaluationReason.ErrorKind.EXCEPTION)));
161197
assertThat(rule.preprocessed.allPossibleResults.forVariation(1, true),
162-
equalTo(EvalResult.of(bValue, 1, inExperimentReason)));
198+
equalTo(EvalResult.error(EvaluationReason.ErrorKind.EXCEPTION)));
163199
}
164200

165201
@Test

0 commit comments

Comments
 (0)