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
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,10 @@ private void handleDataSourceStatus(DataSourceStatusProvider.Status res, Complet
// Our client/provider cannot be restarted, so we just go to error.
setState(ProviderState.ERROR);
completer.complete(false);
emitProviderError(ProviderEventDetails.builder().message("Provider shutdown").build());
var message = res.getLastError() != null
? res.getLastError().toString()
: "the provider has encountered a permanent error or has been shutdown";
emitProviderError(ProviderEventDetails.builder().message(message).build());
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,26 @@
class DelayedDataSource implements DataSource {
private Duration startDelay;
private boolean willError;
private boolean errorAfterInitialization;
private boolean useHttpError;
private boolean initialized = false;
private Object lock = new Object();
DataSourceUpdateSink sink;

DelayedDataSource(Duration delay, boolean error, DataSourceUpdateSink sink) {
this(delay, error, false, false, sink);
}

DelayedDataSource(Duration delay, boolean error, boolean errorAfterInitialization, DataSourceUpdateSink sink) {
this(delay, error, errorAfterInitialization, false, sink);
}

DelayedDataSource(Duration delay, boolean error, boolean errorAfterInitialization, boolean useHttpError,
DataSourceUpdateSink sink) {
startDelay = delay;
willError = error;
this.errorAfterInitialization = errorAfterInitialization;
this.useHttpError = useHttpError;
this.sink = sink;
}

Expand All @@ -58,13 +71,11 @@ public void run() {
synchronized (lock) {
initialized = true;
}
if (errorAfterInitialization) {
sink.updateStatus(DataSourceStatusProvider.State.OFF, errorInfo());
}
} else {
sink.updateStatus(DataSourceStatusProvider.State.OFF,
new DataSourceStatusProvider.ErrorInfo(
DataSourceStatusProvider.ErrorKind.NETWORK_ERROR,
404,
"bad",
LocalDateTime.now().toInstant(ZoneOffset.UTC)));
sink.updateStatus(DataSourceStatusProvider.State.OFF, errorInfo());
}
future.complete(null);
}
Expand All @@ -73,6 +84,16 @@ public void run() {
return future;
}

private DataSourceStatusProvider.ErrorInfo errorInfo() {
return useHttpError
? DataSourceStatusProvider.ErrorInfo.fromHttpError(401)
: new DataSourceStatusProvider.ErrorInfo(
DataSourceStatusProvider.ErrorKind.NETWORK_ERROR,
404,
"bad",
LocalDateTime.now().toInstant(ZoneOffset.UTC));
}

public boolean isInitialized() {
synchronized (lock) {
return initialized;
Expand All @@ -86,15 +107,32 @@ public void close() throws IOException {
class DelayedDataSourceFactory implements ComponentConfigurer<DataSource> {
private Duration startDelay;
private boolean willError;
private boolean errorAfterInitialization;
private boolean useHttpError;

DelayedDataSourceFactory(Duration delay, boolean error) {
this(delay, error, false);
}

DelayedDataSourceFactory(Duration delay, boolean error, boolean errorAfterInitialization) {
this(delay, error, errorAfterInitialization, false);
}

DelayedDataSourceFactory(Duration delay, boolean error, boolean errorAfterInitialization, boolean useHttpError) {
startDelay = delay;
willError = error;
this.errorAfterInitialization = errorAfterInitialization;
this.useHttpError = useHttpError;
}

@Override
public DataSource build(ClientContext clientContext) {
return new DelayedDataSource(startDelay, willError, clientContext.getDataSourceUpdateSink());
return new DelayedDataSource(
startDelay,
willError,
errorAfterInitialization,
useHttpError,
clientContext.getDataSourceUpdateSink());
}
}

Expand Down Expand Up @@ -221,4 +259,47 @@ public void itCanHandleClientThatIsNotInitializedImmediatelyAndErrors() throws E

assertTrue(gotErrorEvent.get(1000, TimeUnit.MILLISECONDS));
}

@Test
public void itIncludesTheDataSourceErrorInErrorEvents() throws Exception {
var config = new LDConfig.Builder()
.startWait(Duration.ZERO)
.dataSource(new DelayedDataSourceFactory(Duration.ofMillis(100), false, true))
.events(Components.noEvents())
.build();
var provider = new Provider("fake-key", config);
CompletableFuture<String> errorMessage = new CompletableFuture<>();

OpenFeatureAPI.getInstance().on(ProviderEvent.PROVIDER_ERROR, (detail) -> {
errorMessage.complete(detail.getMessage());
});

OpenFeatureAPI.getInstance().setProviderAndWait(provider);

var message = errorMessage.get(1000, TimeUnit.MILLISECONDS);
assertTrue(message.contains("404"));
assertTrue(message.contains("bad"));
}

@Test
public void itIncludesHttpDataSourceErrorInErrorEvents() throws Exception {
var config = new LDConfig.Builder()
.startWait(Duration.ZERO)
.dataSource(new DelayedDataSourceFactory(Duration.ofMillis(100), false, true, true))
.events(Components.noEvents())
.build();
var provider = new Provider("fake-key", config);
CompletableFuture<String> errorMessage = new CompletableFuture<>();

OpenFeatureAPI.getInstance().on(ProviderEvent.PROVIDER_ERROR, (detail) -> {
errorMessage.complete(detail.getMessage());
});

OpenFeatureAPI.getInstance().setProviderAndWait(provider);

var message = errorMessage.get(1000, TimeUnit.MILLISECONDS);
assertNotNull(message);
assertTrue(!message.isEmpty());
assertTrue(message.contains("401"));
}
}
Loading