1
1
package com .launchdarkly .sdk .server ;
2
2
3
- import com .google .common .collect .ImmutableList ;
4
3
import com .google .common .collect .ImmutableSet ;
5
4
import com .launchdarkly .sdk .EvaluationReason ;
6
5
import com .launchdarkly .sdk .EvaluationReason .ErrorKind ;
16
15
17
16
import java .time .Instant ;
18
17
import java .util .ArrayList ;
18
+ import java .util .Collections ;
19
19
import java .util .List ;
20
20
import java .util .Set ;
21
21
import java .util .function .Function ;
@@ -62,10 +62,10 @@ EvalResult getResult(boolean inExperiment) {
62
62
}
63
63
64
64
static final class EvalResultFactoryMultiVariations {
65
- private final ImmutableList <EvalResultsForSingleVariation > variations ;
65
+ private final List <EvalResultsForSingleVariation > variations ;
66
66
67
67
EvalResultFactoryMultiVariations (
68
- ImmutableList <EvalResultsForSingleVariation > variations
68
+ List <EvalResultsForSingleVariation > variations
69
69
) {
70
70
this .variations = variations ;
71
71
}
@@ -74,6 +74,13 @@ EvalResult forVariation(int index, boolean inExperiment) {
74
74
if (index < 0 || index >= variations .size ()) {
75
75
return EvalResult .error (ErrorKind .MALFORMED_FLAG );
76
76
}
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
+
77
84
return variations .get (index ).getResult (inExperiment );
78
85
}
79
86
}
@@ -140,7 +147,7 @@ static final class ValueData {
140
147
static void preprocessFlag (FeatureFlag f ) {
141
148
f .preprocessed = new FlagPreprocessed (
142
149
EvaluatorHelpers .offResult (f ),
143
- precomputeMultiVariationResults (f , EvaluationReason .fallthrough (false ),
150
+ precomputeMultiVariationResultsForFlag (f , EvaluationReason .fallthrough (false ),
144
151
EvaluationReason .fallthrough (true ), f .isTrackEventsFallthrough ())
145
152
);
146
153
@@ -183,7 +190,7 @@ static void preprocessTarget(Target t, FeatureFlag f) {
183
190
static void preprocessFlagRule (Rule r , int ruleIndex , FeatureFlag f ) {
184
191
EvaluationReason ruleMatchReason = EvaluationReason .ruleMatch (ruleIndex , r .getId (), false );
185
192
EvaluationReason ruleMatchReasonInExperiment = EvaluationReason .ruleMatch (ruleIndex , r .getId (), true );
186
- r .preprocessed = new FlagRulePreprocessed (precomputeMultiVariationResults ( f ,
193
+ r .preprocessed = new FlagRulePreprocessed (precomputeMultiVariationResultsForRule ( f , r ,
187
194
ruleMatchReason , ruleMatchReasonInExperiment , r .isTrackEvents ()));
188
195
189
196
for (Clause c : r .getClauses ()) {
@@ -255,18 +262,52 @@ private static ClausePreprocessed preprocessClauseValues(
255
262
return new ClausePreprocessed (null , valuesExtra );
256
263
}
257
264
258
- private static EvalResultFactoryMultiVariations precomputeMultiVariationResults (
265
+ private static EvalResultFactoryMultiVariations precomputeMultiVariationResultsForFlag (
259
266
FeatureFlag f ,
260
267
EvaluationReason regularReason ,
261
268
EvaluationReason inExperimentReason ,
262
269
boolean alwaysInExperiment
263
270
) {
264
- ImmutableList .Builder <EvalResultsForSingleVariation > builder =
265
- ImmutableList .builderWithExpectedSize (f .getVariations ().size ());
271
+ ArrayList <EvalResultsForSingleVariation > variations = new ArrayList <>(f .getVariations ().size ());
266
272
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 ,
268
274
regularReason , inExperimentReason , alwaysInExperiment ));
269
275
}
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 ));
271
312
}
272
313
}
0 commit comments