@@ -21,6 +21,7 @@ import (
21
21
"errors"
22
22
"fmt"
23
23
"io"
24
+ "math/rand/v2"
24
25
"reflect"
25
26
"runtime"
26
27
"sort"
@@ -497,9 +498,8 @@ type DB struct {
497
498
498
499
mu sync.Mutex // protects following fields
499
500
freeConn []* driverConn // free connections ordered by returnedAt oldest to newest
500
- connRequests map [uint64 ]chan connRequest
501
- nextRequest uint64 // Next key to use in connRequests.
502
- numOpen int // number of opened and pending open connections
501
+ connRequests connRequestSet
502
+ numOpen int // number of opened and pending open connections
503
503
// Used to signal the need for new connections
504
504
// a goroutine running connectionOpener() reads on this chan and
505
505
// maybeOpenNewConnections sends on the chan (one send per needed connection)
@@ -814,11 +814,10 @@ func (t dsnConnector) Driver() driver.Driver {
814
814
func OpenDB (c driver.Connector ) * DB {
815
815
ctx , cancel := context .WithCancel (context .Background ())
816
816
db := & DB {
817
- connector : c ,
818
- openerCh : make (chan struct {}, connectionRequestQueueSize ),
819
- lastPut : make (map [* driverConn ]string ),
820
- connRequests : make (map [uint64 ]chan connRequest ),
821
- stop : cancel ,
817
+ connector : c ,
818
+ openerCh : make (chan struct {}, connectionRequestQueueSize ),
819
+ lastPut : make (map [* driverConn ]string ),
820
+ stop : cancel ,
822
821
}
823
822
824
823
go db .connectionOpener (ctx )
@@ -922,9 +921,7 @@ func (db *DB) Close() error {
922
921
}
923
922
db .freeConn = nil
924
923
db .closed = true
925
- for _ , req := range db .connRequests {
926
- close (req )
927
- }
924
+ db .connRequests .CloseAndRemoveAll ()
928
925
db .mu .Unlock ()
929
926
for _ , fn := range fns {
930
927
err1 := fn ()
@@ -1223,7 +1220,7 @@ func (db *DB) Stats() DBStats {
1223
1220
// If there are connRequests and the connection limit hasn't been reached,
1224
1221
// then tell the connectionOpener to open new connections.
1225
1222
func (db * DB ) maybeOpenNewConnections () {
1226
- numRequests := len ( db .connRequests )
1223
+ numRequests := db .connRequests . Len ( )
1227
1224
if db .maxOpen > 0 {
1228
1225
numCanOpen := db .maxOpen - db .numOpen
1229
1226
if numRequests > numCanOpen {
@@ -1297,14 +1294,6 @@ type connRequest struct {
1297
1294
1298
1295
var errDBClosed = errors .New ("sql: database is closed" )
1299
1296
1300
- // nextRequestKeyLocked returns the next connection request key.
1301
- // It is assumed that nextRequest will not overflow.
1302
- func (db * DB ) nextRequestKeyLocked () uint64 {
1303
- next := db .nextRequest
1304
- db .nextRequest ++
1305
- return next
1306
- }
1307
-
1308
1297
// conn returns a newly-opened or cached *driverConn.
1309
1298
func (db * DB ) conn (ctx context.Context , strategy connReuseStrategy ) (* driverConn , error ) {
1310
1299
db .mu .Lock ()
@@ -1352,8 +1341,7 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
1352
1341
// Make the connRequest channel. It's buffered so that the
1353
1342
// connectionOpener doesn't block while waiting for the req to be read.
1354
1343
req := make (chan connRequest , 1 )
1355
- reqKey := db .nextRequestKeyLocked ()
1356
- db .connRequests [reqKey ] = req
1344
+ delHandle := db .connRequests .Add (req )
1357
1345
db .waitCount ++
1358
1346
db .mu .Unlock ()
1359
1347
@@ -1365,16 +1353,26 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
1365
1353
// Remove the connection request and ensure no value has been sent
1366
1354
// on it after removing.
1367
1355
db .mu .Lock ()
1368
- delete ( db .connRequests , reqKey )
1356
+ deleted := db .connRequests . Delete ( delHandle )
1369
1357
db .mu .Unlock ()
1370
1358
1371
1359
db .waitDuration .Add (int64 (time .Since (waitStart )))
1372
1360
1373
- select {
1374
- default :
1375
- case ret , ok := <- req :
1376
- if ok && ret .conn != nil {
1377
- db .putConn (ret .conn , ret .err , false )
1361
+ // If we failed to delete it, that means something else
1362
+ // grabbed it and is about to send on it.
1363
+ if ! deleted {
1364
+ // TODO(bradfitz): rather than this best effort select, we
1365
+ // should probably start a goroutine to read from req. This best
1366
+ // effort select existed before the change to check 'deleted'.
1367
+ // But if we know for sure it wasn't deleted and a sender is
1368
+ // outstanding, we should probably block on req (in a new
1369
+ // goroutine) to get the connection back.
1370
+ select {
1371
+ default :
1372
+ case ret , ok := <- req :
1373
+ if ok && ret .conn != nil {
1374
+ db .putConn (ret .conn , ret .err , false )
1375
+ }
1378
1376
}
1379
1377
}
1380
1378
return nil , ctx .Err ()
@@ -1530,13 +1528,7 @@ func (db *DB) putConnDBLocked(dc *driverConn, err error) bool {
1530
1528
if db .maxOpen > 0 && db .numOpen > db .maxOpen {
1531
1529
return false
1532
1530
}
1533
- if c := len (db .connRequests ); c > 0 {
1534
- var req chan connRequest
1535
- var reqKey uint64
1536
- for reqKey , req = range db .connRequests {
1537
- break
1538
- }
1539
- delete (db .connRequests , reqKey ) // Remove from pending requests.
1531
+ if req , ok := db .connRequests .TakeRandom (); ok {
1540
1532
if err == nil {
1541
1533
dc .inUse = true
1542
1534
}
@@ -3529,3 +3521,104 @@ func withLock(lk sync.Locker, fn func()) {
3529
3521
defer lk .Unlock () // in case fn panics
3530
3522
fn ()
3531
3523
}
3524
+
3525
+ // connRequestSet is a set of chan connRequest that's
3526
+ // optimized for:
3527
+ //
3528
+ // - adding an element
3529
+ // - removing an element (only by the caller who added it)
3530
+ // - taking (get + delete) a random element
3531
+ //
3532
+ // We previously used a map for this but the take of a random element
3533
+ // was expensive, making mapiters. This type avoids a map entirely
3534
+ // and just uses a slice.
3535
+ type connRequestSet struct {
3536
+ // s are the elements in the set.
3537
+ s []connRequestAndIndex
3538
+ }
3539
+
3540
+ type connRequestAndIndex struct {
3541
+ // req is the element in the set.
3542
+ req chan connRequest
3543
+
3544
+ // curIdx points to the current location of this element in
3545
+ // connRequestSet.s. It gets set to -1 upon removal.
3546
+ curIdx * int
3547
+ }
3548
+
3549
+ // CloseAndRemoveAll closes all channels in the set
3550
+ // and clears the set.
3551
+ func (s * connRequestSet ) CloseAndRemoveAll () {
3552
+ for _ , v := range s .s {
3553
+ close (v .req )
3554
+ }
3555
+ s .s = nil
3556
+ }
3557
+
3558
+ // Len returns the length of the set.
3559
+ func (s * connRequestSet ) Len () int { return len (s .s ) }
3560
+
3561
+ // connRequestDelHandle is an opaque handle to delete an
3562
+ // item from calling Add.
3563
+ type connRequestDelHandle struct {
3564
+ idx * int // pointer to index; or -1 if not in slice
3565
+ }
3566
+
3567
+ // Add adds v to the set of waiting requests.
3568
+ // The returned connRequestDelHandle can be used to remove the item from
3569
+ // the set.
3570
+ func (s * connRequestSet ) Add (v chan connRequest ) connRequestDelHandle {
3571
+ idx := len (s .s )
3572
+ // TODO(bradfitz): for simplicity, this always allocates a new int-sized
3573
+ // allocation to store the index. But generally the set will be small and
3574
+ // under a scannable-threshold. As an optimization, we could permit the *int
3575
+ // to be nil when the set is small and should be scanned. This works even if
3576
+ // the set grows over the threshold with delete handles outstanding because
3577
+ // an element can only move to a lower index. So if it starts with a nil
3578
+ // position, it'll always be in a low index and thus scannable. But that
3579
+ // can be done in a follow-up change.
3580
+ idxPtr := & idx
3581
+ s .s = append (s .s , connRequestAndIndex {v , idxPtr })
3582
+ return connRequestDelHandle {idxPtr }
3583
+ }
3584
+
3585
+ // Delete removes an element from the set.
3586
+ //
3587
+ // It reports whether the element was deleted. (It can return false if a caller
3588
+ // of TakeRandom took it meanwhile, or upon the second call to Delete)
3589
+ func (s * connRequestSet ) Delete (h connRequestDelHandle ) bool {
3590
+ idx := * h .idx
3591
+ if idx < 0 {
3592
+ return false
3593
+ }
3594
+ s .deleteIndex (idx )
3595
+ return true
3596
+ }
3597
+
3598
+ func (s * connRequestSet ) deleteIndex (idx int ) {
3599
+ // Mark item as deleted.
3600
+ * (s .s [idx ].curIdx ) = - 1
3601
+ // Copy last element, updating its position
3602
+ // to its new home.
3603
+ if idx < len (s .s )- 1 {
3604
+ last := s .s [len (s .s )- 1 ]
3605
+ * last .curIdx = idx
3606
+ s .s [idx ] = last
3607
+ }
3608
+ // Zero out last element (for GC) before shrinking the slice.
3609
+ s .s [len (s .s )- 1 ] = connRequestAndIndex {}
3610
+ s .s = s .s [:len (s .s )- 1 ]
3611
+ }
3612
+
3613
+ // TakeRandom returns and removes a random element from s
3614
+ // and reports whether there was one to take. (It returns ok=false
3615
+ // if the set is empty.)
3616
+ func (s * connRequestSet ) TakeRandom () (v chan connRequest , ok bool ) {
3617
+ if len (s .s ) == 0 {
3618
+ return nil , false
3619
+ }
3620
+ pick := rand .IntN (len (s .s ))
3621
+ e := s .s [pick ]
3622
+ s .deleteIndex (pick )
3623
+ return e .req , true
3624
+ }
0 commit comments