1+ 'use strict'
2+
3+ /* global describe after it */
4+
5+ const assert = require ( 'assert' )
6+ const { TestEnv } = require ( './env/test-env' )
7+
8+ describe ( 'pool growth efficiency' , function ( ) {
9+ this . timeout ( 30000 )
10+ const env = new TestEnv ( )
11+ const sql = env . sql
12+
13+ this . beforeEach ( done => {
14+ env . open ( ) . then ( ( ) => done ( ) ) . catch ( done )
15+ } )
16+
17+ this . afterEach ( done => {
18+ env . close ( ) . then ( ( ) => done ( ) ) . catch ( done )
19+ } )
20+
21+ it ( 'pool should not grow when idle connections are available' , async function handler ( ) {
22+ const debugMessages = [ ]
23+ const growthMessages = [ ]
24+ const crankMessages = [ ]
25+
26+ const pool = new sql . Pool ( {
27+ connectionString : env . connectionString ,
28+ floor : 10 ,
29+ ceiling : 20 ,
30+ heartbeatSecs : 1 ,
31+ inactivityTimeoutSecs : 10
32+ } )
33+
34+ // Capture debug messages to verify behavior
35+ pool . on ( 'debug' , msg => {
36+ debugMessages . push ( msg )
37+ console . log ( '[DEBUG]' , msg )
38+ if ( msg . includes ( 'grow creates' ) ) {
39+ growthMessages . push ( msg )
40+ }
41+ if ( msg . includes ( 'crank needs to grow pool' ) ) {
42+ crankMessages . push ( msg )
43+ }
44+ } )
45+
46+ await pool . promises . open ( )
47+
48+ // Wait a moment to ensure all initial connections are established
49+ await new Promise ( resolve => setTimeout ( resolve , 500 ) )
50+
51+ // Submit 10 queries (matching the floor/initial connections)
52+ const queries = [ ]
53+ for ( let i = 0 ; i < 10 ; i ++ ) {
54+ queries . push ( pool . promises . query ( `SELECT ${ i } as num, @@SPID as spid` ) )
55+ }
56+
57+ // Wait for all queries to complete
58+ const results1 = await Promise . all ( queries )
59+ assert . strictEqual ( results1 . length , 10 )
60+
61+ // Wait a moment for connections to return to idle
62+ await new Promise ( resolve => setTimeout ( resolve , 100 ) )
63+
64+ // Submit another batch of 10 queries
65+ const queries2 = [ ]
66+ for ( let i = 10 ; i < 20 ; i ++ ) {
67+ queries2 . push ( pool . promises . query ( `SELECT ${ i } as num, @@SPID as spid` ) )
68+ }
69+
70+ const results2 = await Promise . all ( queries2 )
71+ assert . strictEqual ( results2 . length , 10 )
72+
73+ await pool . promises . close ( )
74+
75+ // Debug output
76+ console . log ( '\n=== Test Summary ===' )
77+ console . log ( 'Growth messages:' , growthMessages )
78+ console . log ( 'Crank messages:' , crankMessages )
79+ console . log ( 'Total growth events:' , growthMessages . length )
80+ console . log ( 'Total crank growth requests:' , crankMessages . length )
81+
82+ // Verify behavior - the pool may grow in stages during initial open
83+ const totalGrowthConnections = growthMessages . reduce ( ( sum , msg ) => {
84+ const match = msg . match ( / g r o w c r e a t e s ( \d + ) c o n n e c t i o n s / )
85+ return sum + ( match ? parseInt ( match [ 1 ] ) : 0 )
86+ } , 0 )
87+
88+ console . log ( 'Total connections created:' , totalGrowthConnections )
89+
90+ // We expect the pool to create connections up to the floor (10)
91+ assert ( totalGrowthConnections >= 10 , `Should have created at least floor (10) connections, but created ${ totalGrowthConnections } ` )
92+
93+ // The key test: after initial setup, no additional growth should occur
94+ // when we're just reusing idle connections
95+ assert . strictEqual ( crankMessages . length , 0 , 'Should not have needed to grow pool when idle connections were available' )
96+ } )
97+
98+ it ( 'pool should grow only when all connections are busy' , async function handler ( ) {
99+ const debugMessages = [ ]
100+ const growthMessages = [ ]
101+ const crankMessages = [ ]
102+
103+ const pool = new sql . Pool ( {
104+ connectionString : env . connectionString ,
105+ floor : 2 ,
106+ ceiling : 10 ,
107+ scalingStrategy : 'gradual' ,
108+ scalingIncrement : 2
109+ } )
110+
111+ pool . on ( 'debug' , msg => {
112+ debugMessages . push ( msg )
113+ console . log ( '[DEBUG]' , msg )
114+ if ( msg . includes ( 'grow creates' ) ) {
115+ growthMessages . push ( msg )
116+ }
117+ if ( msg . includes ( 'crank needs to grow pool' ) ) {
118+ crankMessages . push ( msg )
119+ }
120+ } )
121+
122+ await pool . promises . open ( )
123+
124+ // Wait for initial connections
125+ await new Promise ( resolve => setTimeout ( resolve , 500 ) )
126+
127+ // Submit queries that will hold connections busy
128+ const slowQueries = [ ]
129+ for ( let i = 0 ; i < 5 ; i ++ ) {
130+ slowQueries . push ( pool . promises . query ( "WAITFOR DELAY '00:00:01'; SELECT @@SPID as spid" ) )
131+ }
132+
133+ // Give a moment for queries to start
134+ await new Promise ( resolve => setTimeout ( resolve , 100 ) )
135+
136+ // Wait for queries to complete
137+ await Promise . all ( slowQueries )
138+ await pool . promises . close ( )
139+
140+ console . log ( '\n=== Test Summary ===' )
141+ console . log ( 'Growth messages:' , growthMessages )
142+ console . log ( 'Crank growth messages:' , crankMessages )
143+
144+ // Verify that growth occurred when needed
145+ // We submitted 5 slow queries but only have 2 initial connections
146+ // So the pool should have grown
147+ assert ( crankMessages . length > 0 || growthMessages . length > 1 , 'Pool should grow when all connections are busy' )
148+ } )
149+
150+ it ( 'pool with burst workload should reuse connections efficiently' , async function handler ( ) {
151+ const spidsUsed = new Set ( )
152+ const debugMessages = [ ]
153+
154+ const pool = new sql . Pool ( {
155+ connectionString : env . connectionString ,
156+ floor : 5 ,
157+ ceiling : 20
158+ } )
159+
160+ pool . on ( 'debug' , msg => {
161+ debugMessages . push ( msg )
162+ if ( msg . includes ( 'checkout' ) || msg . includes ( 'checkin' ) || msg . includes ( 'grow' ) ) {
163+ console . log ( '[DEBUG]' , msg )
164+ }
165+ } )
166+
167+ await pool . promises . open ( )
168+
169+ // Wait for initial connections
170+ await new Promise ( resolve => setTimeout ( resolve , 1000 ) )
171+
172+ // Burst 1: Submit 5 queries
173+ console . log ( '\nBurst 1: 5 queries' )
174+ const burst1 = [ ]
175+ for ( let i = 0 ; i < 5 ; i ++ ) {
176+ burst1 . push ( pool . promises . query ( 'SELECT @@SPID as spid' ) )
177+ }
178+
179+ const results1 = await Promise . all ( burst1 )
180+ results1 . forEach ( r => spidsUsed . add ( r . first [ 0 ] . spid ) )
181+ console . log ( 'SPIDs after burst 1:' , Array . from ( spidsUsed ) )
182+
183+ // Wait for connections to return to idle
184+ await new Promise ( resolve => setTimeout ( resolve , 500 ) )
185+
186+ // Burst 2: Submit another 5 queries
187+ console . log ( '\nBurst 2: 5 queries' )
188+ const burst2 = [ ]
189+ for ( let i = 0 ; i < 5 ; i ++ ) {
190+ burst2 . push ( pool . promises . query ( 'SELECT @@SPID as spid' ) )
191+ }
192+
193+ const results2 = await Promise . all ( burst2 )
194+ const spidsBeforeBurst2 = spidsUsed . size
195+ results2 . forEach ( r => spidsUsed . add ( r . first [ 0 ] . spid ) )
196+ console . log ( 'SPIDs after burst 2:' , Array . from ( spidsUsed ) )
197+ console . log ( 'New SPIDs in burst 2:' , spidsUsed . size - spidsBeforeBurst2 )
198+
199+ await pool . promises . close ( )
200+
201+ // Verify that we reused connections (should have at most 5 unique SPIDs)
202+ console . log ( '\nUnique SPIDs used:' , spidsUsed . size )
203+ assert ( spidsUsed . size <= 5 , `Should reuse connections efficiently, but used ${ spidsUsed . size } unique connections` )
204+ } )
205+ } )
0 commit comments