其他分享
首页 > 其他分享> > 使用try-with-resources和HikariCP连接泄漏

使用try-with-resources和HikariCP连接泄漏

作者:互联网

以下代码触发连接泄漏警告.我使用的是OpenJDK 1.7.0_80和HikariCP 2.2.5(也可以使用最新的HikariCP 2.3.9重现).我错过了什么吗?

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.junit.Test;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

public class HikaryAutoCloseTest {
    private static HikariDataSource configureDataSource() {
        try {
            Class.forName("org.postgresql.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://127.0.0.1/DATABASE");
        config.setUsername("USERNAME");
        config.setPassword("PASSWORD");

        config.setLeakDetectionThreshold(10000);

        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("useServerPrepStmts", "true");

        return new HikariDataSource(config);
    }

    @Test
    public void testHikaryAutoClose() {
        HikariDataSource dataSource = configureDataSource();

        boolean ret = shouldNotLeakConnection(dataSource);
        if (ret) {
            System.out.println("UPDATE okey");
        }

        /* Wait for LeakTask to complain */
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Exiting");
    }

    private boolean shouldNotLeakConnection(HikariDataSource dataSource) {
        String sql = "INSERT INTO error_logs (description) values (?)";

        try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql);) {
            stmt.setString(1, "description");
            return stmt.executeUpdate() != 0; // minor changes to this line remove the leak
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

更新:略微更改return语句消除了泄漏:

private boolean shouldNotLeakConnection(HikariDataSource dataSource) {
    String sql = "INSERT INTO error_logs (description) values (?)";

    try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql);) {
        stmt.setString(1, "description");
        boolean ret = stmt.executeUpdate() != 0;
        return ret;
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

解决方法:

似乎问题来自于AspectJ 1.7.2中的一个错误.它产生了以下字节码:

  private boolean shouldNotLeakConnection(com.zaxxer.hikari.HikariDataSource);
    Code:
       0: ldc           #115                // String INSERT INTO error_logs (description) values (?)
       2: astore_2      
       3: aconst_null   
       4: astore_3      
       5: aconst_null   
       6: astore        4
       8: aload_1       
       9: invokevirtual #117                // Method com/zaxxer/hikari/HikariDataSource.getConnection:()Ljava/sql/Connection;
      12: astore        5
      14: aload         5
      16: aload_2       
      17: invokeinterface #121,  2          // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;)Ljava/sql/PreparedStatement;
      22: astore        6
      24: aload         6
      26: iconst_1      
      27: ldc           #127                // String description
      29: invokeinterface #129,  3          // InterfaceMethod java/sql/PreparedStatement.setString:(ILjava/lang/String;)V
      34: aload         6
      36: invokeinterface #135,  1          // InterfaceMethod java/sql/PreparedStatement.executeUpdate:()I
      41: ifeq          46
      44: iconst_1      
      45: ireturn       
      46: iconst_0      
      47: aload         6
      49: ifnull        59
      52: aload         6
      54: invokeinterface #139,  1          // InterfaceMethod java/sql/PreparedStatement.close:()V
      59: aload         5
      61: ifnull        71
      64: aload         5
      66: invokeinterface #142,  1          // InterfaceMethod java/sql/Connection.close:()V
      71: ireturn       
      72: astore_3      
      73: aload         6
      75: ifnull        85
      78: aload         6
      80: invokeinterface #139,  1          // InterfaceMethod java/sql/PreparedStatement.close:()V
      85: aload_3       
      86: athrow        
      87: astore        4
      89: aload_3       
      90: ifnonnull     99
      93: aload         4
      95: astore_3      
      96: goto          111
      99: aload_3       
     100: aload         4
     102: if_acmpeq     111
     105: aload_3       
     106: aload         4
     108: invokevirtual #143                // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     111: aload         5
     113: ifnull        123
     116: aload         5
     118: invokeinterface #142,  1          // InterfaceMethod java/sql/Connection.close:()V
     123: aload_3       
     124: athrow        
     125: astore        4
     127: aload_3       
     128: ifnonnull     137
     131: aload         4
     133: astore_3      
     134: goto          149
     137: aload_3       
     138: aload         4
     140: if_acmpeq     149
     143: aload_3       
     144: aload         4
     146: invokevirtual #143                // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     149: aload_3       
     150: athrow        
     151: astore_3      
     152: new           #25                 // class java/lang/RuntimeException
     155: dup           
     156: aload_3       
     157: invokespecial #27                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/Throwable;)V
     160: athrow        
    Exception table:
       from    to  target type
          24    47    72   any
          71    72    72   any
          14    59    87   any
          71    87    87   any
           8   125   125   any
           3    71   151   Class java/sql/SQLException
          72   151   151   Class java/sql/SQLException

注意第45行,ireturn跳过两个close()方法.

使用AspectJ 1.8.6,生成正确的字节码:

  private boolean shouldNotLeakConnection(com.zaxxer.hikari.HikariDataSource);
    Code:
       0: ldc           #115                // String INSERT INTO error_logs (description) values (?)
       2: astore_2      
       3: aconst_null   
       4: astore_3      
       5: aconst_null   
       6: astore        4
       8: aload_1       
       9: invokevirtual #117                // Method com/zaxxer/hikari/HikariDataSource.getConnection:()Ljava/sql/Connection;
      12: astore        5
      14: aload         5
      16: aload_2       
      17: invokeinterface #121,  2          // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;)Ljava/sql/PreparedStatement;
      22: astore        6
      24: aload         6
      26: iconst_1      
      27: ldc           #127                // String description
      29: invokeinterface #129,  3          // InterfaceMethod java/sql/PreparedStatement.setString:(ILjava/lang/String;)V
      34: aload         6
      36: invokeinterface #135,  1          // InterfaceMethod java/sql/PreparedStatement.executeUpdate:()I
      41: ifeq          48
      44: iconst_1      
      45: goto          49
      48: iconst_0      
      49: aload         6
      51: ifnull        61
      54: aload         6
      56: invokeinterface #139,  1          // InterfaceMethod java/sql/PreparedStatement.close:()V
      61: aload         5
      63: ifnull        73
      66: aload         5
      68: invokeinterface #142,  1          // InterfaceMethod java/sql/Connection.close:()V
      73: ireturn       
      74: astore_3      
      75: aload         6
      77: ifnull        87
      80: aload         6
      82: invokeinterface #139,  1          // InterfaceMethod java/sql/PreparedStatement.close:()V
      87: aload_3       
      88: athrow        
      89: astore        4
      91: aload_3       
      92: ifnonnull     101
      95: aload         4
      97: astore_3      
      98: goto          113
     101: aload_3       
     102: aload         4
     104: if_acmpeq     113
     107: aload_3       
     108: aload         4
     110: invokevirtual #143                // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     113: aload         5
     115: ifnull        125
     118: aload         5
     120: invokeinterface #142,  1          // InterfaceMethod java/sql/Connection.close:()V
     125: aload_3       
     126: athrow        
     127: astore        4
     129: aload_3       
     130: ifnonnull     139
     133: aload         4
     135: astore_3      
     136: goto          151
     139: aload_3       
     140: aload         4
     142: if_acmpeq     151
     145: aload_3       
     146: aload         4
     148: invokevirtual #143                // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     151: aload_3       
     152: athrow        
     153: astore_3      
     154: new           #25                 // class java/lang/RuntimeException
     157: dup           
     158: aload_3       
     159: invokespecial #27                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/Throwable;)V
     162: athrow        
    Exception table:
       from    to  target type
          24    49    74   any
          73    74    74   any
          14    61    89   any
          73    89    89   any
           8   127   127   any
           3    73   153   Class java/sql/SQLException
          74   153   153   Class java/sql/SQLException

请注意,在第45行,ireturn被替换为不跳过两个close()方法的goto.

谢谢@brettw帮助我缩小问题的范围.

标签:java,openjdk,try-with-resources,hikaricp
来源: https://codeday.me/bug/20190623/1273179.html