View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.pool2;
19  
20  import java.time.Duration;
21  import java.time.Instant;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.NoSuchElementException;
25  import java.util.concurrent.Callable;
26  import java.util.concurrent.ExecutionException;
27  import java.util.concurrent.ExecutorService;
28  import java.util.concurrent.Executors;
29  import java.util.concurrent.Future;
30  
31  import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
32  import org.apache.commons.pool2.impl.DefaultPooledObject;
33  import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
34  import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
35  
36  /**
37   * On my box with 4 cores this test fails at between 5s and 900s with an average
38   * of 240s (data from 10 runs of test).
39   *
40   * It is hard to turn this in a unit test because it can affect the build
41   * negatively since you need to run it for a while.
42   */
43  public final class ObjectPoolIssue326 {
44      private static class ObjectFactory extends BaseKeyedPooledObjectFactory<Integer, Object> {
45          @Override
46          public Object create(final Integer s) {
47              return new TestObject();
48          }
49  
50          @Override
51          public PooledObject<Object> wrap(final Object o) {
52              return new DefaultPooledObject<>(o);
53          }
54      }
55  
56      private static class Task<E extends Exception> implements Callable<Object> {
57          private final GenericKeyedObjectPool<Integer, Object> m_pool;
58          private final int m_key;
59  
60          Task(final GenericKeyedObjectPool<Integer, Object> pool, final int count) {
61              m_pool = pool;
62              m_key = count % 20;
63          }
64  
65          private void busyWait(final long timeMillis) {
66              // busy waiting intentionally as a simple thread.sleep fails to reproduce
67              final long endTimeMillis = System.currentTimeMillis() + timeMillis;
68              while (System.currentTimeMillis() < endTimeMillis) {
69                  // empty
70              }
71          }
72  
73          @Override
74          public Object call() throws Exception {
75              try {
76                  final Object value;
77                  value = m_pool.borrowObject(m_key);
78                  // don't make this too long or it won't reproduce, and don't make it zero or it
79                  // won't reproduce
80                  // constant low value also doesn't reproduce
81                  busyWait(System.currentTimeMillis() % 4);
82                  m_pool.returnObject(m_key, value);
83                  return "success";
84              } catch (final NoSuchElementException e) {
85                  // ignore, we've exhausted the pool
86                  // not sure whether what we do here matters for reproducing
87                  busyWait(System.currentTimeMillis() % 20);
88                  return "exhausted";
89              }
90          }
91      }
92  
93      private static class TestObject {
94      }
95  
96      public static void main(final String[] args) {
97          try {
98              new ObjectPoolIssue326().run();
99          } catch (final Exception e) {
100             e.printStackTrace();
101         }
102     }
103 
104     private <E extends Exception> List<Task<E>> createTasks(final GenericKeyedObjectPool<Integer, Object> pool) {
105         final List<Task<E>> tasks = new ArrayList<>();
106         for (int i = 0; i < 250; i++) {
107             tasks.add(new Task<>(pool, i));
108         }
109         return tasks;
110     }
111 
112     private void run() throws InterruptedException, ExecutionException {
113         final GenericKeyedObjectPoolConfig<Object> poolConfig = new GenericKeyedObjectPoolConfig<>();
114         poolConfig.setMaxTotal(10);
115         poolConfig.setMaxTotalPerKey(5);
116         poolConfig.setMinIdlePerKey(-1);
117         poolConfig.setMaxIdlePerKey(-1);
118         poolConfig.setLifo(true);
119         poolConfig.setFairness(true);
120         poolConfig.setMaxWait(Duration.ofSeconds(30));
121         poolConfig.setMinEvictableIdleDuration(Duration.ofMillis(-1));
122         poolConfig.setMinEvictableIdleTime(Duration.ofMillis(-1));
123         poolConfig.setSoftMinEvictableIdleDuration(Duration.ofMillis(-1));
124         poolConfig.setSoftMinEvictableIdleTime(Duration.ofMillis(-1));
125         poolConfig.setNumTestsPerEvictionRun(1);
126         poolConfig.setTestOnCreate(false);
127         poolConfig.setTestOnBorrow(false);
128         poolConfig.setTestOnReturn(false);
129         poolConfig.setTestWhileIdle(false);
130         poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(5));
131         poolConfig.setEvictionPolicyClassName(BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME);
132         poolConfig.setBlockWhenExhausted(false);
133         poolConfig.setJmxEnabled(false);
134         poolConfig.setJmxNameBase(null);
135         poolConfig.setJmxNamePrefix(null);
136 
137         final GenericKeyedObjectPool<Integer, Object> pool = new GenericKeyedObjectPool<>(new ObjectFactory(), poolConfig);
138 
139         // number of threads to reproduce is finicky. this count seems to be best for my
140         // 4 core box.
141         // too many doesn't reproduce it ever, too few doesn't either.
142         final ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
143         final Instant startTime = Instant.now();
144         long testIter = 0;
145         try {
146             while (true) {
147                 testIter++;
148                 if (testIter % 1000 == 0) {
149                     System.out.println(testIter);
150                 }
151                 final List<Task<RuntimeException>> tasks = createTasks(pool);
152                 final List<Future<Object>> futures = service.invokeAll(tasks);
153                 for (final Future<Object> future : futures) {
154                     future.get();
155                 }
156             }
157         } finally {
158             System.out.println("Time: " + Duration.between(startTime, Instant.now()));
159             service.shutdown();
160         }
161     }
162 }
163 
164 /*
165  * Example stack trace: java.util.concurrent.ExecutionException:
166  * java.lang.NullPointerException at
167  * java.util.concurrent.FutureTask.report(FutureTask.java:122) at
168  * java.util.concurrent.FutureTask.get(FutureTask.java:192) at
169  * threading_pool.ObjectPoolIssue.run(ObjectPoolIssue.java:63) at
170  * threading_pool.ObjectPoolIssue.main(ObjectPoolIssue.java:23) at
171  * sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
172  * sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
173  * at
174  * sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.
175  * java:43) at java.lang.reflect.Method.invoke(Method.java:498) at
176  * com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) Caused
177  * by: java.lang.NullPointerException at
178  * org.apache.commons.pool2.impl.GenericKeyedObjectPool.returnObject(
179  * GenericKeyedObjectPool.java:474) at
180  * threading_pool.ObjectPoolIssue$Task.call(ObjectPoolIssue.java:112) at
181  * java.util.concurrent.FutureTask.run(FutureTask.java:266) at
182  * java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:
183  * 1142) at
184  * java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:
185  * 617) at java.lang.Thread.run(Thread.java:745)
186  */