diff --git a/src/main/java/dev/openfeature/sdk/EventProvider.java b/src/main/java/dev/openfeature/sdk/EventProvider.java index 0d7e897c2..4ccac184e 100644 --- a/src/main/java/dev/openfeature/sdk/EventProvider.java +++ b/src/main/java/dev/openfeature/sdk/EventProvider.java @@ -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; @@ -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; diff --git a/src/main/java/dev/openfeature/sdk/EventSupport.java b/src/main/java/dev/openfeature/sdk/EventSupport.java index 8396795bd..0b446c6b2 100644 --- a/src/main/java/dev/openfeature/sdk/EventSupport.java +++ b/src/main/java/dev/openfeature/sdk/EventSupport.java @@ -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; @@ -26,7 +27,8 @@ class EventSupport { private static final String DEFAULT_CLIENT_UUID = UUID.randomUUID().toString(); private final Map 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. diff --git a/src/main/java/dev/openfeature/sdk/ProviderRepository.java b/src/main/java/dev/openfeature/sdk/ProviderRepository.java index ab024a750..147074a58 100644 --- a/src/main/java/dev/openfeature/sdk/ProviderRepository.java +++ b/src/main/java/dev/openfeature/sdk/ProviderRepository.java @@ -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; @@ -22,11 +23,8 @@ class ProviderRepository { private final Map stateManagers = new ConcurrentHashMap<>(); private final AtomicReference 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; diff --git a/src/main/java/dev/openfeature/sdk/internal/ConfigurableThreadFactory.java b/src/main/java/dev/openfeature/sdk/internal/ConfigurableThreadFactory.java new file mode 100644 index 000000000..8d5e77db8 --- /dev/null +++ b/src/main/java/dev/openfeature/sdk/internal/ConfigurableThreadFactory.java @@ -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; + } + + @Override + public Thread newThread(Runnable runnable) { + final Thread thread = new Thread(runnable); + thread.setDaemon(daemon); + thread.setName(namePrefix + "-" + counter.incrementAndGet()); + return thread; + } +} diff --git a/src/test/java/dev/openfeature/sdk/internal/ConfigurableThreadFactoryTest.java b/src/test/java/dev/openfeature/sdk/internal/ConfigurableThreadFactoryTest.java new file mode 100644 index 000000000..0de360ae6 --- /dev/null +++ b/src/test/java/dev/openfeature/sdk/internal/ConfigurableThreadFactoryTest.java @@ -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(); + } +}