几行代码轻松复现druid连接泄露的BUG之PhyTimeout

本文涉及的产品
性能测试 PTS,5000VUM额度
注册配置 MSE Nacos/ZooKeeper,118元/月
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 几行代码轻松复现druid连接泄露的BUG之PhyTimeout

背景介绍

一次druid数据库连接池连接泄露的排查分析介绍了连接泄露的分析排查过程,但是对造成连接泄露的场景描述的不是太详细,下面通过代码的方式来复现连接泄露的case。

复现过程

连接泄露场景

模拟了在设置了phyTimeoutMillis参数情况下,连接泄露的场景。

连接泄露模拟代码

import com.alibaba.druid.pool.DruidConnectionHolder;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import java.util.List;
import java.util.Map;
public class DruidAbandonedCase4PhyTimeout {
    public static void main(String[] args) throws Exception{
        DruidDataSource dataSource =  new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false");
        dataSource.setMinIdle(2);
        // 保证不会触发DestroyTask,以便手动调用shrink
        dataSource.setTimeBetweenEvictionRunsMillis(1 * 60 * 60 * 1000);
        // 物理连接超时时长(超过该阈值会close掉物理连接)
        dataSource.setPhyTimeoutMillis(1000);
        dataSource.setMinEvictableIdleTimeMillis(500);
        dataSource.setKeepAliveBetweenTimeMillis(501);
        dataSource.init();
        DruidPooledConnection connection1 = dataSource.getConnection();
        DruidConnectionHolder holder1 = connection1.getConnectionHolder();
        System.out.println(holder1);
        try{
            //假装该连接使用了500ms,要小于phyTimeoutMillis,以免归还连接的时候被discard了
            Thread.sleep(500L);
        }catch (Exception e){
            e.printStackTrace();
        }
        DruidPooledConnection connection2 = dataSource.getConnection();
        DruidConnectionHolder holder2 = connection2.getConnectionHolder();
        System.out.println(holder2);
        connection2.close();
        connection1.close();
        System.out.println("调用shrink【之前】,连接池中连接情况,begin");
        List<Map<String, Object>> conns = dataSource.getPoolingConnectionInfo();
        for(Map<String,Object> conn : conns){
            System.out.println(conn.get("id"));
        }
        System.out.println("调用shrink【之前】,连接池中连接情况,end");
        try{
            // 让连接1的物理连接时间超过phyTimeoutMillis,以观察连接1是否会被连接池释放掉
            Thread.sleep(501L);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println();
        System.out.println("物理连接时间,begin");
        long ctm = System.currentTimeMillis();
        System.out.println(holder1 + " phyConnectTimeMillis:" + (ctm - holder1.getConnectTimeMillis()) + " ,idleMillis:" + (ctm - holder1.getLastActiveTimeMillis()));
        System.out.println(holder2 + " phyConnectTimeMillis:" + (ctm - holder2.getConnectTimeMillis()) + " ,idleMillis:" + (ctm - holder2.getLastActiveTimeMillis()));
        System.out.println("物理连接时间,end");
        System.out.println();
        // 手动执行shrink
        dataSource.shrink(true);
        System.out.println("调用shrink【之后】,连接池中连接情况,begin");
        conns = dataSource.getPoolingConnectionInfo();
        for(Map<String,Object> conn : conns){
            System.out.println(conn.get("id"));
        }
        System.out.println("调用shrink【之后】,连接池中连接情况,end");
        System.out.println();
        System.out.println(holder1 + " isClosed:" + holder1.getConnection().isClosed());
        System.out.println(holder2 + " isClosed:" + holder2.getConnection().isClosed());
    }
}

运行以上程序,输出结果如下:

{ID:26004719, ConnectTime:"2023-10-10 12:40:04", UseCount:1, LastActiveTime:"2023-10-10 12:40:04"}
{ID:31314834, ConnectTime:"2023-10-10 12:40:05", UseCount:1, LastActiveTime:"2023-10-10 12:40:05"}
调用shrink【之前】,连接池中连接情况,begin
31314834
26004719
调用shrink【之前】,连接池中连接情况,end
物理连接时间,begin
{ID:26004719, ConnectTime:"2023-10-10 12:40:04", UseCount:1, LastActiveTime:"2023-10-10 12:40:05"} phyConnectTimeMillis:1011 ,idleMillis:501
{ID:31314834, ConnectTime:"2023-10-10 12:40:05", UseCount:1, LastActiveTime:"2023-10-10 12:40:05"} phyConnectTimeMillis:502 ,idleMillis:501
物理连接时间,end
调用shrink【之后】,连接池中连接情况,begin
26004719
调用shrink【之后】,连接池中连接情况,end
{ID:26004719, ConnectTime:"2023-10-10 12:40:04", UseCount:1, LastActiveTime:"2023-10-10 12:40:05"} isClosed:true
{ID:31314834, ConnectTime:"2023-10-10 12:40:05", UseCount:1, LastActiveTime:"2023-10-10 12:40:05"} isClosed:false

期望执行结果:

ID是26004719的连接应该从连接池中删除,ID是31314834的连接应该被连接池管理

实际执行结果:

ID是26004719的连接没有从连接池删除;ID是31314834的连接却从连接池删除了,且连接没有被关闭,即ID是31314834的连接既没有被连接池管理又没有被关闭,属于泄露的连接。

测试了druid 1.1.11,1.2.0,1.2.8 ~ 1.2.17,1.2.18~1.2.20,其中【1.1.11,1.2.0,1.2.8 ~ 1.2.17】都存在以上连接泄露的问题,1.2.18~1.2.20不存在以上连接泄露的问题了。

目录
相关文章
|
6月前
|
测试技术
无法复现的bug,如何处理?
无法复现的bug,如何处理?
439 0
|
druid 数据库
几行代码轻松复现druid连接泄露的BUG之keepalive
几行代码轻松复现druid连接泄露的BUG之keepalive
676 6
|
Arthas druid Java
一次druid数据库连接池连接泄露的排查分析
一次druid数据库连接池连接泄露的排查分析
1274 1
|
SQL 监控 druid
Druid未授权访问 漏洞复现
Druid未授权访问 漏洞复现
9763 0
|
druid 数据库
几行代码轻松复现druid连接泄露的BUG之onFatalError
几行代码轻松复现druid连接泄露的BUG之onFatalError
371 0
|
安全 Java fastjson
Log4j2漏洞复现&原理&补丁绕过
Log4j2漏洞复现&原理&补丁绕过
|
安全 IDE Java
Spring 新版本修复远程命令执行漏洞(CVE-2022-22965),墨菲安全开源工具可应急排查
Spring 新版本修复远程命令执行漏洞(CVE-2022-22965),墨菲安全开源工具可应急排查
Spring 新版本修复远程命令执行漏洞(CVE-2022-22965),墨菲安全开源工具可应急排查
|
Java
如何排查Java内存泄露(内附各种排查工具介绍)
今天刚刚才加一个故障review会议, 故障非常典型, 在google也可以找到相似案例介绍。 在排查问题的过程中,使用了大量的工具, 发现有问题的地方还不只一个,总结一下. (本篇文章不会重点描述案例本身,重点会介绍个人对java内存泄露问题的排查思路和各种工具的使用)。
22056 0
|
安全 开发者
网站总是存在漏洞怎么解决
为什么很多网站系统都存在这个安全漏洞,这里面我所指的一些网站都是一些小公司的,或者是一些个人的网站系统,比如说像阿里、腾讯、百度这些大公司,他们因为有自己的开发团队和相关的这个安全人员,他们的漏洞相对就非常非常的少。那么一个网站的话,一旦存在这个安全漏洞,它就有可能会造成巨大的这个经济损失。一般出现这个安全漏洞的网站的话,它会导致这个数据被恶意的去修改,或者是一些敏感的数据被盗取,从而造成经济的损失。
210 0
网站总是存在漏洞怎么解决
|
测试技术
软件测试面试题:在配置测试中,如何判断发现的缺陷是普通问题还是特定的配置问题?
软件测试面试题:在配置测试中,如何判断发现的缺陷是普通问题还是特定的配置问题?
117 0