[ 
https://issues.apache.org/jira/browse/POOL-411?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17696483#comment-17696483
 ] 

Gary D. Gregory edited comment on POOL-411 at 3/4/23 8:17 PM:
--------------------------------------------------------------

I thought I could reproduce it using the test below, but no:
{noformat}
diff --git 
a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java 
b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
index 9ec3f60..a074257 100644
--- 
a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
+++ 
b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
@@ -33,7 +33,9 @@
 import java.lang.management.ManagementFactory;
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Random;
@@ -41,6 +43,7 @@
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -976,7 +979,7 @@
 
         // Start and park threads waiting to borrow objects
         final TestThread[] threads = new TestThread[numThreads];
-        for(int i=0;i<numThreads;i++) {
+        for (int i = 0; i < numThreads; i++) {
             threads[i] = new TestThread<>(gkoPool, 1, 0, 2000, false, "0" + 
String.valueOf(i % maxTotal), "0");
             final Thread t = new Thread(threads[i]);
             t.start();
@@ -1005,6 +1008,69 @@
     }
 
     /**
+     * Tests POOL-411, or least tries to reproduce the NPE.
+     *
+     * @throws TestException 
+     */
+    @Test
+    public void testConcurrentBorrowAndClear() throws TestException {
+        final int threadCount = 64;
+        final int taskCount = 64;
+        final int addCount = 1;
+        final int borrowCycles = 1024;
+        final int clearCycles = 1024;
+
+        final GenericKeyedObjectPoolConfig<String> config = new 
GenericKeyedObjectPoolConfig<>();
+        final int maxTotalPerKey = borrowCycles + 1;
+        config.setMaxTotalPerKey(threadCount);
+        config.setMaxIdlePerKey(threadCount);
+        config.setMaxTotal(maxTotalPerKey * threadCount);
+        config.setBlockWhenExhausted(false); // pool exhausted indicates a bug 
in the test
+
+        gkoPool = new GenericKeyedObjectPool<>(simpleFactory, config);
+        gkoPool.addObjects(Arrays.asList("0"), threadCount);
+        // all objects in the pool are now idle.
+
+        final ExecutorService threadPool = 
Executors.newFixedThreadPool(threadCount);
+        final List<Future<?>> futures = new ArrayList<>();
+        try {
+            for (int t = 0; t < taskCount; t++) {
+                futures.add(threadPool.submit(() -> {
+                    for (int i = 0; i < clearCycles; i++) {
+                        Thread.yield();
+                        gkoPool.clear("0", true);
+                        try {
+                            gkoPool.addObjects(Arrays.asList("0"), addCount);
+                        } catch (IllegalArgumentException | TestException e) {
+                            fail(e);
+                        }
+                    }
+                }));
+                futures.add(threadPool.submit(() -> {
+                    try {
+                        for (int i = 0; i < borrowCycles; i++) {
+                            Thread.yield();
+                            final String pooled = gkoPool.borrowObject("0");
+                            gkoPool.returnObject("0", pooled);
+                        }
+                    } catch (TestException e) {
+                        fail(e);
+                    }
+                }));
+            }
+            futures.forEach(f -> {
+                try {
+                    f.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    fail(e);
+                }
+            });
+        } finally {
+            threadPool.shutdownNow();
+        }
+    }
+
+    /**
      * POOL-192
      * Verify that clear(key) does not leak capacity.
      *
{noformat}


was (Author: garydgregory):
I thought I could reproduce it like the test below, but no:
{noformat}
diff --git 
a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java 
b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
index 9ec3f60..a074257 100644
--- 
a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
+++ 
b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
@@ -33,7 +33,9 @@
 import java.lang.management.ManagementFactory;
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Random;
@@ -41,6 +43,7 @@
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -976,7 +979,7 @@
 
         // Start and park threads waiting to borrow objects
         final TestThread[] threads = new TestThread[numThreads];
-        for(int i=0;i<numThreads;i++) {
+        for (int i = 0; i < numThreads; i++) {
             threads[i] = new TestThread<>(gkoPool, 1, 0, 2000, false, "0" + 
String.valueOf(i % maxTotal), "0");
             final Thread t = new Thread(threads[i]);
             t.start();
@@ -1005,6 +1008,69 @@
     }
 
     /**
+     * Tests POOL-411, or least tries to reproduce the NPE.
+     *
+     * @throws TestException 
+     */
+    @Test
+    public void testConcurrentBorrowAndClear() throws TestException {
+        final int threadCount = 64;
+        final int taskCount = 64;
+        final int addCount = 1;
+        final int borrowCycles = 1024;
+        final int clearCycles = 1024;
+
+        final GenericKeyedObjectPoolConfig<String> config = new 
GenericKeyedObjectPoolConfig<>();
+        final int maxTotalPerKey = borrowCycles + 1;
+        config.setMaxTotalPerKey(threadCount);
+        config.setMaxIdlePerKey(threadCount);
+        config.setMaxTotal(maxTotalPerKey * threadCount);
+        config.setBlockWhenExhausted(false); // pool exhausted indicates a bug 
in the test
+
+        gkoPool = new GenericKeyedObjectPool<>(simpleFactory, config);
+        gkoPool.addObjects(Arrays.asList("0"), threadCount);
+        // all objects in the pool are now idle.
+
+        final ExecutorService threadPool = 
Executors.newFixedThreadPool(threadCount);
+        final List<Future<?>> futures = new ArrayList<>();
+        try {
+            for (int t = 0; t < taskCount; t++) {
+                futures.add(threadPool.submit(() -> {
+                    for (int i = 0; i < clearCycles; i++) {
+                        Thread.yield();
+                        gkoPool.clear("0", true);
+                        try {
+                            gkoPool.addObjects(Arrays.asList("0"), addCount);
+                        } catch (IllegalArgumentException | TestException e) {
+                            fail(e);
+                        }
+                    }
+                }));
+                futures.add(threadPool.submit(() -> {
+                    try {
+                        for (int i = 0; i < borrowCycles; i++) {
+                            Thread.yield();
+                            final String pooled = gkoPool.borrowObject("0");
+                            gkoPool.returnObject("0", pooled);
+                        }
+                    } catch (TestException e) {
+                        fail(e);
+                    }
+                }));
+            }
+            futures.forEach(f -> {
+                try {
+                    f.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    fail(e);
+                }
+            });
+        } finally {
+            threadPool.shutdownNow();
+        }
+    }
+
+    /**
      * POOL-192
      * Verify that clear(key) does not leak capacity.
      *
{noformat}

> Potential NPE when deregistering key at end of borrow
> -----------------------------------------------------
>
>                 Key: POOL-411
>                 URL: https://issues.apache.org/jira/browse/POOL-411
>             Project: Commons Pool
>          Issue Type: Task
>    Affects Versions: 2.11.1
>            Reporter: Richard Eckart de Castilho
>            Priority: Major
>
> There is a potential for an NPE happening in the finally block of 
> borrowObject:
> {noformat}
> Caused by: java.lang.NullPointerException: Cannot invoke 
> "org.apache.commons.pool2.impl.GenericKeyedObjectPool$ObjectDeque.getNumInterested()"
>  because "objectDeque" is null
>       at 
> org.apache.commons.pool2.impl.GenericKeyedObjectPool.deregister(GenericKeyedObjectPool.java:821)
>       at 
> org.apache.commons.pool2.impl.GenericKeyedObjectPool.borrowObject(GenericKeyedObjectPool.java:507)
>       at 
> org.apache.commons.pool2.impl.GenericKeyedObjectPool.borrowObject(GenericKeyedObjectPool.java:350)
>  
> {noformat}
> From reading the code, it seems this could happen e.g. if a pool is 
> concurrently cleared while a borrow is in progress.
> Not sure what a proper solution here would be. Maybe deregister should 
> silently do nothing if poolMap.get(k) returns null? 



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to