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.assertTrue;
22  import static org.junit.jupiter.api.Assertions.fail;
23  
24  import java.util.LinkedList;
25  import java.util.List;
26  
27  import org.apache.commons.lang3.ArrayFill;
28  import org.apache.commons.pool2.BasePooledObjectFactory;
29  import org.apache.commons.pool2.PooledObject;
30  import org.junit.jupiter.api.AfterEach;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   */
35  class TestSoftRefOutOfMemory {
36  
37      public static class LargePoolableObjectFactory extends BasePooledObjectFactory<String> {
38  
39          private final String buffer;
40          private int counter;
41  
42          LargePoolableObjectFactory(final int size) {
43              buffer = new StringBuffer().append(ArrayFill.fill(new char[size], '.')).toString();
44          }
45  
46          @Override
47          public String create() {
48              counter++;
49              return String.valueOf(counter) + buffer;
50          }
51  
52          @Override
53          public PooledObject<String> wrap(final String value) {
54              return new DefaultPooledObject<>(value);
55          }
56      }
57  
58      private static final class OomeFactory extends BasePooledObjectFactory<String> {
59  
60          private final OomeTrigger trigger;
61  
62          OomeFactory(final OomeTrigger trigger) {
63              this.trigger = trigger;
64          }
65  
66          @Override
67          public String create() {
68              if (trigger.equals(OomeTrigger.CREATE)) {
69                  throw new OutOfMemoryError();
70              }
71              // It seems that as of Java 1.4 String.valueOf may return an
72              // intern()'ed String this may cause problems when the tests
73              // depend on the returned object to be eventually garbaged
74              // collected. Either way, making sure a new String instance
75              // is returned eliminated false failures.
76              return new StringBuffer().toString();
77          }
78  
79          @Override
80          public void destroyObject(final PooledObject<String> p) throws Exception {
81              if (trigger.equals(OomeTrigger.DESTROY)) {
82                  throw new OutOfMemoryError();
83              }
84              super.destroyObject(p);
85          }
86  
87          @Override
88          public boolean validateObject(final PooledObject<String> p) {
89              if (trigger.equals(OomeTrigger.VALIDATE)) {
90                  throw new OutOfMemoryError();
91              }
92              return !trigger.equals(OomeTrigger.DESTROY);
93          }
94  
95          @Override
96          public PooledObject<String> wrap(final String value) {
97              return new DefaultPooledObject<>(value);
98          }
99      }
100 
101     private enum OomeTrigger {
102         CREATE,
103         VALIDATE,
104         DESTROY
105     }
106 
107     public static class SmallPoolableObjectFactory extends BasePooledObjectFactory<String> {
108         private int counter;
109 
110         @Override
111         public String create() {
112             counter++;
113             // It seems that as of Java 1.4 String.valueOf may return an
114             // intern()'ed String this may cause problems when the tests
115             // depend on the returned object to be eventually garbaged
116             // collected. Either way, making sure a new String instance
117             // is returned eliminated false failures.
118             return new StringBuffer().append(counter).toString();
119         }
120         @Override
121         public PooledObject<String> wrap(final String value) {
122             return new DefaultPooledObject<>(value);
123         }
124     }
125 
126     private SoftReferenceObjectPool<String> pool;
127 
128     @AfterEach
129     public void tearDown() {
130         if (pool != null) {
131             pool.close();
132             pool = null;
133         }
134         System.gc();
135     }
136 
137     @Test
138     void testOutOfMemory() throws Exception {
139         pool = new SoftReferenceObjectPool<>(new SmallPoolableObjectFactory());
140 
141         String obj = pool.borrowObject();
142         assertEquals("1", obj);
143         pool.returnObject(obj);
144         obj = null;
145 
146         assertEquals(1, pool.getNumIdle());
147 
148         final List<byte[]> garbage = new LinkedList<>();
149         final Runtime runtime = Runtime.getRuntime();
150         while (pool.getNumIdle() > 0) {
151             try {
152                 long freeMemory = runtime.freeMemory();
153                 if (freeMemory > Integer.MAX_VALUE) {
154                     freeMemory = Integer.MAX_VALUE;
155                 }
156                 garbage.add(new byte[Math.min(1024 * 1024, (int) freeMemory / 2)]);
157             } catch (final OutOfMemoryError oome) {
158                 System.gc();
159                 assertEquals(0, pool.getNumIdle());
160             }
161             System.gc();
162         }
163         garbage.clear();
164         System.gc();
165 
166         obj = pool.borrowObject();
167         assertEquals("2", obj);
168         pool.returnObject(obj);
169         obj = null;
170 
171         assertEquals(1, pool.getNumIdle());
172     }
173 
174     @Test
175     void testOutOfMemory1000() throws Exception {
176         pool = new SoftReferenceObjectPool<>(new SmallPoolableObjectFactory());
177 
178         for (int i = 0 ; i < 1000 ; i++) {
179             pool.addObject();
180         }
181 
182         String obj = pool.borrowObject();
183         assertEquals("1000", obj);
184         pool.returnObject(obj);
185         obj = null;
186 
187         assertEquals(1000, pool.getNumIdle());
188 
189         final List<byte[]> garbage = new LinkedList<>();
190         final Runtime runtime = Runtime.getRuntime();
191         while (pool.getNumIdle() > 0) {
192             try {
193                 long freeMemory = runtime.freeMemory();
194                 if (freeMemory > Integer.MAX_VALUE) {
195                     freeMemory = Integer.MAX_VALUE;
196                 }
197                 garbage.add(new byte[Math.min(1024 * 1024, (int) freeMemory / 2)]);
198             } catch (final OutOfMemoryError oome) {
199                 System.gc();
200                 assertEquals(0, pool.getNumIdle());
201             }
202             System.gc();
203         }
204         garbage.clear();
205         System.gc();
206 
207         obj = pool.borrowObject();
208         assertEquals("1001", obj);
209         pool.returnObject(obj);
210         obj = null;
211 
212         assertEquals(1, pool.getNumIdle());
213     }
214 
215     /**
216      * Makes sure an {@link OutOfMemoryError} isn't swallowed.
217      *
218      * @throws Exception May occur in some failure modes
219      */
220     @Test
221     void testOutOfMemoryError() throws Exception {
222         pool = new SoftReferenceObjectPool<>(new OomeFactory(OomeTrigger.CREATE));
223         try {
224             pool.borrowObject();
225             fail("Expected out of memory.");
226         } catch (final OutOfMemoryError ex) {
227             // expected
228         }
229         pool.close();
230         pool = new SoftReferenceObjectPool<>(new OomeFactory(OomeTrigger.VALIDATE));
231         try {
232             pool.borrowObject();
233             fail("Expected out of memory.");
234         } catch (final OutOfMemoryError ex) {
235             // expected
236         }
237         pool.close();
238         pool = new SoftReferenceObjectPool<>(new OomeFactory(OomeTrigger.DESTROY));
239         try {
240             pool.borrowObject();
241             fail("Expected out of memory.");
242         } catch (final OutOfMemoryError ex) {
243             // expected
244         }
245         pool.close();
246     }
247 
248     @Test
249     void testOutOfMemoryLarge() throws Exception {
250         pool = new SoftReferenceObjectPool<>(new LargePoolableObjectFactory(1000000));
251 
252         String obj = pool.borrowObject();
253         assertTrue(obj.startsWith("1."));
254         pool.returnObject(obj);
255         obj = null;
256 
257         assertEquals(1, pool.getNumIdle());
258 
259         final List<byte[]> garbage = new LinkedList<>();
260         final Runtime runtime = Runtime.getRuntime();
261         while (pool.getNumIdle() > 0) {
262             try {
263                 long freeMemory = runtime.freeMemory();
264                 if (freeMemory > Integer.MAX_VALUE) {
265                     freeMemory = Integer.MAX_VALUE;
266                 }
267                 garbage.add(new byte[Math.min(1024 * 1024, (int) freeMemory / 2)]);
268             } catch (final OutOfMemoryError oome) {
269                 System.gc();
270                 assertEquals(0, pool.getNumIdle());
271             }
272             System.gc();
273         }
274         garbage.clear();
275         System.gc();
276 
277         obj = pool.borrowObject();
278         assertTrue(obj.startsWith("2."));
279         pool.returnObject(obj);
280         obj = null;
281 
282         assertEquals(1, pool.getNumIdle());
283     }
284 }