mirror of
https://github.com/hanzokv/go.git
synced 2026-09-24 00:31:51 +00:00
Update module path from github.com/redis/go-redis/v9 to github.com/hanzoai/kv-go/v9 for use as the Hanzo KV Go SDK. - Update go.mod module declaration - Update all Go imports across 260 files - Update all submodule go.mod files (extra/*, example/*, internal/customvet)
241 lines
6.7 KiB
Go
241 lines
6.7 KiB
Go
package streaming
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoai/kv-go/v9/internal/pool"
|
|
)
|
|
|
|
// TestReAuthOnlyWhenIdle verifies that re-authentication only happens when
|
|
// a connection is in IDLE state, not when it's IN_USE.
|
|
func TestReAuthOnlyWhenIdle(t *testing.T) {
|
|
// Create a connection
|
|
cn := pool.NewConn(nil)
|
|
|
|
// Initialize to IDLE state
|
|
cn.GetStateMachine().Transition(pool.StateInitializing)
|
|
cn.GetStateMachine().Transition(pool.StateIdle)
|
|
|
|
// Simulate connection being acquired (IDLE → IN_USE)
|
|
if !cn.CompareAndSwapUsed(false, true) {
|
|
t.Fatal("Failed to acquire connection")
|
|
}
|
|
|
|
// Verify state is IN_USE
|
|
if state := cn.GetStateMachine().GetState(); state != pool.StateInUse {
|
|
t.Errorf("Expected state IN_USE, got %s", state)
|
|
}
|
|
|
|
// Try to transition to UNUSABLE (for reauth) - should fail
|
|
_, err := cn.GetStateMachine().TryTransition([]pool.ConnState{pool.StateIdle}, pool.StateUnusable)
|
|
if err == nil {
|
|
t.Error("Expected error when trying to transition IN_USE → UNUSABLE, but got none")
|
|
}
|
|
|
|
// Verify state is still IN_USE
|
|
if state := cn.GetStateMachine().GetState(); state != pool.StateInUse {
|
|
t.Errorf("Expected state to remain IN_USE, got %s", state)
|
|
}
|
|
|
|
// Release connection (IN_USE → IDLE)
|
|
if !cn.CompareAndSwapUsed(true, false) {
|
|
t.Fatal("Failed to release connection")
|
|
}
|
|
|
|
// Verify state is IDLE
|
|
if state := cn.GetStateMachine().GetState(); state != pool.StateIdle {
|
|
t.Errorf("Expected state IDLE, got %s", state)
|
|
}
|
|
|
|
// Now try to transition to UNUSABLE - should succeed
|
|
_, err = cn.GetStateMachine().TryTransition([]pool.ConnState{pool.StateIdle}, pool.StateUnusable)
|
|
if err != nil {
|
|
t.Errorf("Failed to transition IDLE → UNUSABLE: %v", err)
|
|
}
|
|
|
|
// Verify state is UNUSABLE
|
|
if state := cn.GetStateMachine().GetState(); state != pool.StateUnusable {
|
|
t.Errorf("Expected state UNUSABLE, got %s", state)
|
|
}
|
|
}
|
|
|
|
// TestReAuthWaitsForConnectionToBeIdle verifies that the re-auth worker
|
|
// waits for a connection to become IDLE before performing re-authentication.
|
|
func TestReAuthWaitsForConnectionToBeIdle(t *testing.T) {
|
|
// Create a connection
|
|
cn := pool.NewConn(nil)
|
|
|
|
// Initialize to IDLE state
|
|
cn.GetStateMachine().Transition(pool.StateInitializing)
|
|
cn.GetStateMachine().Transition(pool.StateIdle)
|
|
|
|
// Simulate connection being acquired (IDLE → IN_USE)
|
|
if !cn.CompareAndSwapUsed(false, true) {
|
|
t.Fatal("Failed to acquire connection")
|
|
}
|
|
|
|
// Track re-auth attempts
|
|
var reAuthAttempts atomic.Int32
|
|
var reAuthSucceeded atomic.Bool
|
|
|
|
// Start a goroutine that tries to acquire the connection for re-auth
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
// Try to acquire for re-auth with timeout
|
|
timeout := time.After(2 * time.Second)
|
|
acquired := false
|
|
|
|
for !acquired {
|
|
select {
|
|
case <-timeout:
|
|
t.Error("Timeout waiting to acquire connection for re-auth")
|
|
return
|
|
default:
|
|
reAuthAttempts.Add(1)
|
|
// Try to atomically transition from IDLE to UNUSABLE
|
|
_, err := cn.GetStateMachine().TryTransition([]pool.ConnState{pool.StateIdle}, pool.StateUnusable)
|
|
if err == nil {
|
|
// Successfully acquired
|
|
acquired = true
|
|
reAuthSucceeded.Store(true)
|
|
} else {
|
|
// Connection is still IN_USE, wait a bit
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release the connection
|
|
cn.GetStateMachine().Transition(pool.StateIdle)
|
|
}()
|
|
|
|
// Keep connection IN_USE for 500ms
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Verify re-auth hasn't succeeded yet (connection is still IN_USE)
|
|
if reAuthSucceeded.Load() {
|
|
t.Error("Re-auth succeeded while connection was IN_USE")
|
|
}
|
|
|
|
// Verify there were multiple attempts
|
|
attempts := reAuthAttempts.Load()
|
|
if attempts < 2 {
|
|
t.Errorf("Expected multiple re-auth attempts, got %d", attempts)
|
|
}
|
|
|
|
// Release connection (IN_USE → IDLE)
|
|
if !cn.CompareAndSwapUsed(true, false) {
|
|
t.Fatal("Failed to release connection")
|
|
}
|
|
|
|
// Wait for re-auth to complete
|
|
wg.Wait()
|
|
|
|
// Verify re-auth succeeded after connection became IDLE
|
|
if !reAuthSucceeded.Load() {
|
|
t.Error("Re-auth did not succeed after connection became IDLE")
|
|
}
|
|
|
|
// Verify final state is IDLE
|
|
if state := cn.GetStateMachine().GetState(); state != pool.StateIdle {
|
|
t.Errorf("Expected final state IDLE, got %s", state)
|
|
}
|
|
}
|
|
|
|
// TestConcurrentReAuthAndUsage verifies that re-auth and normal usage
|
|
// don't interfere with each other.
|
|
func TestConcurrentReAuthAndUsage(t *testing.T) {
|
|
// Create a connection
|
|
cn := pool.NewConn(nil)
|
|
|
|
// Initialize to IDLE state
|
|
cn.GetStateMachine().Transition(pool.StateInitializing)
|
|
cn.GetStateMachine().Transition(pool.StateIdle)
|
|
|
|
var wg sync.WaitGroup
|
|
var usageCount atomic.Int32
|
|
var reAuthCount atomic.Int32
|
|
|
|
// Goroutine 1: Simulate normal usage (acquire/release)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 100; i++ {
|
|
// Try to acquire
|
|
if cn.CompareAndSwapUsed(false, true) {
|
|
usageCount.Add(1)
|
|
// Simulate work
|
|
time.Sleep(1 * time.Millisecond)
|
|
// Release
|
|
cn.CompareAndSwapUsed(true, false)
|
|
}
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
// Goroutine 2: Simulate re-auth attempts
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 50; i++ {
|
|
// Try to acquire for re-auth
|
|
_, err := cn.GetStateMachine().TryTransition([]pool.ConnState{pool.StateIdle}, pool.StateUnusable)
|
|
if err == nil {
|
|
reAuthCount.Add(1)
|
|
// Simulate re-auth work
|
|
time.Sleep(2 * time.Millisecond)
|
|
// Release
|
|
cn.GetStateMachine().Transition(pool.StateIdle)
|
|
}
|
|
time.Sleep(2 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
// Verify both operations happened
|
|
if usageCount.Load() == 0 {
|
|
t.Error("No successful usage operations")
|
|
}
|
|
if reAuthCount.Load() == 0 {
|
|
t.Error("No successful re-auth operations")
|
|
}
|
|
|
|
t.Logf("Usage operations: %d, Re-auth operations: %d", usageCount.Load(), reAuthCount.Load())
|
|
|
|
// Verify final state is IDLE
|
|
if state := cn.GetStateMachine().GetState(); state != pool.StateIdle {
|
|
t.Errorf("Expected final state IDLE, got %s", state)
|
|
}
|
|
}
|
|
|
|
// TestReAuthRespectsClosed verifies that re-auth doesn't happen on closed connections.
|
|
func TestReAuthRespectsClosed(t *testing.T) {
|
|
// Create a connection
|
|
cn := pool.NewConn(nil)
|
|
|
|
// Initialize to IDLE state
|
|
cn.GetStateMachine().Transition(pool.StateInitializing)
|
|
cn.GetStateMachine().Transition(pool.StateIdle)
|
|
|
|
// Close the connection
|
|
cn.GetStateMachine().Transition(pool.StateClosed)
|
|
|
|
// Try to transition to UNUSABLE - should fail
|
|
_, err := cn.GetStateMachine().TryTransition([]pool.ConnState{pool.StateIdle}, pool.StateUnusable)
|
|
if err == nil {
|
|
t.Error("Expected error when trying to transition CLOSED → UNUSABLE, but got none")
|
|
}
|
|
|
|
// Verify state is still CLOSED
|
|
if state := cn.GetStateMachine().GetState(); state != pool.StateClosed {
|
|
t.Errorf("Expected state to remain CLOSED, got %s", state)
|
|
}
|
|
}
|