blob: 09511e80844a220ff3eec5120c598633f89f29c2 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.pool3;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.pool3.impl.BaseObjectPoolConfig;
import org.apache.commons.pool3.impl.DefaultPooledObject;
import org.apache.commons.pool3.impl.GenericKeyedObjectPool;
import org.apache.commons.pool3.impl.GenericKeyedObjectPoolConfig;
/**
* On my box with 4 cores this test fails at between 5s and 900s with an average
* of 240s (data from 10 runs of test).
*
* It is hard to turn this in a unit test because it can affect the build
* negatively since you need to run it for a while.
*/
public final class ObjectPoolIssue326 {
private static final class ObjectFactory extends BaseKeyedPooledObjectFactory<Integer, Object, RuntimeException> {
@Override
public Object create(final Integer s) {
return new TestObject();
}
@Override
public PooledObject<Object> wrap(final Object o) {
return new DefaultPooledObject<>(o);
}
}
private static final class Task<E extends Exception> implements Callable<Object> {
private final GenericKeyedObjectPool<Integer, Object, E> pool;
private final int key;
Task(final GenericKeyedObjectPool<Integer, Object, E> pool, final int count) {
this.pool = pool;
this.key = count % 20;
}
private void busyWait(final long timeMillis) {
// busy waiting intentionally as a simple thread.sleep fails to reproduce
final long endTimeMillis = System.currentTimeMillis() + timeMillis;
while (System.currentTimeMillis() < endTimeMillis) {
// empty
}
}
@Override
public Object call() throws E {
try {
final Object value;
value = pool.borrowObject(key);
// don't make this too long or it won't reproduce, and don't make it zero or it
// won't reproduce
// constant low value also doesn't reproduce
busyWait(System.currentTimeMillis() % 4);
pool.returnObject(key, value);
return "success";
} catch (final NoSuchElementException e) {
// ignore, we've exhausted the pool
// not sure whether what we do here matters for reproducing
busyWait(System.currentTimeMillis() % 20);
return "exhausted";
}
}
}
private static final class TestObject {
}
public static void main(final String[] args) {
try {
new ObjectPoolIssue326().run();
} catch (final Exception e) {
e.printStackTrace();
}
}
private <E extends Exception> List<Task<E>> createTasks(final GenericKeyedObjectPool<Integer, Object, E> pool) {
final List<Task<E>> tasks = new ArrayList<>();
for (int i = 0; i < 250; i++) {
tasks.add(new Task<>(pool, i));
}
return tasks;
}
private void run() throws InterruptedException, ExecutionException {
final GenericKeyedObjectPoolConfig<Object> poolConfig = new GenericKeyedObjectPoolConfig<>();
poolConfig.setMaxTotal(10);
poolConfig.setMaxTotalPerKey(5);
poolConfig.setMinIdlePerKey(-1);
poolConfig.setMaxIdlePerKey(-1);
poolConfig.setLifo(true);
poolConfig.setFairness(true);
poolConfig.setMaxWait(Duration.ofSeconds(30));
poolConfig.setMinEvictableIdleDuration(Duration.ofMillis(-1));
poolConfig.setSoftMinEvictableIdleDuration(Duration.ofMillis(-1));
poolConfig.setNumTestsPerEvictionRun(1);
poolConfig.setTestOnCreate(false);
poolConfig.setTestOnBorrow(false);
poolConfig.setTestOnReturn(false);
poolConfig.setTestWhileIdle(false);
poolConfig.setDurationBetweenEvictionRuns(Duration.ofSeconds(5));
poolConfig.setEvictionPolicyClassName(BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME);
poolConfig.setBlockWhenExhausted(false);
poolConfig.setJmxEnabled(false);
poolConfig.setJmxNameBase(null);
poolConfig.setJmxNamePrefix(null);
final GenericKeyedObjectPool<Integer, Object, RuntimeException> pool = new GenericKeyedObjectPool<>(new ObjectFactory(), poolConfig);
// number of threads to reproduce is finicky. this count seems to be best for my
// 4 core box.
// too many doesn't reproduce it ever, too few doesn't either.
final ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
final Instant startTime = Instant.now();
long testIter = 0;
try {
while (true) {
testIter++;
if (testIter % 1000 == 0) {
System.out.println(testIter);
}
final List<Task<RuntimeException>> tasks = createTasks(pool);
final List<Future<Object>> futures = service.invokeAll(tasks);
for (final Future<Object> future : futures) {
future.get();
}
}
} finally {
System.out.println("Time: " + Duration.between(startTime, Instant.now()));
service.shutdown();
}
}
}
/*
* Example stack trace: java.util.concurrent.ExecutionException:
* java.lang.NullPointerException at
* java.util.concurrent.FutureTask.report(FutureTask.java:122) at
* java.util.concurrent.FutureTask.get(FutureTask.java:192) at
* threading_pool.ObjectPoolIssue.run(ObjectPoolIssue.java:63) at
* threading_pool.ObjectPoolIssue.main(ObjectPoolIssue.java:23) at
* sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
* sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
* at
* sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.
* java:43) at java.lang.reflect.Method.invoke(Method.java:498) at
* com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) Caused
* by: java.lang.NullPointerException at
* org.apache.commons.pool3.impl.GenericKeyedObjectPool.returnObject(
* GenericKeyedObjectPool.java:474) at
* threading_pool.ObjectPoolIssue$Task.call(ObjectPoolIssue.java:112) at
* java.util.concurrent.FutureTask.run(FutureTask.java:266) at
* java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:
* 1142) at
* java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:
* 617) at java.lang.Thread.run(Thread.java:745)
*/