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.impl;
19  
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  
22  import java.time.Duration;
23  import java.util.concurrent.CountDownLatch;
24  import java.util.concurrent.TimeUnit;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  
27  import org.apache.commons.pool2.BasePooledObjectFactory;
28  import org.apache.commons.pool2.PooledObject;
29  import org.apache.commons.pool2.Waiter;
30  import org.junit.jupiter.api.Test;
31  import org.junit.jupiter.api.Timeout;
32  
33  /**
34   * Tests POOL-340.
35   */
36  public class TestGenericObjectPoolFactoryCreateFailure {
37  
38      private static class SingleObjectFactory extends BasePooledObjectFactory<Object> {
39          private final AtomicBoolean created = new AtomicBoolean();
40  
41          @Override
42          public Object create() throws Exception {
43              if (!created.getAndSet(true)) {
44                  return new Object();
45              }
46              throw new Exception("Already created");
47          }
48  
49          @Override
50          public boolean validateObject(final PooledObject<Object> p) {
51              return true;
52          }
53  
54          @Override
55          public PooledObject<Object> wrap(final Object obj) {
56              return new DefaultPooledObject<>(new Object());
57          }
58      }
59  
60      private static class WinnerRunnable implements Runnable {
61          private final CountDownLatch barrier;
62          private final AtomicBoolean failed;
63          private final GenericObjectPool<Object> pool;
64          private WinnerRunnable(final GenericObjectPool<Object> pool, final CountDownLatch barrier, final AtomicBoolean failed) {
65              this.pool = pool;
66              this.failed = failed;
67              this.barrier = barrier;
68          }
69          @Override
70          public void run() {
71              try {
72                  println("start borrowing in parallel thread");
73                  final Object obj = pool.borrowObject();
74  
75                  // wait for another thread to start borrowObject
76                  if (!barrier.await(5, TimeUnit.SECONDS)) {
77                      println("Timeout waiting");
78                      failed.set(true);
79                  } else {
80                      // just to make sure, borrowObject has started waiting on queue
81                      Waiter.sleepQuietly(1000);
82                  }
83  
84                  pool.returnObject(obj);
85                  println("ended borrowing in parallel thread");
86              } catch (final Exception e) {
87                  failed.set(true);
88                  e.printStackTrace();
89              }
90          }
91      }
92  
93      private static final Duration NEG_ONE_DURATION = Duration.ofMillis(-1);
94  
95      private static void println(final String msg) {
96          // System.out.println(msg);
97      }
98  
99      @Test
100     @Timeout(value = 10_000, unit = TimeUnit.MILLISECONDS)
101     public void testBorrowObjectStuck() {
102         final SingleObjectFactory factory = new SingleObjectFactory();
103         final GenericObjectPoolConfig<Object> config = new GenericObjectPoolConfig<>();
104         config.setMaxIdle(1);
105         config.setMaxTotal(1);
106         config.setBlockWhenExhausted(true);
107         config.setMinIdle(0);
108         config.setTestOnBorrow(true);
109         config.setTestOnReturn(true);
110         config.setTestWhileIdle(false);
111         config.setTimeBetweenEvictionRuns(NEG_ONE_DURATION);
112         config.setMinEvictableIdleTime(NEG_ONE_DURATION);
113         config.setMinEvictableIdleDuration(NEG_ONE_DURATION);
114         config.setSoftMinEvictableIdleDuration(NEG_ONE_DURATION);
115 
116         config.setMaxWait(NEG_ONE_DURATION);
117         try (GenericObjectPool<Object> pool = new GenericObjectPool<>(factory, config)) {
118 
119             final AtomicBoolean failed = new AtomicBoolean();
120             final CountDownLatch barrier = new CountDownLatch(1);
121             final Thread thread1 = new Thread(new WinnerRunnable(pool, barrier, failed));
122             thread1.start();
123 
124             // wait for object to be created
125             while (!factory.created.get()) {
126                 Waiter.sleepQuietly(5);
127             }
128 
129             // now borrow
130             barrier.countDown();
131             try {
132                 println("try borrow in main thread");
133 
134                 final Object o = pool.borrowObject();
135                 println("Success borrow in main thread " + o);
136             } catch (final Exception e) {
137                 e.printStackTrace();
138             }
139 
140             assertFalse(failed.get());
141         }
142 
143     }
144 }