Skip to content

Commit bf48eec

Browse files
authored
Replace synchronization with a ReentrantReadWriteLock in ConfigCatHooks (#49)
The ConfigCat client is thread safe and largely scales evaluations across threads however, hook executions are all synchronized. This can seriously impact performance if the onFlagEvaluated hook performs any significant logic (even small 100s of microseconds) and could cause evaluations to hang while a potentially logic-filled onConfigChanged hook is executed. A ReentrantReadWriteLock resolves this by allowing multiple concurrent readers to iterate the hook lists.
1 parent 77e029f commit bf48eec

File tree

1 file changed

+38
-10
lines changed

1 file changed

+38
-10
lines changed

src/main/java/com/configcat/ConfigCatHooks.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import java.util.ArrayList;
44
import java.util.List;
55
import java.util.Map;
6+
import java.util.concurrent.locks.ReentrantReadWriteLock;
67
import java.util.function.Consumer;
78

89
public class ConfigCatHooks {
9-
private final Object sync = new Object();
10+
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
1011
private final List<Consumer<Map<String, Setting>>> onConfigChanged = new ArrayList<>();
1112
private final List<Runnable> onClientReady = new ArrayList<>();
1213
private final List<Consumer<EvaluationDetails<Object>>> onFlagEvaluated = new ArrayList<>();
@@ -22,8 +23,11 @@ public class ConfigCatHooks {
2223
* @param callback the method to call when the event fires.
2324
*/
2425
public void addOnClientReady(Runnable callback) {
25-
synchronized (sync) {
26+
lock.writeLock().lock();
27+
try {
2628
this.onClientReady.add(callback);
29+
} finally {
30+
lock.writeLock().unlock();
2731
}
2832
}
2933

@@ -34,8 +38,11 @@ public void addOnClientReady(Runnable callback) {
3438
* @param callback the method to call when the event fires.
3539
*/
3640
public void addOnConfigChanged(Consumer<Map<String, Setting>> callback) {
37-
synchronized (sync) {
41+
lock.writeLock().lock();
42+
try {
3843
this.onConfigChanged.add(callback);
44+
} finally {
45+
lock.writeLock().unlock();
3946
}
4047
}
4148

@@ -45,8 +52,11 @@ public void addOnConfigChanged(Consumer<Map<String, Setting>> callback) {
4552
* @param callback the method to call when the event fires.
4653
*/
4754
public void addOnError(Consumer<String> callback) {
48-
synchronized (sync) {
55+
lock.writeLock().lock();
56+
try {
4957
this.onError.add(callback);
58+
} finally {
59+
lock.writeLock().unlock();
5060
}
5161
}
5262

@@ -57,49 +67,67 @@ public void addOnError(Consumer<String> callback) {
5767
* @param callback the method to call when the event fires.
5868
*/
5969
public void addOnFlagEvaluated(Consumer<EvaluationDetails<Object>> callback) {
60-
synchronized (sync) {
70+
lock.writeLock().lock();
71+
try {
6172
this.onFlagEvaluated.add(callback);
73+
} finally {
74+
lock.writeLock().unlock();
6275
}
6376
}
6477

6578
void invokeOnClientReady() {
66-
synchronized (sync) {
79+
lock.readLock().lock();
80+
try {
6781
for (Runnable func : this.onClientReady) {
6882
func.run();
6983
}
84+
} finally {
85+
lock.readLock().unlock();
7086
}
7187
}
7288

7389
void invokeOnError(String error) {
74-
synchronized (sync) {
90+
lock.readLock().lock();
91+
try {
7592
for (Consumer<String> func : this.onError) {
7693
func.accept(error);
7794
}
95+
} finally {
96+
lock.readLock().unlock();
7897
}
7998
}
8099

81100
void invokeOnConfigChanged(Map<String, Setting> settingMap) {
82-
synchronized (sync) {
101+
lock.readLock().lock();
102+
try {
83103
for (Consumer<Map<String, Setting>> func : this.onConfigChanged) {
84104
func.accept(settingMap);
85105
}
106+
} finally {
107+
lock.readLock().unlock();
86108
}
87109
}
88110

89111
void invokeOnFlagEvaluated(EvaluationDetails<Object> evaluationDetails) {
90-
synchronized (sync) {
112+
lock.readLock().lock();
113+
try {
91114
for (Consumer<EvaluationDetails<Object>> func : this.onFlagEvaluated) {
92115
func.accept(evaluationDetails);
93116
}
117+
} finally {
118+
lock.readLock().unlock();
94119
}
95120
}
96121

97122
void clear() {
98-
synchronized (sync) {
123+
lock.writeLock().lock();
124+
try {
99125
this.onConfigChanged.clear();
100126
this.onError.clear();
101127
this.onFlagEvaluated.clear();
102128
this.onClientReady.clear();
129+
} finally {
130+
lock.writeLock().unlock();
103131
}
104132
}
105133
}

0 commit comments

Comments
 (0)