This is an automated email from the ASF dual-hosted git repository.
aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-math.git
The following commit(s) were added to refs/heads/master by this push:
new ee9e9bedc MATH-1694: Fix racy Jacobi coefficient cache in
PolynomialsUtils (#340)
ee9e9bedc is described below
commit ee9e9bedc1b95c03772961c83404fef9f935e2b2
Author: insaf021 <[email protected]>
AuthorDate: Thu Oct 29 14:13:17 2026 +0530
MATH-1694: Fix racy Jacobi coefficient cache in PolynomialsUtils (#340)
BaseRuleFactory.getRule read and updated the pointsAndWeightsDouble
TreeMap without holding the lock used by getRuleInternal. Factory
instances are shared (IterativeLegendreGaussIntegrator keeps a static
GaussIntegratorFactory), so concurrent requests for different rules
could corrupt the map and leave callers spinning in TreeMap.getEntry.
PolynomialsUtils.createJacobiPolynomial published the coefficient list
for a new (v, w) key before seeding it and then read it outside the
lock guarding the seeding, so a racing caller could extend a partial
list and return a wrong polynomial. buildPolynomial also read the
cached coefficients after releasing the list lock.
---
.../analysis/polynomials/PolynomialsUtils.java | 60 +++++++++++-----------
.../analysis/polynomials/PolynomialsUtilsTest.java | 39 ++++++++++++++
2 files changed, 68 insertions(+), 31 deletions(-)
diff --git
a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtils.java
b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtils.java
index 01648b615..552dbb94f 100644
---
a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtils.java
+++
b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtils.java
@@ -17,9 +17,9 @@
package org.apache.commons.math4.legacy.analysis.polynomials;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.numbers.fraction.BigFraction;
import org.apache.commons.numbers.combinatorics.BinomialCoefficient;
@@ -82,7 +82,7 @@ public final class PolynomialsUtils {
LEGENDRE_COEFFICIENTS.add(BigFraction.ONE);
// initialize map for Jacobi polynomials
- JACOBI_COEFFICIENTS = new HashMap<>();
+ JACOBI_COEFFICIENTS = new ConcurrentHashMap<>();
}
/**
@@ -228,24 +228,21 @@ public final class PolynomialsUtils {
// select the appropriate list
final JacobiKey key = new JacobiKey(v, w);
- if (!JACOBI_COEFFICIENTS.containsKey(key)) {
- synchronized(JACOBI_COEFFICIENTS) {
- if (!JACOBI_COEFFICIENTS.containsKey(key)) {
- // allocate a new list for v, w
- final List<BigFraction> list = new ArrayList<>();
- JACOBI_COEFFICIENTS.put(key, list);
+ final List<BigFraction> coefficients =
JACOBI_COEFFICIENTS.computeIfAbsent(key, k -> {
+ // allocate a new list for v, w
+ final List<BigFraction> list = new ArrayList<>();
- // Pv,w,0(x) = 1;
- list.add(BigFraction.ONE);
+ // Pv,w,0(x) = 1;
+ list.add(BigFraction.ONE);
- // P1(x) = (v - w) / 2 + (2 + v + w) * X / 2
- list.add(BigFraction.of(v - w, 2));
- list.add(BigFraction.of(2 + v + w, 2));
- }
- }
- }
+ // P1(x) = (v - w) / 2 + (2 + v + w) * X / 2
+ list.add(BigFraction.of(v - w, 2));
+ list.add(BigFraction.of(2 + v + w, 2));
+
+ return list;
+ });
- return buildPolynomial(degree, JACOBI_COEFFICIENTS.get(key),
+ return buildPolynomial(degree, coefficients,
new RecurrenceCoefficientsGenerator() {
/** {@inheritDoc} */
@Override
@@ -368,26 +365,27 @@ public final class PolynomialsUtils {
// Synchronizing on a method parameter is not safe; however, in this
// case, the lock object is an immutable field that belongs to this
// class.
+ final double[] a;
synchronized (coefficients) {
final int maxDegree = (int) JdkMath.floor(JdkMath.sqrt(2.0 *
coefficients.size())) - 1;
if (degree > maxDegree) {
computeUpToDegree(degree, maxDegree, generator, coefficients);
}
- }
- // coefficient for polynomial 0 is l [0]
- // coefficients for polynomial 1 are l [1] ... l [2] (degrees 0 ... 1)
- // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0 ... 2)
- // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0 ... 3)
- // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0 ... 4)
- // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0 ... 5)
- // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0 ... 6)
- // ...
- final int start = degree * (degree + 1) / 2;
-
- final double[] a = new double[degree + 1];
- for (int i = 0; i <= degree; ++i) {
- a[i] = coefficients.get(start + i).doubleValue();
+ // coefficient for polynomial 0 is l [0]
+ // coefficients for polynomial 1 are l [1] ... l [2] (degrees 0
... 1)
+ // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0
... 2)
+ // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0
... 3)
+ // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0
... 4)
+ // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0
... 5)
+ // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0
... 6)
+ // ...
+ final int start = degree * (degree + 1) / 2;
+
+ a = new double[degree + 1];
+ for (int i = 0; i <= degree; ++i) {
+ a[i] = coefficients.get(start + i).doubleValue();
+ }
}
// build the polynomial
diff --git
a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtilsTest.java
b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtilsTest.java
index d6f4014f7..1f6732c6f 100644
---
a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtilsTest.java
+++
b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtilsTest.java
@@ -16,6 +16,13 @@
*/
package org.apache.commons.math4.legacy.analysis.polynomials;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
import
org.apache.commons.math4.legacy.analysis.integration.IterativeLegendreGaussIntegrator;
import org.apache.commons.numbers.combinatorics.BinomialCoefficient;
@@ -291,6 +298,38 @@ public class PolynomialsUtilsTest {
}
}
+ @Test
+ public void testJacobiConcurrentCreation() throws Exception {
+ final int numThreads = 8;
+ final ExecutorService exec = Executors.newFixedThreadPool(numThreads);
+ try {
+ // Exponents not used elsewhere, so that every (v, w) list is
+ // created while the threads are racing.
+ for (int v = 20; v < 40; ++v) {
+ for (int w = 20; w < 40; ++w) {
+ final int fv = v;
+ final int fw = w;
+ final CyclicBarrier barrier = new
CyclicBarrier(numThreads);
+ final List<Future<Double>> results = new ArrayList<>();
+ for (int t = 0; t < numThreads; ++t) {
+ final int degree = 1 + t % 4;
+ results.add(exec.submit(() -> {
+ barrier.await();
+ return
PolynomialsUtils.createJacobiPolynomial(degree, fv, fw).value(1.0);
+ }));
+ }
+ for (int t = 0; t < numThreads; ++t) {
+ final int degree = 1 + t % 4;
+ final double binomial = BinomialCoefficient.value(v +
degree, degree);
+ Assert.assertTrue(Precision.equals(binomial,
results.get(t).get(), 1));
+ }
+ }
+ }
+ } finally {
+ exec.shutdownNow();
+ }
+ }
+
@Test
public void testJacobiOrthogonality() {
for (int v = 0; v < 5; ++v) {