diff --git a/CHANGES.txt b/CHANGES.txt
index 888b21421..ec4d34a39 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,6 @@
+4.11.1 (Feb 29, 2024)
+- Fixed deadlock in UniqueKeysTracker when sending Unique Keys.
+
4.11.0 (Jan 9, 2024)
- Added impressionsListener method in the IntegrationConfig builder to set Sync or Async Listener execution.
- Fixed localhost to read files with yml ending.
diff --git a/client/pom.xml b/client/pom.xml
index 0b4f93bd7..131fe248c 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -5,7 +5,7 @@
io.split.client
java-client-parent
- 4.11.0
+ 4.11.1
java-client
jar
diff --git a/client/src/main/java/io/split/client/impressions/UniqueKeysTrackerImp.java b/client/src/main/java/io/split/client/impressions/UniqueKeysTrackerImp.java
index b6694a9be..bd2ff9185 100644
--- a/client/src/main/java/io/split/client/impressions/UniqueKeysTrackerImp.java
+++ b/client/src/main/java/io/split/client/impressions/UniqueKeysTrackerImp.java
@@ -14,10 +14,13 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
public class UniqueKeysTrackerImp implements UniqueKeysTracker{
private static final Logger _log = LoggerFactory.getLogger(UniqueKeysTrackerImp.class);
@@ -31,6 +34,7 @@ public class UniqueKeysTrackerImp implements UniqueKeysTracker{
private final ConcurrentHashMap> uniqueKeysTracker;
private final int _uniqueKeysRefreshRate;
private final int _filterRefreshRate;
+ private final AtomicBoolean sendGuard = new AtomicBoolean(false);
private static final Logger _logger = LoggerFactory.getLogger(UniqueKeysTrackerImp.class);
public UniqueKeysTrackerImp(TelemetrySynchronizer telemetrySynchronizer, int uniqueKeysRefreshRate, int filterRefreshRate,
@@ -46,19 +50,19 @@ public UniqueKeysTrackerImp(TelemetrySynchronizer telemetrySynchronizer, int uni
}
@Override
- public synchronized boolean track(String featureFlagName, String key) {
+ public boolean track(String featureFlagName, String key) {
if (!filterAdapter.add(featureFlagName, key)) {
_logger.debug("The feature flag " + featureFlagName + " and key " + key + " exist in the UniqueKeysTracker");
return false;
}
- HashSet value = new HashSet<>();
- if(uniqueKeysTracker.containsKey(featureFlagName)){
- value = uniqueKeysTracker.get(featureFlagName);
- }
- value.add(key);
- uniqueKeysTracker.put(featureFlagName, value);
+ uniqueKeysTracker.compute(featureFlagName,
+ (feature, current) -> {
+ HashSet keysByFeature = Optional.ofNullable(current).orElse(new HashSet<>());
+ keysByFeature.add(key);
+ return keysByFeature;
+ });
_logger.debug("The feature flag " + featureFlagName + " and key " + key + " was added");
- if (uniqueKeysTracker.size() == MAX_AMOUNT_OF_TRACKED_UNIQUE_KEYS){
+ if (uniqueKeysTracker.size() >= MAX_AMOUNT_OF_TRACKED_UNIQUE_KEYS){
_logger.warn("The UniqueKeysTracker size reached the maximum limit");
try {
sendUniqueKeys();
@@ -107,17 +111,25 @@ public HashMap> popAll(){
}
private void sendUniqueKeys(){
- if (uniqueKeysTracker.size() == 0) {
- _log.warn("The Unique Keys Tracker is empty");
- return;
+ if (!sendGuard.compareAndSet(false, true)) {
+ _log.debug("SendUniqueKeys already running");
+ return;
}
- HashMap> uniqueKeysHashMap = popAll();
- List uniqueKeysFromPopAll = new ArrayList<>();
- for (String featureFlag : uniqueKeysHashMap.keySet()) {
- UniqueKeys.UniqueKey uniqueKey = new UniqueKeys.UniqueKey(featureFlag, new ArrayList<>(uniqueKeysHashMap.get(featureFlag)));
- uniqueKeysFromPopAll.add(uniqueKey);
+ try {
+ if (uniqueKeysTracker.size() == 0) {
+ _log.warn("The Unique Keys Tracker is empty");
+ return;
+ }
+ HashMap> uniqueKeysHashMap = popAll();
+ List uniqueKeysFromPopAll = new ArrayList<>();
+ for (Map.Entry> uniqueKeyEntry : uniqueKeysHashMap.entrySet()) {
+ UniqueKeys.UniqueKey uniqueKey = new UniqueKeys.UniqueKey(uniqueKeyEntry.getKey(), new ArrayList<>(uniqueKeyEntry.getValue()));
+ uniqueKeysFromPopAll.add(uniqueKey);
+ }
+ _telemetrySynchronizer.synchronizeUniqueKeys(new UniqueKeys(uniqueKeysFromPopAll));
+ } finally {
+ sendGuard.set(false);
}
- _telemetrySynchronizer.synchronizeUniqueKeys(new UniqueKeys(uniqueKeysFromPopAll));
}
private interface ExecuteUniqueKeysAction{
@@ -138,4 +150,8 @@ public void execute() {
sendUniqueKeys();
}
}
+
+ public AtomicBoolean getSendGuard() {
+ return sendGuard;
+ }
}
diff --git a/client/src/main/java/io/split/client/impressions/filters/BloomFilterImp.java b/client/src/main/java/io/split/client/impressions/filters/BloomFilterImp.java
index 554964d16..9bf82f81e 100644
--- a/client/src/main/java/io/split/client/impressions/filters/BloomFilterImp.java
+++ b/client/src/main/java/io/split/client/impressions/filters/BloomFilterImp.java
@@ -6,7 +6,7 @@
public class BloomFilterImp implements Filter {
- private BloomFilter bloomFilter;
+ private BloomFilter bloomFilter;
private final int size;
private final double errorMargin;
@@ -17,17 +17,17 @@ public BloomFilterImp(int size, double errorMargin) {
}
@Override
- public synchronized boolean add(String data) {
+ public boolean add(String data) {
return bloomFilter.put(data);
}
@Override
- public synchronized boolean contains(String data) {
+ public boolean contains(String data) {
return bloomFilter.mightContain(data);
}
@Override
- public synchronized void clear() {
+ public void clear() {
bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_16), size, errorMargin);
}
diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java
index dcebbc100..4f5ce3714 100644
--- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java
+++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java
@@ -29,7 +29,7 @@
import static io.split.client.utils.SplitExecutorFactory.buildExecutorService;
public class SyncManagerImp implements SyncManager {
- private static final Logger _log = LoggerFactory.getLogger(SyncManager.class);
+ private static final Logger _log = LoggerFactory.getLogger(SyncManagerImp.class);
private final AtomicBoolean _streamingEnabledConfig;
private final Synchronizer _synchronizer;
diff --git a/client/src/main/java/io/split/engine/sse/AuthApiClientImp.java b/client/src/main/java/io/split/engine/sse/AuthApiClientImp.java
index 4f39baa6c..9f750ada3 100644
--- a/client/src/main/java/io/split/engine/sse/AuthApiClientImp.java
+++ b/client/src/main/java/io/split/engine/sse/AuthApiClientImp.java
@@ -22,7 +22,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
public class AuthApiClientImp implements AuthApiClient {
- private static final Logger _log = LoggerFactory.getLogger(AuthApiClient.class);
+ private static final Logger _log = LoggerFactory.getLogger(AuthApiClientImp.class);
private final CloseableHttpClient _httpClient;
private final String _target;
diff --git a/client/src/test/java/io/split/client/impressions/UniqueKeysTrackerImpTest.java b/client/src/test/java/io/split/client/impressions/UniqueKeysTrackerImpTest.java
index 5edd8e531..a15940110 100644
--- a/client/src/test/java/io/split/client/impressions/UniqueKeysTrackerImpTest.java
+++ b/client/src/test/java/io/split/client/impressions/UniqueKeysTrackerImpTest.java
@@ -90,6 +90,7 @@ public void testStopSynchronization() throws Exception {
TelemetrySynchronizer telemetrySynchronizer = Mockito.mock(TelemetryInMemorySubmitter.class);
UniqueKeysTrackerImp uniqueKeysTrackerImp = new UniqueKeysTrackerImp(telemetrySynchronizer, 1, 2, null);
uniqueKeysTrackerImp.start();
+ Assert.assertFalse(uniqueKeysTrackerImp.getSendGuard().get());
Assert.assertTrue(uniqueKeysTrackerImp.track("feature1","key1"));
Assert.assertTrue(uniqueKeysTrackerImp.track("feature1","key2"));
Assert.assertTrue(uniqueKeysTrackerImp.track("feature2","key3"));
diff --git a/pluggable-storage/pom.xml b/pluggable-storage/pom.xml
index 0d6bd2656..b797e50fe 100644
--- a/pluggable-storage/pom.xml
+++ b/pluggable-storage/pom.xml
@@ -6,7 +6,7 @@
java-client-parent
io.split.client
- 4.11.0
+ 4.11.1
2.1.0
diff --git a/pom.xml b/pom.xml
index d186fdd63..4c5c2871b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
io.split.client
java-client-parent
- 4.11.0
+ 4.11.1
diff --git a/redis-wrapper/pom.xml b/redis-wrapper/pom.xml
index f33f560a7..2f9da5c8c 100644
--- a/redis-wrapper/pom.xml
+++ b/redis-wrapper/pom.xml
@@ -6,7 +6,7 @@
java-client-parent
io.split.client
- 4.11.0
+ 4.11.1
redis-wrapper
3.1.0
diff --git a/testing/pom.xml b/testing/pom.xml
index a7f75d1bc..d3f815e27 100644
--- a/testing/pom.xml
+++ b/testing/pom.xml
@@ -5,7 +5,7 @@
io.split.client
java-client-parent
- 4.11.0
+ 4.11.1
java-client-testing
jar