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