| 481 | } |
| 482 | |
| 483 | func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error { |
| 484 | // This function is called in two scenarios: |
| 485 | // 1. First-time init: Connection is in CREATED state (from pool.Get()) |
| 486 | // - We need to transition CREATED → INITIALIZING and do the initialization |
| 487 | // - If another goroutine is already initializing, we WAIT for it to finish |
| 488 | // 2. Re-initialization: Connection is in INITIALIZING state (from SetNetConnAndInitConn()) |
| 489 | // - We're already in INITIALIZING, so just proceed with initialization |
| 490 | |
| 491 | currentState := cn.GetStateMachine().GetState() |
| 492 | |
| 493 | // Fast path: Check if already initialized (IDLE or IN_USE) |
| 494 | if currentState == pool.StateIdle || currentState == pool.StateInUse { |
| 495 | return nil |
| 496 | } |
| 497 | |
| 498 | // If in CREATED state, try to transition to INITIALIZING |
| 499 | if currentState == pool.StateCreated { |
| 500 | finalState, err := cn.GetStateMachine().TryTransition([]pool.ConnState{pool.StateCreated}, pool.StateInitializing) |
| 501 | if err != nil { |
| 502 | // Another goroutine is initializing or connection is in unexpected state |
| 503 | // Check what state we're in now |
| 504 | if finalState == pool.StateIdle || finalState == pool.StateInUse { |
| 505 | // Already initialized by another goroutine |
| 506 | return nil |
| 507 | } |
| 508 | |
| 509 | if finalState == pool.StateInitializing { |
| 510 | // Another goroutine is initializing - WAIT for it to complete |
| 511 | // Use a context with timeout = min(remaining command timeout, DialTimeout) |
| 512 | // This prevents waiting too long while respecting the caller's deadline |
| 513 | var waitCtx context.Context |
| 514 | var cancel context.CancelFunc |
| 515 | dialTimeout := c.opt.DialTimeout |
| 516 | |
| 517 | if cmdDeadline, hasCmdDeadline := ctx.Deadline(); hasCmdDeadline { |
| 518 | // Calculate remaining time until command deadline |
| 519 | remainingTime := time.Until(cmdDeadline) |
| 520 | // Use the minimum of remaining time and DialTimeout |
| 521 | if remainingTime < dialTimeout { |
| 522 | // Command deadline is sooner, use it |
| 523 | waitCtx = ctx |
| 524 | } else { |
| 525 | // DialTimeout is shorter, cap the wait at DialTimeout |
| 526 | waitCtx, cancel = context.WithTimeout(ctx, dialTimeout) |
| 527 | } |
| 528 | } else { |
| 529 | // No command deadline, use DialTimeout to prevent waiting indefinitely |
| 530 | waitCtx, cancel = context.WithTimeout(ctx, dialTimeout) |
| 531 | } |
| 532 | if cancel != nil { |
| 533 | defer cancel() |
| 534 | } |
| 535 | |
| 536 | finalState, err := cn.GetStateMachine().AwaitAndTransition( |
| 537 | waitCtx, |
| 538 | []pool.ConnState{pool.StateIdle, pool.StateInUse}, |
| 539 | pool.StateIdle, // Target is IDLE (but we're already there, so this is a no-op) |
| 540 | ) |