Skip to content

Commit d5e854a

Browse files
committed
fix: add read/write locks to client/api
Signed-off-by: Todd Baert <[email protected]>
1 parent da7a662 commit d5e854a

File tree

4 files changed

+88
-21
lines changed

4 files changed

+88
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
11
package dev.openfeature.sdk;
22

3-
import lombok.Getter;
4-
import lombok.Setter;
5-
6-
import javax.annotation.Nullable;
73
import java.util.ArrayList;
84
import java.util.Arrays;
95
import java.util.List;
106

7+
import javax.annotation.Nullable;
8+
9+
import dev.openfeature.sdk.internal.AutoCloseableLock;
10+
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
11+
import lombok.Getter;
12+
import lombok.Setter;
13+
1114
/**
1215
* A global singleton which holds base configuration for the OpenFeature library.
1316
* Configuration here will be shared across all {@link Client}s.
1417
*/
1518
public class OpenFeatureAPI {
16-
private static OpenFeatureAPI api;
19+
// package-private multi-read/single-write lock
20+
static AutoCloseableReentrantReadWriteLock rwLock = new AutoCloseableReentrantReadWriteLock();
1721
@Getter
18-
@Setter
1922
private FeatureProvider provider;
2023
@Getter
2124
@Setter
2225
private EvaluationContext evaluationContext;
2326
@Getter
2427
private List<Hook> apiHooks;
2528

26-
public OpenFeatureAPI() {
29+
private OpenFeatureAPI() {
2730
this.apiHooks = new ArrayList<>();
2831
}
2932

33+
private static class SingletonHolder {
34+
private static final OpenFeatureAPI INSTANCE = new OpenFeatureAPI();
35+
}
36+
3037
/**
3138
* Provisions the {@link OpenFeatureAPI} singleton (if needed) and returns it.
3239
* @return The singleton instance.
3340
*/
3441
public static OpenFeatureAPI getInstance() {
35-
synchronized (OpenFeatureAPI.class) {
36-
if (api == null) {
37-
api = new OpenFeatureAPI();
38-
}
39-
}
40-
return api;
42+
return SingletonHolder.INSTANCE;
4143
}
4244

4345
public Metadata getProviderMetadata() {
@@ -56,11 +58,30 @@ public Client getClient(@Nullable String name, @Nullable String version) {
5658
return new OpenFeatureClient(this, name, version);
5759
}
5860

61+
/**
62+
* {@inheritDoc}
63+
*/
64+
public void setProvider(FeatureProvider provider) {
65+
try (AutoCloseableLock __ = rwLock.writeLockAutoCloseable()) {
66+
this.provider = provider;
67+
}
68+
}
69+
70+
/**
71+
* {@inheritDoc}
72+
*/
5973
public void addHooks(Hook... hooks) {
60-
this.apiHooks.addAll(Arrays.asList(hooks));
74+
try (AutoCloseableLock __ = rwLock.writeLockAutoCloseable()) {
75+
this.apiHooks.addAll(Arrays.asList(hooks));
76+
}
6177
}
6278

79+
/**
80+
* {@inheritDoc}
81+
*/
6382
public void clearHooks() {
64-
this.apiHooks.clear();
83+
try (AutoCloseableLock __ = rwLock.writeLockAutoCloseable()) {
84+
this.apiHooks.clear();
85+
}
6586
}
6687
}

src/main/java/dev/openfeature/sdk/OpenFeatureClient.java

+14-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import dev.openfeature.sdk.exceptions.GeneralError;
1010
import dev.openfeature.sdk.exceptions.OpenFeatureError;
11+
import dev.openfeature.sdk.internal.AutoCloseableLock;
12+
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
1113
import dev.openfeature.sdk.internal.ObjectUtils;
1214
import lombok.Getter;
1315
import lombok.Setter;
@@ -25,6 +27,7 @@ public class OpenFeatureClient implements Client {
2527
@Getter
2628
private final List<Hook> clientHooks;
2729
private final HookSupport hookSupport;
30+
private AutoCloseableReentrantReadWriteLock rwLock = new AutoCloseableReentrantReadWriteLock();
2831

2932
@Getter
3033
@Setter
@@ -48,7 +51,9 @@ public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String name, String vers
4851

4952
@Override
5053
public void addHooks(Hook... hooks) {
51-
this.clientHooks.addAll(Arrays.asList(hooks));
54+
try (AutoCloseableLock __ = this.rwLock.writeLockAutoCloseable()) {
55+
this.clientHooks.addAll(Arrays.asList(hooks));
56+
}
5257
}
5358

5459
private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key, T defaultValue,
@@ -57,16 +62,19 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
5762
() -> FlagEvaluationOptions.builder().build());
5863
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
5964
ctx = ObjectUtils.defaultIfNull(ctx, () -> new MutableContext());
60-
FeatureProvider provider = ObjectUtils.defaultIfNull(openfeatureApi.getProvider(), () -> {
61-
log.debug("No provider configured, using no-op provider.");
62-
return new NoOpProvider();
63-
});
65+
6466

6567
FlagEvaluationDetails<T> details = null;
6668
List<Hook> mergedHooks = null;
6769
HookContext<T> hookCtx = null;
6870

69-
try {
71+
try (AutoCloseableLock __ = OpenFeatureAPI.rwLock.readLockAutoCloseable();
72+
AutoCloseableLock ___ = this.rwLock.readLockAutoCloseable()) {
73+
74+
FeatureProvider provider = ObjectUtils.defaultIfNull(openfeatureApi.getProvider(), () -> {
75+
log.debug("No provider configured, using no-op provider.");
76+
return new NoOpProvider();
77+
});
7078

7179
hookCtx = HookContext.from(key, type, this.getMetadata(),
7280
openfeatureApi.getProvider().getMetadata(), ctx, defaultValue);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.openfeature.sdk.internal;
2+
3+
public interface AutoCloseableLock extends AutoCloseable {
4+
5+
/**
6+
* Override the exception in AutoClosable.
7+
*/
8+
@Override
9+
void close();
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.openfeature.sdk.internal;
2+
3+
import java.util.concurrent.locks.ReentrantReadWriteLock;
4+
5+
/**
6+
* A utility class that wraps a multi-read/single-write lock construct as AutoCloseable, so it can
7+
* be used in a try-with-resources.
8+
*/
9+
public class AutoCloseableReentrantReadWriteLock extends ReentrantReadWriteLock {
10+
11+
/**
12+
* Get the single write lock as an AutoCloseableLock.
13+
* @return unlock method ref
14+
*/
15+
public AutoCloseableLock writeLockAutoCloseable() {
16+
this.writeLock().lock();
17+
return this.writeLock()::unlock;
18+
}
19+
20+
/**
21+
* Get the multi read lock as an AutoCloseableLock.
22+
* @return unlock method ref
23+
*/
24+
public AutoCloseableLock readLockAutoCloseable() {
25+
this.readLock().lock();
26+
return this.readLock()::unlock;
27+
}
28+
}

0 commit comments

Comments
 (0)