Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion clients/java/openfeature-provider/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ dependencies {
}

tasks.test {
useJUnitPlatform()
useJUnitPlatform {
// Unit tests run by default; the live-server integration tests are opt-in. This lets the
// test task auto-discover every unit test without maintaining an explicit class list.
if (!project.hasProperty("withIntegration")) {
excludeTags("integration")
}
}
testLogging {
events("passed", "skipped", "failed", "standardOut", "standardError")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void start() {
if (t != null) t.cancel(false);
return;
}
var o = RefreshJob.runRefreshWithTimeout(self.action, self.config.timeout);
var o = RefreshJob.runRefreshWithTimeout(self.action, self.config.getTimeoutMilliseconds());
if (o != null) {
boolean changed = !o.equals(self.latestOutput);
self.latestOutput = o;
Expand All @@ -66,7 +66,7 @@ void start() {
}
},
0,
config.interval,
config.getIntervalMilliseconds(),
TimeUnit.MILLISECONDS
);

Expand All @@ -83,7 +83,7 @@ public Optional<T> getOutput() {
if (poll == null) {
log.warn("Polling hasn't started but the output is being used.");
} else if (!firstOutput.isDone()) {
return Optional.ofNullable(firstOutput.get(config.timeout, TimeUnit.MILLISECONDS));
return Optional.ofNullable(firstOutput.get(config.getTimeoutMilliseconds(), TimeUnit.MILLISECONDS));
}
} catch (Exception e) {
log.warn("Attempted to await for poll output but an exception occurred: {}", e.toString());
Expand Down Expand Up @@ -118,9 +118,9 @@ final class OnDemand<T> implements RefreshJob<T> {
@Override
public Optional<T> getOutput() {
if (!stopped) {
if (lastUpdated - System.currentTimeMillis() < config.ttl) {
if (lastUpdated - System.currentTimeMillis() < config.getTtlMilliseconds()) {
log.debug("Running refresh as current output is stale.");
var o = RefreshJob.runRefreshWithTimeout(action, config.timeout);
var o = RefreshJob.runRefreshWithTimeout(action, config.getTimeoutMilliseconds());
Comment on lines 118 to +123

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="clients/java/openfeature-provider/src/main/java/io/juspay/superposition/openfeature/RefreshJob.java"

printf '\n== Outline ==\n'
ast-grep outline "$FILE" --view expanded || true

printf '\n== Relevant slice ==\n'
nl -ba "$FILE" | sed -n '1,220p'

printf '\n== Search for ttl/lastUpdated/stopped usage ==\n'
rg -n "lastUpdated|ttlMilliseconds|stopped|getOutput\\(" "$FILE"

Repository: juspay/superposition

Length of output: 843


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="clients/java/openfeature-provider/src/main/java/io/juspay/superposition/openfeature/RefreshJob.java"

printf '\n== Relevant slice ==\n'
nl -ba "$FILE" | sed -n '100,160p'

Repository: juspay/superposition

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="clients/java/openfeature-provider/src/main/java/io/juspay/superposition/openfeature/RefreshJob.java"

printf '\n== Relevant slice 108-140 ==\n'
sed -n '108,140p' "$FILE"

printf '\n== Relevant slice 80-110 ==\n'
sed -n '80,110p' "$FILE"

printf '\n== Search for lastUpdated assignment and ttl usage ==\n'
rg -n "lastUpdated\\s*=|getTtlMilliseconds\\(|ttlMilliseconds|stopped" "$FILE"

Repository: juspay/superposition

Length of output: 3330


Fix the TTL comparison
lastUpdated - System.currentTimeMillis() < config.getTtlMilliseconds() is backwards. With a non-negative TTL, this stays true after every refresh, so getOutput() always takes the refresh path and the cache never behaves as intended.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@clients/java/openfeature-provider/src/main/java/io/juspay/superposition/openfeature/RefreshJob.java`
around lines 118 - 123, Correct the staleness condition in RefreshJob.getOutput:
compare the elapsed time since lastUpdated (System.currentTimeMillis() -
lastUpdated) against config.getTtlMilliseconds(), so refresh runs only when the
cached output has exceeded its TTL. Preserve the existing stopped handling and
refresh flow.

if (o != null) {
boolean changed = !o.equals(output);
output = o;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.juspay.superposition.openfeature;

import dev.openfeature.sdk.Structure;
import dev.openfeature.sdk.Value;
import lombok.Builder;
import lombok.Data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
Expand All @@ -33,7 +34,7 @@
* // Configure experimentation options
* SuperpositionProviderOptions.ExperimentationOptions expOptions =
* SuperpositionProviderOptions.ExperimentationOptions.builder()
* .refreshStrategy(RefreshStrategy.Polling.of(5000, 2000)) // 5s timeout, 2s interval
* .refreshStrategy(new RefreshStrategy.Polling(5000, 2000)) // 5s timeout, 2s interval
* .build();
*
* // Configure provider options
Expand All @@ -43,7 +44,7 @@
* .workspaceId("your-workspace-id")
* .endpoint("https://api.superposition.dev")
* .token("your-api-token")
* .refreshStrategy(RefreshStrategy.Polling.of(10000, 5000)) // 10s timeout, 5s interval
* .refreshStrategy(new RefreshStrategy.Polling(10000, 5000)) // 10s timeout, 5s interval
* .experimentationOptions(expOptions)
* .build();
*
Expand Down Expand Up @@ -95,7 +96,7 @@ public SuperpositionOpenFeatureProvider(@NonNull SuperpositionProviderOptions op
}
this.sdk = builder.build();
this.cache = new ProviderCache();
this.configTimeout = options.refreshStrategy.getTimeout();
this.configTimeout = options.refreshStrategy.getTimeoutMilliseconds();

var getConfigInput = GetConfigInput.builder()
.context(Map.of())
Expand All @@ -110,7 +111,7 @@ public SuperpositionOpenFeatureProvider(@NonNull SuperpositionProviderOptions op

if (options.experimentationOptions != null) {
this.experimentationTimeout =
options.experimentationOptions.refreshStrategy.getTimeout();
options.experimentationOptions.refreshStrategy.getTimeoutMilliseconds();
var listExpInput = ListExperimentInput.builder()
.orgId(options.orgId)
.workspaceId(options.workspaceId)
Expand Down Expand Up @@ -328,16 +329,16 @@ private Map<String, String> evaluateConfigInternal(EvaluationContext ctx) throws
throw new Exception("Experiments cache not initialized within timeout (" + experimentationTimeout + "ms).");
}
}
var ctx_ = defaultCtx.isPresent() ? ctx.merge(defaultCtx.get()) : ctx;
var ctx_ = defaultCtx.isPresent() ? defaultCtx.get().merge(ctx) : ctx;
var queryData = EvaluationArgs.Companion.buildQueryData(ctx_);
String targetingKey = ctx_.getTargetingKey();
return cache.evalConfig(queryData, MergeStrategy.MERGE, null, targetingKey);
}

private List<String> getApplicableVariantsInternal(EvaluationContext ctx) throws Exception {
EvaluationArgs args = getEvaluationArgs(ctx);
var ctx_ = defaultCtx.isPresent() ? ctx.merge(defaultCtx.get()) : ctx;
return args.getApplicableVariants(ctx_, getExperimentationArgs(ctx_));
var ctx_ = defaultCtx.isPresent() ? defaultCtx.get().merge(ctx) : ctx;
return args.getApplicableVariants(ctx_, Objects.requireNonNull(getExperimentationArgs(ctx_)));
Comment on lines 338 to +341
}

private ExperimentationArgs getExperimentationArgs(EvaluationContext ctx) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.juspay.superposition.openfeature;

import io.juspay.superposition.openfeature.options.EvaluationCacheOptions;
import io.juspay.superposition.openfeature.options.RefreshStrategy;
import lombok.Builder;
import lombok.Data;
Expand Down Expand Up @@ -52,8 +51,5 @@ public static class ExperimentationOptions {
/** Refresh strategy for experimentation data. */
@NonNull
RefreshStrategy refreshStrategy;
/** Evaluation cache options for experimentation data (optional). */
@Nullable
EvaluationCacheOptions evaluationCacheOptions;
}
}
Loading
Loading