@@ -5,20 +5,28 @@ const describe = require('mocha').describe
55const it = require ( 'mocha' ) . it
66const Pool = require ( '..' )
77
8- describe ( 'poison connection pool defense (_txStatus check) ' , function ( ) {
8+ describe ( 'leaked connection pool guard ' , function ( ) {
99 it ( 'removes a client with an open transaction on release' , async function ( ) {
10- const pool = new Pool ( { max : 1 } )
10+ const logMessages = [ ]
11+ const pool = new Pool ( {
12+ max : 1 ,
13+ log : ( msg ) => logMessages . push ( msg ) ,
14+ } )
1115 const client = await pool . connect ( )
1216 await client . query ( 'BEGIN' )
1317 expect ( client . _txStatus ) . to . be ( 'T' )
1418
1519 client . release ( )
1620 expect ( pool . totalCount ) . to . be ( 0 )
1721 expect ( pool . idleCount ) . to . be ( 0 )
22+ expect ( logMessages ) . to . contain ( 'remove client with leaked transaction' )
1823
19- // pool should still work by creating a fresh connection
24+ // pool recovers by creating a fresh connection
2025 const { rows } = await pool . query ( 'SELECT 1 as num' )
2126 expect ( rows [ 0 ] . num ) . to . be ( 1 )
27+ expect ( pool . totalCount ) . to . be ( 1 )
28+ expect ( pool . idleCount ) . to . be ( 1 )
29+
2230 await pool . end ( )
2331 } )
2432
@@ -45,9 +53,12 @@ describe('poison connection pool defense (_txStatus check)', function () {
4553 expect ( pool . totalCount ) . to . be ( 0 )
4654 expect ( pool . idleCount ) . to . be ( 0 )
4755
48- // pool should still work
56+ // pool recovers by creating a fresh connection
4957 const { rows } = await pool . query ( 'SELECT 1 as num' )
5058 expect ( rows [ 0 ] . num ) . to . be ( 1 )
59+ expect ( pool . totalCount ) . to . be ( 1 )
60+ expect ( pool . idleCount ) . to . be ( 1 )
61+
5162 await pool . end ( )
5263 } )
5364
@@ -57,7 +68,7 @@ describe('poison connection pool defense (_txStatus check)', function () {
5768 const clientB = await pool . connect ( )
5869 const clientC = await pool . connect ( )
5970
60- // Client A: open transaction (poisoned )
71+ // Client A: open transaction (leaked )
6172 await clientA . query ( 'BEGIN' )
6273 expect ( clientA . _txStatus ) . to . be ( 'T' )
6374
@@ -79,4 +90,50 @@ describe('poison connection pool defense (_txStatus check)', function () {
7990 expect ( pool . idleCount ) . to . be ( 2 )
8091 await pool . end ( )
8192 } )
93+
94+ describe ( 'pool.query' , function ( ) {
95+ it ( 'removes a client after pool.query leaks transaction via BEGIN' , async function ( ) {
96+ const logMessages = [ ]
97+ const pool = new Pool ( { max : 1 , log : ( msg ) => logMessages . push ( msg ) } )
98+
99+ await pool . query ( 'BEGIN' )
100+
101+ // Client auto-released with txStatus='T', should be removed
102+ expect ( pool . totalCount ) . to . be ( 0 )
103+ expect ( pool . idleCount ) . to . be ( 0 )
104+ expect ( logMessages ) . to . contain ( 'remove client with leaked transaction' )
105+
106+ // Verify pool recovers
107+ const { rows } = await pool . query ( 'SELECT 1 as num' )
108+ expect ( rows [ 0 ] . num ) . to . be ( 1 )
109+ expect ( pool . totalCount ) . to . be ( 1 )
110+ expect ( pool . idleCount ) . to . be ( 1 )
111+
112+ await pool . end ( )
113+ } )
114+
115+ it ( 'removes a client after pool.query in failed transaction state' , async function ( ) {
116+ const pool = new Pool ( { max : 1 } )
117+
118+ await pool . query ( 'BEGIN' )
119+
120+ try {
121+ await pool . query ( 'SELECT invalid_column FROM nonexistent_table' )
122+ } catch ( e ) {
123+ // Expected error
124+ }
125+
126+ // Client with txStatus='E' should be removed
127+ expect ( pool . totalCount ) . to . be ( 0 )
128+ expect ( pool . idleCount ) . to . be ( 0 )
129+
130+ // Pool recovers
131+ const { rows } = await pool . query ( 'SELECT 1 as num' )
132+ expect ( rows [ 0 ] . num ) . to . be ( 1 )
133+ expect ( pool . totalCount ) . to . be ( 1 )
134+ expect ( pool . idleCount ) . to . be ( 1 )
135+
136+ await pool . end ( )
137+ } )
138+ } )
82139} )
0 commit comments