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    *      https://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.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.lang.management.ManagementFactory;
25  import java.time.Duration;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.concurrent.CountDownLatch;
29  import java.util.concurrent.ExecutorService;
30  import java.util.concurrent.Executors;
31  import java.util.concurrent.Future;
32  import java.util.concurrent.TimeUnit;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
35  import javax.management.MBeanServer;
36  import javax.management.ObjectName;
37  
38  import org.apache.commons.pool2.Waiter;
39  import org.apache.commons.pool2.WaiterFactory;
40  import org.apache.commons.pool2.impl.TestGenericObjectPool.SimpleFactory;
41  import org.junit.jupiter.api.AfterEach;
42  import org.junit.jupiter.api.BeforeEach;
43  import org.junit.jupiter.api.Test;
44  import org.junit.jupiter.api.Timeout;
45  
46  /**
47   */
48  class TestBaseGenericObjectPool {
49  
50      BaseGenericObjectPool<String> pool;
51      SimpleFactory factory;
52  
53      @BeforeEach
54      public void setUp() {
55          factory = new SimpleFactory();
56          pool = new GenericObjectPool<>(factory);
57      }
58  
59      @AfterEach
60      public void tearDown() {
61          pool.close();
62          pool = null;
63          factory = null;
64      }
65  
66      @Test
67      void testActiveTimeStatistics() {
68          for (int i = 0; i < 99; i++) { // must be < MEAN_TIMING_STATS_CACHE_SIZE
69              pool.updateStatsReturn(Duration.ofMillis(i));
70          }
71          assertEquals(49, pool.getMeanActiveTimeMillis(), Double.MIN_VALUE);
72      }
73  
74      @Test
75      void testBorrowWaitStatistics() {
76          final DefaultPooledObject<String> p = (DefaultPooledObject<String>) factory.makeObject();
77          pool.updateStatsBorrow(p, Duration.ofMillis(10));
78          pool.updateStatsBorrow(p, Duration.ofMillis(20));
79          pool.updateStatsBorrow(p, Duration.ofMillis(20));
80          pool.updateStatsBorrow(p, Duration.ofMillis(30));
81          assertEquals(20, pool.getMeanBorrowWaitTimeMillis(), Double.MIN_VALUE);
82          assertEquals(30, pool.getMaxBorrowWaitTimeMillis(), 0);
83      }
84  
85      void testBorrowWaitStatisticsMax() {
86          final DefaultPooledObject<String> p = (DefaultPooledObject<String>) factory.makeObject();
87          assertEquals(0, pool.getMaxBorrowWaitTimeMillis(), Double.MIN_VALUE);
88          pool.updateStatsBorrow(p, Duration.ZERO);
89          assertEquals(0, pool.getMaxBorrowWaitTimeMillis(), Double.MIN_VALUE);
90          pool.updateStatsBorrow(p, Duration.ofMillis(20));
91          assertEquals(20, pool.getMaxBorrowWaitTimeMillis(), Double.MIN_VALUE);
92          pool.updateStatsBorrow(p, Duration.ofMillis(20));
93          assertEquals(20, pool.getMaxBorrowWaitTimeMillis(), Double.MIN_VALUE);
94          pool.updateStatsBorrow(p, Duration.ofMillis(10));
95          assertEquals(20, pool.getMaxBorrowWaitTimeMillis(), Double.MIN_VALUE);
96      }
97  
98      @Test
99      void testCollectDetailedStatisticsConfiguration() {
100         // Test configuration through config object
101         final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
102         config.setCollectDetailedStatistics(false);
103         try (GenericObjectPool<String> testPool = new GenericObjectPool<>(factory, config)) {
104             assertFalse(testPool.getCollectDetailedStatistics());
105         }
106         // Test runtime configuration
107         pool.setCollectDetailedStatistics(false);
108         assertFalse(pool.getCollectDetailedStatistics());
109         pool.setCollectDetailedStatistics(true);
110         assertTrue(pool.getCollectDetailedStatistics());
111     }
112 
113     @Test
114     void testCollectDetailedStatisticsDefault() {
115         // Test that collectDetailedStatistics defaults to true for backward compatibility
116         assertTrue(pool.getCollectDetailedStatistics());
117     }
118 
119     @Test
120     void testCollectDetailedStatisticsDisabled() throws Exception {
121         // Configure pool to disable detailed statistics
122         pool.setCollectDetailedStatistics(false);
123         final DefaultPooledObject<String> pooledObject = (DefaultPooledObject<String>) factory.makeObject();
124         // Record initial values
125         final long initialActiveTime = pool.getMeanActiveTimeMillis();
126         final long initialIdleTime = pool.getMeanIdleDuration().toMillis();
127         final long initialWaitTime = pool.getMeanBorrowWaitTimeMillis();
128         final long initialMaxWaitTime = pool.getMaxBorrowWaitTimeMillis();
129         // Update statistics - should be ignored for detailed stats
130         pool.updateStatsBorrow(pooledObject, Duration.ofMillis(100));
131         pool.updateStatsReturn(Duration.ofMillis(200));
132         // Basic counters should still work
133         assertEquals(1, pool.getBorrowedCount());
134         assertEquals(1, pool.getReturnedCount());
135         // Detailed statistics should remain unchanged
136         assertEquals(initialActiveTime, pool.getMeanActiveTimeMillis());
137         assertEquals(initialIdleTime, pool.getMeanIdleDuration().toMillis());
138         assertEquals(initialWaitTime, pool.getMeanBorrowWaitTimeMillis());
139         assertEquals(initialMaxWaitTime, pool.getMaxBorrowWaitTimeMillis());
140     }
141 
142     @Test
143     void testCollectDetailedStatisticsEnabled() throws Exception {
144         // Ensure detailed statistics are enabled (default)
145         pool.setCollectDetailedStatistics(true);
146         final DefaultPooledObject<String> pooledObject = (DefaultPooledObject<String>) factory.makeObject();
147         // Update statistics
148         pool.updateStatsBorrow(pooledObject, Duration.ofMillis(100));
149         pool.updateStatsReturn(Duration.ofMillis(200));
150         // All counters should work
151         assertEquals(1, pool.getBorrowedCount());
152         assertEquals(1, pool.getReturnedCount());
153         // Detailed statistics should be updated
154         assertEquals(200, pool.getMeanActiveTimeMillis());
155         assertEquals(100, pool.getMeanBorrowWaitTimeMillis());
156         assertEquals(100, pool.getMaxBorrowWaitTimeMillis());
157     }
158 
159     @Test
160     void testCollectDetailedStatisticsToggling() throws Exception {
161         final DefaultPooledObject<String> pooledObject = (DefaultPooledObject<String>) factory.makeObject();
162         // Start with detailed stats enabled
163         pool.setCollectDetailedStatistics(true);
164         pool.updateStatsBorrow(pooledObject, Duration.ofMillis(50));
165         pool.updateStatsReturn(Duration.ofMillis(100));
166         assertEquals(50, pool.getMeanBorrowWaitTimeMillis());
167         assertEquals(100, pool.getMeanActiveTimeMillis());
168         // Disable detailed stats
169         pool.setCollectDetailedStatistics(false);
170         pool.updateStatsBorrow(pooledObject, Duration.ofMillis(200));
171         pool.updateStatsReturn(Duration.ofMillis(300));
172         // Detailed stats should remain at previous values
173         assertEquals(50, pool.getMeanBorrowWaitTimeMillis());
174         assertEquals(100, pool.getMeanActiveTimeMillis());
175         // Basic counters should continue to increment
176         assertEquals(2, pool.getBorrowedCount());
177         assertEquals(2, pool.getReturnedCount());
178     }
179 
180     @Test
181     void testDetailedStatisticsConfigIntegration() {
182         // Test that config property is properly applied during pool construction
183         final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
184         config.setCollectDetailedStatistics(false);
185         try (GenericObjectPool<String> testPool = new GenericObjectPool<>(factory, config)) {
186             assertFalse(testPool.getCollectDetailedStatistics(), "Pool should respect collectDetailedStatistics setting from config");
187             // Test that toString includes the new property
188             final String configString = config.toString();
189             assertTrue(configString.contains("collectDetailedStatistics"), "Config toString should include collectDetailedStatistics property");
190         }
191     }
192 
193     @Test
194     void testEvictionTimerMultiplePools() throws InterruptedException {
195         final AtomicIntegerFactory factory = new AtomicIntegerFactory();
196         factory.setValidateLatency(50);
197         try (GenericObjectPool<AtomicInteger> evictingPool = new GenericObjectPool<>(factory)) {
198             evictingPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
199             evictingPool.setNumTestsPerEvictionRun(5);
200             evictingPool.setTestWhileIdle(true);
201             evictingPool.setMinEvictableIdleTime(Duration.ofMillis(50));
202             for (int i = 0; i < 10; i++) {
203                 try {
204                     evictingPool.addObject();
205                 } catch (final Exception e) {
206                     e.printStackTrace();
207                 }
208             }
209 
210             for (int i = 0; i < 1000; i++) {
211                 try (GenericObjectPool<AtomicInteger> nonEvictingPool = new GenericObjectPool<>(factory)) {
212                     // empty
213                 }
214             }
215 
216             Thread.sleep(1000);
217             assertEquals(0, evictingPool.getNumIdle());
218         }
219     }
220 
221     /**
222      * POOL-393
223      * Tests JMX registration does not add too much latency to pool creation.
224      */
225     @SuppressWarnings("resource") // pools closed in finally block
226     @Test
227     @Timeout(value = 10_000, unit = TimeUnit.MILLISECONDS)
228     void testJMXRegistrationLatency() {
229         final int numPools = 1000;
230         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
231         final ArrayList<GenericObjectPool<Waiter>> pools = new ArrayList<>();
232         try {
233             // final long startTime = System.currentTimeMillis();
234             for (int i = 0; i < numPools; i++) {
235                 pools.add(new GenericObjectPool<>(new WaiterFactory<>(0, 0, 0, 0, 0, 0), new GenericObjectPoolConfig<>()));
236             }
237             // System.out.println("Duration: " + (System.currentTimeMillis() - startTime));
238             final ObjectName oname = pools.get(numPools - 1).getJmxName();
239             assertEquals(1, mbs.queryNames(oname, null).size());
240         } finally {
241             pools.forEach(GenericObjectPool::close);
242         }
243     }
244 
245     @Test
246     void testStatsStoreCircularBuffer() throws Exception {
247         // Test that StatsStore properly handles circular buffer behavior
248         final DefaultPooledObject<String> pooledObject = (DefaultPooledObject<String>) factory.makeObject();
249         // Fill beyond the cache size (100) to test circular behavior
250         final int cacheSize = 100; // BaseGenericObjectPool.MEAN_TIMING_STATS_CACHE_SIZE
251         for (int i = 0; i < cacheSize + 50; i++) {
252             pool.updateStatsBorrow(pooledObject, Duration.ofMillis(i));
253             pool.updateStatsReturn(Duration.ofMillis(i * 2));
254         }
255         // Statistics should still be meaningful after circular buffer wrapping
256         assertTrue(pool.getMeanActiveTimeMillis() > 0);
257         assertTrue(pool.getMeanBorrowWaitTimeMillis() > 0);
258         assertTrue(pool.getMaxBorrowWaitTimeMillis() > 0);
259         // The mean should reflect recent values, not all historical values
260         // (exact assertion depends on circular buffer implementation)
261         assertTrue(pool.getMeanBorrowWaitTimeMillis() >= 50); // Should be influenced by recent higher values
262     }
263 
264     @Test
265     void testStatsStoreConcurrentAccess() throws Exception {
266         // Test the lock-free StatsStore implementation under concurrent load
267         final int numThreads = 10;
268         final int operationsPerThread = 1000;
269         final ExecutorService executor = Executors.newFixedThreadPool(numThreads);
270         final CountDownLatch startLatch = new CountDownLatch(1);
271         final CountDownLatch completeLatch = new CountDownLatch(numThreads);
272         final List<Future<Void>> futures = new ArrayList<>();
273         // Create threads that will concurrently update statistics
274         for (int i = 0; i < numThreads; i++) {
275             final int threadId = i;
276             futures.add(executor.submit(() -> {
277                 try {
278                     final DefaultPooledObject<String> pooledObject = (DefaultPooledObject<String>) factory.makeObject();
279                     // Wait for all threads to be ready
280                     startLatch.await();
281                     // Perform concurrent operations
282                     for (int j = 0; j < operationsPerThread; j++) {
283                         pool.updateStatsBorrow(pooledObject, Duration.ofMillis(threadId * 10 + j));
284                         pool.updateStatsReturn(Duration.ofMillis(threadId * 20 + j));
285                     }
286                 } catch (final Exception e) {
287                     throw new RuntimeException(e);
288                 } finally {
289                     completeLatch.countDown();
290                 }
291                 return null;
292             }));
293         }
294         // Start all threads simultaneously
295         startLatch.countDown();
296         // Wait for completion
297         assertTrue(completeLatch.await(30, TimeUnit.SECONDS), "Concurrent test should complete within 30 seconds");
298         // Verify no exceptions occurred
299         for (final Future<Void> future : futures) {
300             future.get(); // Will throw if there was an exception
301         }
302         // Verify that statistics were collected (exact values may vary due to race conditions)
303         assertEquals(numThreads * operationsPerThread, pool.getBorrowedCount());
304         assertEquals(numThreads * operationsPerThread, pool.getReturnedCount());
305         // Mean values should be reasonable (not zero or wildly incorrect)
306         assertTrue(pool.getMeanActiveTimeMillis() >= 0);
307         assertTrue(pool.getMeanBorrowWaitTimeMillis() >= 0);
308         assertTrue(pool.getMaxBorrowWaitTimeMillis() >= 0);
309         executor.shutdown();
310         assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
311     }
312 }