AwaitAndTransition waits for the connection to reach one of the valid states, then atomically transitions to the target state. Returns the current state after the transition attempt and an error if the operation failed. The returned state is the CURRENT state (after the attempt), not the previous st
( ctx context.Context, validFromStates []ConnState, targetState ConnState, )
| 214 | // - If already in a valid state, this is very fast (no allocation, no waiting) |
| 215 | // - If waiting is required, allocates one waiter struct and one channel |
| 216 | func (sm *ConnStateMachine) AwaitAndTransition( |
| 217 | ctx context.Context, |
| 218 | validFromStates []ConnState, |
| 219 | targetState ConnState, |
| 220 | ) (ConnState, error) { |
| 221 | // Fast path: try immediate transition with CAS to prevent race conditions |
| 222 | // BUT: only if there are no waiters in the queue (to maintain FIFO ordering) |
| 223 | if sm.waiterCount.Load() == 0 { |
| 224 | for _, fromState := range validFromStates { |
| 225 | // Check if we're already in target state |
| 226 | if fromState == targetState && sm.GetState() == targetState { |
| 227 | return targetState, nil |
| 228 | } |
| 229 | |
| 230 | // Try to atomically swap from fromState to targetState |
| 231 | if sm.state.CompareAndSwap(uint32(fromState), uint32(targetState)) { |
| 232 | // Success! We transitioned atomically |
| 233 | sm.notifyWaiters() |
| 234 | return targetState, nil |
| 235 | } |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | // Fast path failed - check if we should wait or fail |
| 240 | currentState := sm.GetState() |
| 241 | |
| 242 | // Check if closed |
| 243 | if currentState == StateClosed { |
| 244 | return currentState, ErrStateMachineClosed |
| 245 | } |
| 246 | |
| 247 | // Slow path: need to wait for state change |
| 248 | // Create waiter with valid states map for fast lookup |
| 249 | validStatesMap := make(map[ConnState]struct{}, len(validFromStates)) |
| 250 | for _, s := range validFromStates { |
| 251 | validStatesMap[s] = struct{}{} |
| 252 | } |
| 253 | |
| 254 | w := &waiter{ |
| 255 | validStates: validStatesMap, |
| 256 | targetState: targetState, |
| 257 | done: make(chan error, 1), // Buffered to avoid goroutine leak |
| 258 | } |
| 259 | |
| 260 | // Add to FIFO queue |
| 261 | sm.mu.Lock() |
| 262 | elem := sm.waiters.PushBack(w) |
| 263 | sm.waiterCount.Add(1) |
| 264 | sm.mu.Unlock() |
| 265 | |
| 266 | // Wait for state change or timeout |
| 267 | select { |
| 268 | case <-ctx.Done(): |
| 269 | // Timeout or cancellation - remove from queue |
| 270 | sm.mu.Lock() |
| 271 | sm.waiters.Remove(elem) |
| 272 | sm.waiterCount.Add(-1) |
| 273 | sm.mu.Unlock() |