mirror of
https://github.com/vlang/v.git
synced 2025-08-03 09:47:15 -04:00
293 lines
7.1 KiB
V
293 lines
7.1 KiB
V
// vtest build: !windows // msvc hung, maybe sync/atomic bug, gcc on windows does too, although less frequently
|
|
import time
|
|
import sync
|
|
import pool
|
|
import rand
|
|
|
|
// Mock connection implementation
|
|
struct MockConn {
|
|
mut:
|
|
healthy bool
|
|
close_flag bool
|
|
reset_flag bool
|
|
closed int
|
|
id string
|
|
}
|
|
|
|
fn (mut c MockConn) validate() !bool {
|
|
return c.healthy
|
|
}
|
|
|
|
fn (mut c MockConn) close() ! {
|
|
if c.close_flag {
|
|
return error('simulated close error')
|
|
}
|
|
c.closed++
|
|
}
|
|
|
|
fn (mut c MockConn) reset() ! {
|
|
if c.reset_flag {
|
|
return error('simulated reset error')
|
|
}
|
|
c.reset_flag = true
|
|
}
|
|
|
|
// Test utility functions
|
|
fn create_mock_factory(mut arr []&pool.ConnectionPoolable, healthy bool, fail_times int) fn () !&pool.ConnectionPoolable {
|
|
mut count := 0
|
|
return fn [mut arr, healthy, fail_times, mut count] () !&pool.ConnectionPoolable {
|
|
if count < fail_times {
|
|
count++
|
|
return error('connection creation failed')
|
|
}
|
|
mut conn := &MockConn{
|
|
healthy: healthy
|
|
id: rand.uuid_v7()
|
|
}
|
|
arr << conn
|
|
return conn
|
|
}
|
|
}
|
|
|
|
fn is_same_conn(conn1 &pool.ConnectionPoolable, conn2 &pool.ConnectionPoolable) bool {
|
|
c1 := conn1 as MockConn
|
|
c2 := conn2 as MockConn
|
|
return c1.id == c2.id
|
|
}
|
|
|
|
// Test cases
|
|
fn test_basic_usage() {
|
|
for _ in 0 .. 1 {
|
|
mut test_conns := []&pool.ConnectionPoolable{}
|
|
factory := create_mock_factory(mut test_conns, true, 0)
|
|
config := pool.ConnectionPoolConfig{
|
|
max_conns: 5
|
|
min_idle_conns: 2
|
|
idle_timeout: 100 * time.millisecond
|
|
get_timeout: 50 * time.millisecond
|
|
}
|
|
|
|
mut p := pool.new_connection_pool(factory, config)!
|
|
|
|
// Acquire a connection
|
|
mut conn1 := p.get()!
|
|
assert p.stats().active_conns == 1
|
|
|
|
// Acquire multiple connections
|
|
mut conns := [p.get()!, p.get()!, p.get()!]
|
|
assert p.stats().active_conns == 4
|
|
|
|
// Return connections
|
|
for c in conns {
|
|
p.put(c)!
|
|
}
|
|
p.put(conn1)!
|
|
assert p.stats().active_conns == 0
|
|
assert p.stats().total_conns >= 4
|
|
p.close()
|
|
}
|
|
}
|
|
|
|
fn test_pool_exhaustion() {
|
|
for i in 0 .. 1 {
|
|
mut test_conns := []&pool.ConnectionPoolable{}
|
|
factory := create_mock_factory(mut test_conns, true, 0)
|
|
config := pool.ConnectionPoolConfig{
|
|
max_conns: 2
|
|
min_idle_conns: 1
|
|
get_timeout: 10 * time.millisecond
|
|
}
|
|
|
|
mut p := pool.new_connection_pool(factory, config)!
|
|
|
|
// Acquire all connections
|
|
c1 := p.get()!
|
|
c2 := p.get()!
|
|
assert p.stats().active_conns == 2
|
|
|
|
// Attempt to acquire third connection (should timeout)
|
|
p.get() or { assert err.msg().contains('timeout') }
|
|
|
|
// After returning, should be able to acquire again
|
|
p.put(c2)!
|
|
c3 := p.get()!
|
|
assert is_same_conn(c3, c2)
|
|
assert p.stats().active_conns == 2
|
|
p.close()
|
|
}
|
|
}
|
|
|
|
fn test_connection_validation() {
|
|
mut test_conns := []&pool.ConnectionPoolable{}
|
|
factory := create_mock_factory(mut test_conns, false, 0) // Create unhealthy connections
|
|
config := pool.ConnectionPoolConfig{
|
|
min_idle_conns: 1
|
|
}
|
|
|
|
mut p := pool.new_connection_pool(factory, config) or {
|
|
assert err.msg().contains('connection validation failed')
|
|
return
|
|
}
|
|
defer {
|
|
p.close()
|
|
}
|
|
}
|
|
|
|
fn test_eviction() {
|
|
mut test_conns := []&pool.ConnectionPoolable{}
|
|
factory := create_mock_factory(mut test_conns, true, 0)
|
|
config := pool.ConnectionPoolConfig{
|
|
max_lifetime: 10 * time.millisecond
|
|
idle_timeout: 10 * time.millisecond
|
|
min_idle_conns: 0
|
|
}
|
|
|
|
mut p := pool.new_connection_pool(factory, config)!
|
|
defer {
|
|
p.close()
|
|
}
|
|
|
|
// Acquire and return a connection
|
|
c1 := p.get()!
|
|
p.put(c1)!
|
|
|
|
// Wait longer than timeout thresholds
|
|
time.sleep(100 * time.millisecond)
|
|
p.send_eviction(.urgent)
|
|
time.sleep(10 * time.millisecond)
|
|
stats := p.stats()
|
|
assert stats.evicted_count > 0
|
|
assert stats.total_conns == 0
|
|
assert stats.idle_conns == 0
|
|
c2 := p.get()!
|
|
assert !is_same_conn(c1, c2)
|
|
assert p.stats().total_conns == 1
|
|
assert p.stats().evicted_count > 0
|
|
}
|
|
|
|
fn test_retry_mechanism() {
|
|
mut test_conns := []&pool.ConnectionPoolable{}
|
|
factory := create_mock_factory(mut test_conns, true, 3) // First 3 attempts fail
|
|
config := pool.ConnectionPoolConfig{
|
|
max_retry_attempts: 5
|
|
retry_base_delay: 10 * time.millisecond
|
|
min_idle_conns: 0 // No idle connections
|
|
}
|
|
|
|
mut p := pool.new_connection_pool(factory, config)!
|
|
defer {
|
|
p.close()
|
|
}
|
|
// Should successfully create connection after retries
|
|
conn := p.get()!
|
|
assert test_conns.len == 1
|
|
assert p.stats().creation_errors == 3
|
|
}
|
|
|
|
fn test_concurrent_access() {
|
|
mut test_conns := []&pool.ConnectionPoolable{}
|
|
factory := create_mock_factory(mut test_conns, true, 0)
|
|
config := pool.ConnectionPoolConfig{
|
|
max_conns: 10
|
|
}
|
|
|
|
mut p := pool.new_connection_pool(factory, config)!
|
|
defer {
|
|
p.close()
|
|
}
|
|
mut wg := sync.new_waitgroup()
|
|
|
|
for _ in 0 .. 20 {
|
|
wg.add(1)
|
|
spawn fn (mut p pool.ConnectionPool, mut wg sync.WaitGroup) ! {
|
|
defer { wg.done() }
|
|
conn := p.get()!
|
|
time.sleep(5 * time.millisecond)
|
|
p.put(conn)!
|
|
}(mut p, mut wg)
|
|
}
|
|
|
|
wg.wait()
|
|
stats := p.stats()
|
|
assert stats.total_conns <= 10
|
|
assert stats.active_conns == 0
|
|
}
|
|
|
|
fn test_pool_close() {
|
|
mut test_conns := []&pool.ConnectionPoolable{}
|
|
factory := create_mock_factory(mut test_conns, true, 0)
|
|
|
|
mut p := pool.new_connection_pool(factory, pool.ConnectionPoolConfig{})!
|
|
c := p.get()!
|
|
p.put(c)!
|
|
assert p.stats().active_conns == 0
|
|
assert p.stats().idle_conns >= 5
|
|
|
|
p.close()
|
|
|
|
// Attempt to acquire connection after close
|
|
p.get() or { assert err.msg().contains('closed') }
|
|
assert p.stats().active_conns == 0
|
|
assert p.stats().idle_conns == 0
|
|
|
|
// Verify connection was closed
|
|
mock_conn := test_conns[0] as MockConn
|
|
assert mock_conn.closed == 1
|
|
}
|
|
|
|
fn test_config_update() {
|
|
mut dummy := []&pool.ConnectionPoolable{}
|
|
mut p := pool.new_connection_pool(create_mock_factory(mut dummy, true, 0), pool.ConnectionPoolConfig{
|
|
max_conns: 2
|
|
min_idle_conns: 1
|
|
})!
|
|
defer {
|
|
p.close()
|
|
}
|
|
assert p.stats().idle_conns == 1
|
|
|
|
// Modify configuration
|
|
new_config := pool.ConnectionPoolConfig{
|
|
max_conns: 5
|
|
min_idle_conns: 3
|
|
}
|
|
|
|
p.update_config(new_config)!
|
|
|
|
// Trigger idle connection replenishment
|
|
time.sleep(100 * time.millisecond)
|
|
assert p.stats().idle_conns >= 3
|
|
}
|
|
|
|
fn test_error_handling() {
|
|
// Test close error handling
|
|
mut test_conns := []&pool.ConnectionPoolable{}
|
|
factory := create_mock_factory(mut test_conns, true, 0)
|
|
mut p := pool.new_connection_pool(factory, pool.ConnectionPoolConfig{})!
|
|
defer {
|
|
p.close()
|
|
}
|
|
|
|
// default configuration has 5 idle_conns
|
|
assert p.stats().idle_conns == 5
|
|
|
|
// Bug Fix Needed! msvc generated wrong code for `mut conn := p.get()! as MockConn`
|
|
mut connx := p.get()!
|
|
mut conn := connx as MockConn
|
|
assert p.stats().active_conns == 1
|
|
// it depend on `background_maintenance` thread to keep 5 idle_conns
|
|
assert p.stats().idle_conns >= 4
|
|
conn.close_flag = true
|
|
p.put(conn) or { assert err.msg().contains('simulated close error') }
|
|
assert p.stats().active_conns == 0
|
|
// it depend on `background_maintenance` thread to keep 5 idle_conns
|
|
assert p.stats().idle_conns >= 4
|
|
|
|
// Test reset error handling
|
|
conn.reset_flag = true
|
|
p.put(conn) or { assert err.msg().contains('reset') }
|
|
assert p.stats().active_conns == 0
|
|
// it depend on `background_maintenance` thread to keep 5 idle_conns
|
|
assert p.stats().idle_conns >= 4
|
|
}
|