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) {

Reply via email to