use concurrent data structure for hooks

Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>
This commit is contained in:
christian.lutnik 2025-06-18 10:42:35 +02:00
parent 37e89f716f
commit b7816ae45c
3 changed files with 26 additions and 37 deletions

View File

@ -5,10 +5,11 @@ import dev.openfeature.sdk.internal.AutoCloseableLock;
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
@ -23,14 +24,14 @@ import lombok.extern.slf4j.Slf4j;
public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
// package-private multi-read/single-write lock
static AutoCloseableReentrantReadWriteLock lock = new AutoCloseableReentrantReadWriteLock();
private final List<Hook> apiHooks;
private final ConcurrentLinkedQueue<Hook> apiHooks;
private ProviderRepository providerRepository;
private EventSupport eventSupport;
private final AtomicReference<EvaluationContext> evaluationContext = new AtomicReference<>();
private TransactionContextPropagator transactionContextPropagator;
protected OpenFeatureAPI() {
apiHooks = new ArrayList<>();
apiHooks = new ConcurrentLinkedQueue<>();
providerRepository = new ProviderRepository(this);
eventSupport = new EventSupport();
transactionContextPropagator = new NoOpTransactionContextPropagator();
@ -303,9 +304,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* @param hooks The hook to add.
*/
public void addHooks(Hook... hooks) {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
this.apiHooks.addAll(Arrays.asList(hooks));
}
this.apiHooks.addAll(Arrays.asList(hooks));
}
/**
@ -314,22 +313,23 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
* @return A list of {@link Hook}s.
*/
public List<Hook> getHooks() {
try (AutoCloseableLock ignored = lock.readLockAutoCloseable()) {
if (this.apiHooks.isEmpty()) {
return Collections.emptyList();
} else {
return new ArrayList<>(this.apiHooks);
}
}
return new ArrayList<>(this.apiHooks);
}
/**
* Returns a reference to the collection of {@link Hook}s.
*
* @return The collection of {@link Hook}s.
*/
Collection<Hook> getMutableHooks() {
return this.apiHooks;
}
/**
* Removes all hooks.
*/
public void clearHooks() {
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
this.apiHooks.clear();
}
this.apiHooks.clear();
}
/**

View File

@ -5,8 +5,6 @@ import dev.openfeature.sdk.exceptions.FatalError;
import dev.openfeature.sdk.exceptions.GeneralError;
import dev.openfeature.sdk.exceptions.OpenFeatureError;
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
import dev.openfeature.sdk.internal.AutoCloseableLock;
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
import dev.openfeature.sdk.internal.ObjectUtils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
@ -16,6 +14,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import lombok.Getter;
@ -48,9 +47,8 @@ public class OpenFeatureClient implements Client {
@Getter
private final String version;
private final List<Hook> clientHooks;
private final ConcurrentLinkedQueue<Hook> clientHooks;
private final HookSupport hookSupport;
AutoCloseableReentrantReadWriteLock hooksLock = new AutoCloseableReentrantReadWriteLock();
private final AtomicReference<EvaluationContext> evaluationContext = new AtomicReference<>();
/**
@ -69,7 +67,7 @@ public class OpenFeatureClient implements Client {
this.openfeatureApi = openFeatureAPI;
this.domain = domain;
this.version = version;
this.clientHooks = new ArrayList<>();
this.clientHooks = new ConcurrentLinkedQueue<>();
this.hookSupport = new HookSupport();
}
@ -126,9 +124,7 @@ public class OpenFeatureClient implements Client {
*/
@Override
public OpenFeatureClient addHooks(Hook... hooks) {
try (AutoCloseableLock ignored = this.hooksLock.writeLockAutoCloseable()) {
this.clientHooks.addAll(Arrays.asList(hooks));
}
this.clientHooks.addAll(Arrays.asList(hooks));
return this;
}
@ -137,13 +133,7 @@ public class OpenFeatureClient implements Client {
*/
@Override
public List<Hook> getHooks() {
try (AutoCloseableLock ignored = this.hooksLock.readLockAutoCloseable()) {
if (this.clientHooks.isEmpty()) {
return Collections.emptyList();
} else {
return new ArrayList<>(this.clientHooks);
}
}
return new ArrayList<>(this.clientHooks);
}
/**
@ -183,10 +173,8 @@ public class OpenFeatureClient implements Client {
var provider = stateManager.getProvider();
var state = stateManager.getState();
try (AutoCloseableLock ignored = this.hooksLock.readLockAutoCloseable()) {
mergedHooks = ObjectUtils.merge(
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getHooks());
}
mergedHooks = ObjectUtils.merge(
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getMutableHooks());
var mergedCtx = hookSupport.beforeHooks(
type,

View File

@ -1,6 +1,7 @@
package dev.openfeature.sdk.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
@ -64,9 +65,9 @@ public class ObjectUtils {
* @return resulting object
*/
@SafeVarargs
public static <T> List<T> merge(List<T>... sources) {
public static <T> List<T> merge(Collection<T>... sources) {
List<T> merged = new ArrayList<>();
for (List<T> source : sources) {
for (Collection<T> source : sources) {
merged.addAll(source);
}
return merged;