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