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.dbcp2;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  import java.io.StringWriter;
27  import java.sql.CallableStatement;
28  import java.sql.Connection;
29  import java.sql.PreparedStatement;
30  import java.sql.ResultSet;
31  import java.sql.SQLException;
32  import java.sql.Statement;
33  import java.time.Duration;
34  import java.time.Instant;
35  
36  import org.apache.commons.pool2.KeyedObjectPool;
37  import org.junit.jupiter.api.Assertions;
38  import org.junit.jupiter.api.BeforeEach;
39  import org.junit.jupiter.api.Test;
40  
41  /**
42   * TestSuite for BasicDataSource with abandoned connection trace enabled
43   */
44  public class TestAbandonedBasicDataSource extends TestBasicDataSource {
45  
46      private StringWriter sw;
47  
48      /**
49       * Verifies that con.lastUsed has been updated and then resets it to 0
50       */
51      private void assertAndReset(final DelegatingConnection<?> con) {
52          assertTrue(con.getLastUsedInstant().compareTo(Instant.EPOCH) > 0);
53          con.setLastUsed(Instant.EPOCH);
54      }
55  
56      // ---------- Abandoned Test -----------
57  
58      /**
59       * Verifies that PreparedStatement executeXxx methods update lastUsed on the parent connection
60       */
61      private void checkLastUsedPreparedStatement(final PreparedStatement ps, final DelegatingConnection<?> conn)
62              throws Exception {
63          ps.execute();
64          assertAndReset(conn);
65          try (ResultSet rs = ps.executeQuery()) {
66              Assertions.assertNotNull(rs);
67          }
68          assertAndReset(conn);
69          ps.executeUpdate();
70          assertAndReset(conn);
71      }
72  
73      /**
74       * Verifies that Statement executeXxx methods update lastUsed on the parent connection
75       */
76      private void checkLastUsedStatement(final Statement st, final DelegatingConnection<?> conn) throws Exception {
77          st.execute("");
78          assertAndReset(conn);
79          st.execute("", new int[] {});
80          assertAndReset(conn);
81          st.execute("", 0);
82          assertAndReset(conn);
83          st.executeBatch();
84          assertAndReset(conn);
85          st.executeLargeBatch();
86          assertAndReset(conn);
87          try (ResultSet rs = st.executeQuery("")) {
88              Assertions.assertNotNull(rs);
89          }
90          assertAndReset(conn);
91          st.executeUpdate("");
92          assertAndReset(conn);
93          st.executeUpdate("", new int[] {});
94          assertAndReset(conn);
95          st.executeLargeUpdate("", new int[] {});
96          assertAndReset(conn);
97          st.executeUpdate("", 0);
98          assertAndReset(conn);
99          st.executeLargeUpdate("", 0);
100         assertAndReset(conn);
101         st.executeUpdate("", new String[] {});
102         assertAndReset(conn);
103         st.executeLargeUpdate("", new String[] {});
104         assertAndReset(conn);
105     }
106 
107     private void createStatement(final Connection conn) throws Exception{
108         final PreparedStatement ps = conn.prepareStatement("");
109         Assertions.assertNotNull(ps);
110     }
111 
112     @Override
113     @BeforeEach
114     public void setUp() throws Exception {
115         super.setUp();
116         // abandoned enabled but should not affect the basic tests
117         // (very high timeout)
118         ds.setLogAbandoned(true);
119         ds.setRemoveAbandonedOnBorrow(true);
120         ds.setRemoveAbandonedOnMaintenance(true);
121         ds.setRemoveAbandonedTimeout(Duration.ofSeconds(10));
122         sw = new StringWriter();
123         ds.setAbandonedLogWriter(new PrintWriter(sw));
124     }
125 
126     @Test
127     public void testAbandoned() throws Exception {
128         // force abandoned
129         ds.setRemoveAbandonedTimeout(Duration.ZERO);
130         ds.setMaxTotal(1);
131 
132         for (int i = 0; i < 3; i++) {
133             assertNotNull(ds.getConnection());
134         }
135     }
136 
137     @Test
138     public void testAbandonedClose() throws Exception {
139         // force abandoned
140         ds.setRemoveAbandonedTimeout(Duration.ZERO);
141         ds.setMaxTotal(1);
142         ds.setAccessToUnderlyingConnectionAllowed(true);
143 
144         try (Connection conn1 = getConnection()) {
145             assertNotNull(conn1);
146             assertEquals(1, ds.getNumActive());
147 
148             try (Connection conn2 = getConnection()) {
149                 // Attempt to borrow object triggers abandoned cleanup
150                 // conn1 should be closed by the pool to make room
151                 assertNotNull(conn2);
152                 assertEquals(1, ds.getNumActive());
153                 // Verify that conn1 is closed
154                 assertTrue(((DelegatingConnection<?>) conn1).getInnermostDelegate().isClosed());
155                 // Verify that conn1 is aborted
156                 final TesterConnection tCon = (TesterConnection) ((DelegatingConnection<?>) conn1)
157                         .getInnermostDelegate();
158                 assertTrue(tCon.isAborted());
159 
160             }
161             assertEquals(0, ds.getNumActive());
162 
163             // Second close on conn1 is OK as of dbcp 1.3
164         }
165         assertEquals(0, ds.getNumActive());
166         final String string = sw.toString();
167         assertTrue(string.contains("testAbandonedClose"), string);
168     }
169 
170     @Test
171     public void testAbandonedCloseWithExceptions() throws Exception {
172         // force abandoned
173         ds.setRemoveAbandonedTimeout(Duration.ZERO);
174         ds.setMaxTotal(1);
175         ds.setAccessToUnderlyingConnectionAllowed(true);
176 
177         final Connection conn1 = getConnection();
178         assertNotNull(conn1);
179         assertEquals(1, ds.getNumActive());
180 
181         final Connection conn2 = getConnection();
182         assertNotNull(conn2);
183         assertEquals(1, ds.getNumActive());
184 
185         // set an IO failure causing the isClosed method to fail
186         final TesterConnection tconn1 = (TesterConnection) ((DelegatingConnection<?>) conn1).getInnermostDelegate();
187         tconn1.setFailure(new IOException("network error"));
188         final TesterConnection tconn2 = (TesterConnection) ((DelegatingConnection<?>) conn2).getInnermostDelegate();
189         tconn2.setFailure(new IOException("network error"));
190 
191         try {
192             conn2.close();
193         } catch (final SQLException ex) {
194             /* Ignore */
195         }
196         assertEquals(0, ds.getNumActive());
197 
198         try {
199             conn1.close();
200         } catch (final SQLException ex) {
201             // ignore
202         }
203         assertEquals(0, ds.getNumActive());
204         final String string = sw.toString();
205         assertTrue(string.contains("testAbandonedCloseWithExceptions"), string);
206     }
207 
208     @Test
209     public void testAbandonedStackTraces() throws Exception {
210         // force abandoned
211         ds.setRemoveAbandonedTimeout(Duration.ZERO);
212         ds.setMaxTotal(1);
213         ds.setAccessToUnderlyingConnectionAllowed(true);
214         ds.setAbandonedUsageTracking(true);
215 
216         try (Connection conn1 = getConnection()) {
217             assertNotNull(conn1);
218             assertEquals(1, ds.getNumActive());
219             // Use the connection
220             try (Statement stmt = conn1.createStatement()) {
221                 assertNotNull(stmt);
222                 stmt.execute("SELECT 1 FROM DUAL");
223             }
224 
225             try (Connection conn2 = getConnection()) {
226                 // Attempt to borrow object triggers abandoned cleanup
227                 // conn1 should be closed by the pool to make room
228                 assertNotNull(conn2);
229                 assertEquals(1, ds.getNumActive());
230                 // Verify that conn1 is closed
231                 assertTrue(((DelegatingConnection<?>) conn1).getInnermostDelegate().isClosed());
232                 // Verify that conn1 is aborted
233                 final TesterConnection tCon = (TesterConnection) ((DelegatingConnection<?>) conn1)
234                         .getInnermostDelegate();
235                 assertTrue(tCon.isAborted());
236 
237             }
238             assertEquals(0, ds.getNumActive());
239         }
240         assertEquals(0, ds.getNumActive());
241         final String stackTrace = sw.toString();
242         assertTrue(stackTrace.contains("testAbandonedStackTraces"), stackTrace);
243         assertTrue(stackTrace.contains("Pooled object created"), stackTrace);
244         assertTrue(stackTrace.contains("The last code to use this object was:"), stackTrace);
245     }
246 
247     /**
248      * DBCP-180 - verify that a GC can clean up an unused Statement when it is
249      * no longer referenced even when it is tracked via the AbandonedTrace
250      * mechanism.
251      */
252     @Test
253     public void testGarbageCollectorCleanUp01() throws Exception {
254         try (DelegatingConnection<?> conn = (DelegatingConnection<?>) ds.getConnection()) {
255             Assertions.assertEquals(0, conn.getTrace().size());
256             createStatement(conn);
257             Assertions.assertEquals(1, conn.getTrace().size());
258             System.gc();
259             Assertions.assertEquals(0, conn.getTrace().size());
260         }
261     }
262 
263     /**
264      * DBCP-180 - things get more interesting with statement pooling.
265      */
266     @Test
267     public void testGarbageCollectorCleanUp02() throws Exception {
268         ds.setPoolPreparedStatements(true);
269         ds.setAccessToUnderlyingConnectionAllowed(true);
270         final DelegatingConnection<?> conn = (DelegatingConnection<?>) ds.getConnection();
271         final PoolableConnection poolableConn = (PoolableConnection) conn.getDelegate();
272         final PoolingConnection poolingConn = (PoolingConnection) poolableConn.getDelegate();
273         final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement>  gkop = poolingConn.getStatementPool();
274         Assertions.assertEquals(0, conn.getTrace().size());
275         Assertions.assertEquals(0, gkop.getNumActive());
276         createStatement(conn);
277         Assertions.assertEquals(1, conn.getTrace().size());
278         Assertions.assertEquals(1, gkop.getNumActive());
279         System.gc();
280         // Finalization happens in a separate thread. Give the test time for
281         // that to complete.
282         int count = 0;
283         while (count < 50 && gkop.getNumActive() > 0) {
284             Thread.sleep(100);
285             count++;
286         }
287         Assertions.assertEquals(0, gkop.getNumActive());
288         Assertions.assertEquals(0, conn.getTrace().size());
289     }
290 
291     /**
292      * Verify that lastUsed property is updated when a connection
293      * creates or prepares a statement
294      */
295     @Test
296     public void testLastUsed() throws Exception {
297         ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1));
298         ds.setMaxTotal(2);
299         try (Connection conn1 = ds.getConnection()) {
300             Thread.sleep(500);
301             try (Statement s = conn1.createStatement()) {
302                 // Should reset lastUsed
303             }
304             Thread.sleep(800);
305             final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup
306             try (Statement s = conn1.createStatement()) {
307                 // Should still be OK
308             }
309             conn2.close();
310             Thread.sleep(500);
311             try (PreparedStatement ps = conn1.prepareStatement("SELECT 1 FROM DUAL")) {
312                 // reset
313             }
314             Thread.sleep(800);
315             try (Connection c = ds.getConnection()) {
316                 // trigger abandoned cleanup again
317             }
318             try (Statement s = conn1.createStatement()) {
319                 // empty
320             }
321         }
322     }
323 
324     /**
325      * DBCP-343 - verify that using a DelegatingStatement updates
326      * the lastUsed on the parent connection
327      */
328     @Test
329     public void testLastUsedLargePreparedStatementUse() throws Exception {
330         ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1));
331         ds.setMaxTotal(2);
332         try (Connection conn1 = ds.getConnection(); Statement st = conn1.createStatement()) {
333             final String querySQL = "SELECT 1 FROM DUAL";
334             Thread.sleep(500);
335             try (ResultSet rs = st.executeQuery(querySQL)) {
336                 Assertions.assertNotNull(rs); // Should reset lastUsed
337             }
338             Thread.sleep(800);
339             try (final Connection conn2 = ds.getConnection()) { // triggers abandoned cleanup
340                 try (ResultSet rs = st.executeQuery(querySQL)) {
341                     Assertions.assertNotNull(rs); // Should still be OK
342                 }
343             }
344             Thread.sleep(500);
345             st.executeLargeUpdate(""); // Should also reset
346             Thread.sleep(800);
347             try (Connection c = ds.getConnection()) {
348                 // trigger abandoned cleanup again
349             }
350             try (Statement s = conn1.createStatement()) {
351                 // Connection should still be good
352             }
353         }
354     }
355 
356     /**
357      * Verify that lastUsed property is updated when a connection
358      * prepares a callable statement.
359      */
360     @Test
361     public void testLastUsedPrepareCall() throws Exception {
362         ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1));
363         ds.setMaxTotal(2);
364         try (Connection conn1 = ds.getConnection()) {
365             Thread.sleep(500);
366             try (CallableStatement cs = conn1.prepareCall("{call home}")) {
367                 // Should reset lastUsed
368             }
369             Thread.sleep(800);
370             final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup
371             try (CallableStatement cs = conn1.prepareCall("{call home}")) {
372                 // Should still be OK
373             }
374             conn2.close();
375             Thread.sleep(500);
376             try (CallableStatement cs = conn1.prepareCall("{call home}")) {
377                 // reset
378             }
379             Thread.sleep(800);
380             try (Connection c = ds.getConnection()) {
381                 // empty
382             }
383             try (Statement s = conn1.createStatement()) {
384                 // trigger abandoned cleanup again
385             }
386         }
387     }
388 
389     /**
390      * DBCP-343 - verify that using a DelegatingStatement updates
391      * the lastUsed on the parent connection
392      */
393     @Test
394     public void testLastUsedPreparedStatementUse() throws Exception {
395         ds.setRemoveAbandonedTimeout(Duration.ofSeconds(1));
396         ds.setMaxTotal(2);
397         try (Connection conn1 = ds.getConnection();
398                 Statement st = conn1.createStatement()) {
399             final String querySQL = "SELECT 1 FROM DUAL";
400             Thread.sleep(500);
401             Assertions.assertNotNull(st.executeQuery(querySQL)); // Should reset lastUsed
402             Thread.sleep(800);
403             final Connection conn2 = ds.getConnection(); // triggers abandoned cleanup
404             Assertions.assertNotNull(st.executeQuery(querySQL)); // Should still be OK
405             conn2.close();
406             Thread.sleep(500);
407             st.executeUpdate(""); // Should also reset
408             Thread.sleep(800);
409             try (Connection c = ds.getConnection()) {} // trigger abandoned cleanup again
410             try (Statement s = conn1.createStatement()) {}  // Connection should still be good
411         }
412     }
413 
414     /**
415      * DBCP-343 - verify additional operations reset lastUsed on
416      * the parent connection
417      */
418     @Test
419     public void testLastUsedUpdate() throws Exception {
420         try (DelegatingConnection<?> conn = (DelegatingConnection<?>) ds.getConnection();
421                 final PreparedStatement ps = conn.prepareStatement("");
422                 final CallableStatement cs = conn.prepareCall("");
423                 final Statement st = conn.prepareStatement("")) {
424             checkLastUsedStatement(ps, conn);
425             checkLastUsedPreparedStatement(ps, conn);
426             checkLastUsedStatement(cs, conn);
427             checkLastUsedPreparedStatement(cs, conn);
428             checkLastUsedStatement(st, conn);
429         }
430     }
431 }