Search code examples
javaoracle-databasejdbcmemory-leakshikaricp

HikariCP Connection Leak using unwrapped oracle.jdbc.driver.T4CConnection


I am having an issue with a resource leak using HikariCP with an Unwrapped Connection. A little explanation then the code.

I must use an unwrapped connection to access Oracle methods for oracle.sql.BFILE. This is streaming a binary out of an Oracle Directory.

DataSource Example:

private static DataSource unwrapDatasource;
public static synchronized DataSource getUnwrappedDataSource() {
    if (unwrapDatasource == null) {
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(50);
        config.setLeakDetectionThreshold(120000);
        config.setJdbcUrl(DATABASEURL);
        config.addDataSourceProperty("user", USERNAME);
        config.addDataSourceProperty("password", DBPASSWORD);
        config.addDataSourceProperty("driverType", "thin");
        config.setDriverClassName("oracle.jdbc.pool.OracleDataSource");
        config.setMaxLifetime(300000);
        config.setPoolName("UNWRAP");
        unwrapDatasource = new HikariDataSource(config);
    }

    return unwrapDatasource;
}

  public static Connection getUnwrappedConnection() {
    Connection con = null;
    try {
        con = this.getUnwrappedDataSource().getConnection().unwrap(oracle.jdbc.driver.OracleConnection.class);
    } catch (SQLException ex) {
        //logger junk ommitted for brevity
    }
    return con;
}

Using HikariCP-java7-2.4.12 as the app is very old and running on tomcat-6. Here is sample of the Connection that gets reported as Leaked with

    com.zaxxer.hikari.pool.ProxyLeakTask.run(poolProxyLeak.java:91) : <Connection leak detection triggered for {}, stack trace follows>
java.lang.Exception: Apparent connection leak detected
    at myapp.package.obfuscated.getUnwrapConnection(DataSourceConstants.java:253)
    at myapp.package.obfuscated.BB.execute(DatabaseCallingClass2.java:106)
    at myapp.package.obfuscated.BB.execute(DatabaseCallingClass2.java:85)
    at myapp.package.obfuscated.AA.execute(DataBaseCallingClass.java:52)
    at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:425)
    at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:228)
    at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1913)
    at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:462)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:643)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:723)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.displaytag.filter.ResponseOverrideFilter.doFilter(ResponseOverrideFilter.java:123)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:563)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:610)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:503)
    at java.lang.Thread.run(Thread.java:745)

Here is the Connection and how it is used:

     Connection con = null;
     PreparedStatement pstmt = null;
     OracleResultSet rs = null;
     InputStream inputStream = null;
     StringBuilder stringBuilder = new StringBuilder(128);
     try {
            con = DataSourceConstants.getUnwrappedConnection();

            pstmt = con.prepareStatement("SELECT BFILENAME('ORACLE_DIR', 'fileDownload.12345') AS BFILE from dual" );  //hardcoded for example
            rs = (OracleResultSet) pstmt.executeQuery();

            rs.next();  //Assumption if rs.next() blows up, catch would grab it and resources would all attempt to close in finally block

            bfile = rs.getBFILE(1);
            bfile.open();
            inputStream = bfile.getBinaryStream();
            char c;
            long size = bfile.length();

            int i = 0;
            do {
                c= (char) inputStream.read();
                stringBuilder.append(c);
            } while (++i < size);

            } catch (Exception ex) {
              //logger ommitted but not throwing here
            } finally {
             //cleanup resources
             try { inputStream.close(); } catch (Exception ex) {}
             try { bfile.close(); } catch (Exception ex) {}
             try { rs.close(); } catch (Exception ex) {}
             try { pstmt.close(); } catch (Exception ex) {}
             try {con.close();  } catch (Exception ex){}
            }
       //use stringBuilder later in built output.

So The above retrieves an unwrapped T4CConnection so that I can use the OracleResultSet and BFILE / getBFILE(). This works and I get the result that I want, however two separate Classes using this structure to retrieve a BFILE are both Leaking, and another method that doesn't use BFILE but is using an Unwrapped Connection to use OracleCallableStatement is also leaking. I have instantiated 3 DataSources to split all of the connectors by type/function, the only pool that Leaks is the one using Datasource.getConnection().unwrap(oracle.jdbc.driver.OracleConnection.class) for its connectors.

Am I doing something wrong with an unwrapped connection? Why is this the only one that leaks? Is this a known driver bug? (I haven't come up with any articles on it) BFILE doesn't seem to be very popular...


Solution

  • What you are doing in getUnwrappedConnection() is not what you should be doing: if you are unwrapping, then you need to make sure you also hold on to the connection pool connection, as closing that connection is what returns it to the pool. So first get the connection from the pool, and only unwrap at the point you really need it, and then, when you are done, close the original connection you obtained from the data source.

    Do not close the unwrapped connection, because that will close the actual physical connection, and that will defeat the purpose of using a connection pool.

    So in short:

    try (Connection connection = dataSource.getConnection()) {
        OracleConnection unwrapped = connection.unwrap(oracle.jdbc.driver.OracleConnection.class)
    
        ...
    
        // Do not close (or use try-with-resources) on unwrapped
    }