| 581 | } |
| 582 | |
| 583 | func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) { |
| 584 | if p.closed() { |
| 585 | return nil, ErrClosed |
| 586 | } |
| 587 | |
| 588 | if p.cfg.MaxActiveConns > 0 && p.poolSize.Load() >= p.cfg.MaxActiveConns { |
| 589 | return nil, ErrPoolExhausted |
| 590 | } |
| 591 | |
| 592 | // Protect against nil context due to race condition in queuedNewConn |
| 593 | // where the context can be set to nil after timeout/cancellation |
| 594 | if ctx == nil { |
| 595 | ctx = context.Background() |
| 596 | } |
| 597 | |
| 598 | // Do not apply DialTimeout via context here; dialConn applies DialTimeout per attempt. |
| 599 | // We still propagate ctx so callers can cancel explicitly. |
| 600 | cn, err := p.dialConn(ctx, pooled) |
| 601 | if err != nil { |
| 602 | return nil, err |
| 603 | } |
| 604 | |
| 605 | // NOTE: Connection is in CREATED state and will be initialized by redis.go:initConn() |
| 606 | // when first used. Do NOT transition to IDLE here - that happens after initialization completes. |
| 607 | // The state machine flow is: CREATED → INITIALIZING (in initConn) → IDLE (after init success) |
| 608 | |
| 609 | if p.cfg.MaxActiveConns > 0 && p.poolSize.Load() > p.cfg.MaxActiveConns { |
| 610 | _ = cn.Close() |
| 611 | return nil, ErrPoolExhausted |
| 612 | } |
| 613 | |
| 614 | p.connsMu.Lock() |
| 615 | defer p.connsMu.Unlock() |
| 616 | if p.closed() { |
| 617 | _ = cn.Close() |
| 618 | return nil, ErrClosed |
| 619 | } |
| 620 | // Check if pool was closed while we were waiting for the lock |
| 621 | if p.conns == nil { |
| 622 | p.conns = make(map[uint64]*Conn) |
| 623 | } |
| 624 | p.conns[cn.GetID()] = cn |
| 625 | |
| 626 | if pooled { |
| 627 | // If pool is full remove the cn on next Put. |
| 628 | currentPoolSize := p.poolSize.Load() |
| 629 | if currentPoolSize >= p.cfg.PoolSize { |
| 630 | cn.pooled = false |
| 631 | } else { |
| 632 | p.poolSize.Add(1) |
| 633 | } |
| 634 | } |
| 635 | |
| 636 | // All new connections start as "used" metrically. For the miss path in getConn, |
| 637 | // this is the final state. For putIdleConn (undelivered conn), a used→idle |
| 638 | // transition is emitted when it's added to idleConns. |
| 639 | if cb := getMetricConnectionStateChangeCallback(); cb != nil { |
| 640 | cb(ctx, cn, "", MetricStateUsed) |