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
4 changes: 3 additions & 1 deletion src/main/java/dev/openfeature/sdk/EventProvider.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.openfeature.sdk;

import dev.openfeature.sdk.internal.ConfigurableThreadFactory;
import dev.openfeature.sdk.internal.TriConsumer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand All @@ -21,7 +22,8 @@
@Slf4j
public abstract class EventProvider implements FeatureProvider {
private EventProviderListener eventProviderListener;
private final ExecutorService emitterExecutor = Executors.newCachedThreadPool();
private final ExecutorService emitterExecutor =
Executors.newCachedThreadPool(new ConfigurableThreadFactory("openfeature-event-emitter-thread"));

void setEventProviderListener(EventProviderListener eventProviderListener) {
this.eventProviderListener = eventProviderListener;
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/dev/openfeature/sdk/EventSupport.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.openfeature.sdk;

import dev.openfeature.sdk.internal.ConfigurableThreadFactory;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
Expand All @@ -26,7 +27,8 @@ class EventSupport {
private static final String DEFAULT_CLIENT_UUID = UUID.randomUUID().toString();
private final Map<String, HandlerStore> handlerStores = new ConcurrentHashMap<>();
private final HandlerStore globalHandlerStore = new HandlerStore();
private final ExecutorService taskExecutor = Executors.newCachedThreadPool();
private final ExecutorService taskExecutor =
Executors.newCachedThreadPool(new ConfigurableThreadFactory("openfeature-event-handler-thread"));

/**
* Run all the event handlers associated with this domain.
Expand Down
8 changes: 3 additions & 5 deletions src/main/java/dev/openfeature/sdk/ProviderRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dev.openfeature.sdk.exceptions.GeneralError;
import dev.openfeature.sdk.exceptions.OpenFeatureError;
import dev.openfeature.sdk.internal.ConfigurableThreadFactory;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -22,11 +23,8 @@ class ProviderRepository {
private final Map<String, FeatureProviderStateManager> stateManagers = new ConcurrentHashMap<>();
private final AtomicReference<FeatureProviderStateManager> defaultStateManger =
new AtomicReference<>(new FeatureProviderStateManager(new NoOpProvider()));
private final ExecutorService taskExecutor = Executors.newCachedThreadPool(runnable -> {
final Thread thread = new Thread(runnable);
thread.setDaemon(true);
return thread;
});
private final ExecutorService taskExecutor =
Executors.newCachedThreadPool(new ConfigurableThreadFactory("openfeature-provider-thread", true));
private final Object registerStateManagerLock = new Object();
private final OpenFeatureAPI openFeatureAPI;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package dev.openfeature.sdk.internal;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
* A configurable thread factory for internal use in the SDK.
* Allows daemon or non-daemon threads to be created with a custom name prefix.
*/
public final class ConfigurableThreadFactory implements ThreadFactory {

private final AtomicInteger counter = new AtomicInteger();
private final String namePrefix;
private final boolean daemon;

/**
* {@link ConfigurableThreadFactory}'s constructor.
*
* @param namePrefix Prefix used for setting the new thread's name.
*/
public ConfigurableThreadFactory(String namePrefix) {
this(namePrefix, false);
}

/**
* {@link ConfigurableThreadFactory}'s constructor.
*
* @param namePrefix Prefix used for setting the new thread's name.
* @param daemon Whether daemon or non-daemon threads will be created.
*/
public ConfigurableThreadFactory(String namePrefix, boolean daemon) {
this.namePrefix = namePrefix;
this.daemon = daemon;
}
Comment on lines +31 to +34
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For improved robustness, it's a good practice to validate constructor parameters. The namePrefix should not be null or empty to prevent creating threads with malformed names (e.g., -1). Adding a check will make this utility class safer to use in the future.

You could also add a corresponding test case to verify this behavior:

@Test
void shouldThrowOnNullOrEmptyPrefix() {
    assertThatThrownBy(() -> new ConfigurableThreadFactory(null))
        .isInstanceOf(IllegalArgumentException.class);
    assertThatThrownBy(() -> new ConfigurableThreadFactory(""))
        .isInstanceOf(IllegalArgumentException.class);
}
    public ConfigurableThreadFactory(String namePrefix, boolean daemon) {
        if (namePrefix == null || namePrefix.isEmpty()) {
            throw new IllegalArgumentException("Thread name prefix cannot be null or empty.");
        }
        this.namePrefix = namePrefix;
        this.daemon = daemon;
    }


@Override
public Thread newThread(Runnable runnable) {
final Thread thread = new Thread(runnable);
thread.setDaemon(daemon);
thread.setName(namePrefix + "-" + counter.incrementAndGet());
return thread;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dev.openfeature.sdk.internal;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

class ConfigurableThreadFactoryTest {

private static final String THREAD_NAME = "testthread";
private final Runnable runnable = () -> {};

@Test
void verifyNewThreadHasNamePrefix() {

var configurableThreadFactory = new ConfigurableThreadFactory(THREAD_NAME);
var thread = configurableThreadFactory.newThread(runnable);

assertThat(thread.getName()).isEqualTo(THREAD_NAME + "-1");
assertThat(thread.isDaemon()).isFalse();
}

@Test
void verifyNewThreadHasNamePrefixWithIncrement() {

var configurableThreadFactory = new ConfigurableThreadFactory(THREAD_NAME);
var threadOne = configurableThreadFactory.newThread(runnable);
var threadTwo = configurableThreadFactory.newThread(runnable);

assertThat(threadOne.getName()).isEqualTo(THREAD_NAME + "-1");
assertThat(threadTwo.getName()).isEqualTo(THREAD_NAME + "-2");
}

@Test
void verifyNewDaemonThreadHasNamePrefix() {

var configurableThreadFactory = new ConfigurableThreadFactory(THREAD_NAME, true);
var thread = configurableThreadFactory.newThread(runnable);

assertThat(thread.getName()).isEqualTo(THREAD_NAME + "-1");
assertThat(thread.isDaemon()).isTrue();
}
}
Loading