diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 1d173c6f3..ef6751640 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -38,6 +38,7 @@ New Features in 3.3.1:
Bugs Fixed in 3.3.1:
===================
* JEXL-415: Incorrect template eval result
+* JEXL-414: SoftCache may suffer from race conditions
* JEXL-412: Ambiguous syntax between namespace function call and map object definition.
* JEXL-410: JexlFeatures: ctor does not enable all features
* JEXL-409: Disable LEXICAL should disable LEXICAL_SHADE
diff --git a/pom.xml b/pom.xml
index 04f3cf3b8..482ba00b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -126,6 +126,7 @@
com.googlecode.concurrentlinkedhashmapconcurrentlinkedhashmap-lru1.4.2
+ test
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 80fc9107b..48c734d0b 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -45,6 +45,9 @@
Incorrect template eval result.
+
+ SoftCache may suffer from race conditions
+
Ambiguous syntax between namespace function call and map object definition.
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index b67640d75..babc66551 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -24,6 +24,7 @@
import java.util.function.IntFunction;
import org.apache.commons.jexl3.internal.Engine;
+import org.apache.commons.jexl3.internal.SoftCache;
import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.apache.commons.jexl3.introspection.JexlSandbox;
import org.apache.commons.jexl3.introspection.JexlUberspect;
@@ -134,7 +135,7 @@ public static void setDefaultPermissions(final JexlPermissions permissions) {
private int cache = -1;
/** The cache class factory. */
- private IntFunction> cacheFactory = JexlCache.createConcurrent();
+ private IntFunction> cacheFactory = SoftCache::new;
/** The stack overflow limit. */
private int stackOverflow = Integer.MAX_VALUE;
diff --git a/src/main/java/org/apache/commons/jexl3/JexlCache.java b/src/main/java/org/apache/commons/jexl3/JexlCache.java
index c7776e027..1b13c04fe 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlCache.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlCache.java
@@ -21,7 +21,6 @@
import java.util.Map;
import java.util.function.IntFunction;
-import org.apache.commons.jexl3.internal.ConcurrentCache;
import org.apache.commons.jexl3.internal.SoftCache;
/**
@@ -31,7 +30,14 @@
*/
public interface JexlCache {
/**
- * Returns the cache size.
+ * Returns the cache capacity, the maximum number of elements it can contain.
+ *
+ * @return the cache capacity
+ */
+ int capacity();
+
+ /**
+ * Returns the cache size, the actual number of elements it contains.
*
* @return the cache size
*/
@@ -69,18 +75,4 @@ public interface JexlCache {
default Collection> entries() {
return Collections.emptyList();
}
-
- /**
- * @return a synchronized cache factory amenable to low concurrency usage
- */
- static IntFunction> createSynchronized() {
- return SoftCache::new;
- }
-
- /**
- * @return a concurrent cache factory amenable to high concurrency usage
- */
- static IntFunction> createConcurrent() {
- return ConcurrentCache::new;
- }
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java b/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
index 24db61ac1..06d459888 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
@@ -17,12 +17,9 @@
package org.apache.commons.jexl3.internal;
import java.lang.ref.SoftReference;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import java.util.Map;
-import java.util.Set;
import org.apache.commons.jexl3.JexlCache;
@@ -34,8 +31,13 @@
*
*
* Note that the underlying map is a synchronized LinkedHashMap.
- * The reason is that a get() will reorder elements (the LRU queue) and thus
- * needs to be guarded to be thread-safe.
+ * The reason is that a get() will reorder elements (the LRU queue) and thus
+ * needs synchronization to ensure thread-safety.
+ *
+ *
+ * When caching JEXL scripts or expressions, one should expect the execution cost of those
+ * to be several fold the cost of the cache handling; after some (synthetic) tests, measures indicate
+ * cache handling is a marginal latency factor.
*
*
* @param the cache key entry type
@@ -45,11 +47,11 @@ public class SoftCache implements JexlCache {
/**
* The default cache load factor.
*/
- private static final float LOAD_FACTOR = 0.75f;
+ protected static final float LOAD_FACTOR = 0.75f;
/**
- * The cache size.
+ * The cache capacity.
*/
- protected final int size;
+ protected final int capacity;
/**
* The soft reference to the cache map.
*/
@@ -61,21 +63,29 @@ public class SoftCache implements JexlCache {
* @param theSize the cache size
*/
public SoftCache(final int theSize) {
- size = theSize;
+ capacity = theSize;
}
/**
- * Returns the cache size.
- *
- * @return the cache size
+ * {@inheritDoc}
+ */
+ @Override
+ public int capacity() {
+ return capacity;
+ }
+
+ /**
+ * {@inheritDoc}
*/
@Override
public int size() {
- return size;
+ final SoftReference