v / vlib / pool / connection_test.v
292 lines · 251 sloc · 7.06 KB · e3d047822d27c62725fd005b62715c7c840778e5
Raw
1// vtest build: !windows // msvc hung, maybe sync/atomic bug, gcc on windows does too, although less frequently
2import time
3import sync
4import pool
5import rand
6
7// Mock connection implementation
8struct MockConn {
9mut:
10 healthy bool
11 close_flag bool
12 reset_flag bool
13 closed int
14 id string
15}
16
17fn (mut c MockConn) validate() !bool {
18 return c.healthy
19}
20
21fn (mut c MockConn) close() ! {
22 if c.close_flag {
23 return error('simulated close error')
24 }
25 c.closed++
26}
27
28fn (mut c MockConn) reset() ! {
29 if c.reset_flag {
30 return error('simulated reset error')
31 }
32 c.reset_flag = true
33}
34
35// Test utility functions
36fn create_mock_factory(mut arr []&pool.ConnectionPoolable, healthy bool, fail_times int) fn () !&pool.ConnectionPoolable {
37 mut count := 0
38 return fn [mut arr, healthy, fail_times, mut count] () !&pool.ConnectionPoolable {
39 if count < fail_times {
40 count++
41 return error('connection creation failed')
42 }
43 mut conn := &MockConn{
44 healthy: healthy
45 id: rand.uuid_v7()
46 }
47 arr << conn
48 return conn
49 }
50}
51
52fn is_same_conn(conn1 &pool.ConnectionPoolable, conn2 &pool.ConnectionPoolable) bool {
53 c1 := conn1 as MockConn
54 c2 := conn2 as MockConn
55 return c1.id == c2.id
56}
57
58// Test cases
59fn test_basic_usage() {
60 for _ in 0 .. 1 {
61 mut test_conns := []&pool.ConnectionPoolable{}
62 factory := create_mock_factory(mut test_conns, true, 0)
63 config := pool.ConnectionPoolConfig{
64 max_conns: 5
65 min_idle_conns: 2
66 idle_timeout: 100 * time.millisecond
67 get_timeout: 50 * time.millisecond
68 }
69
70 mut p := pool.new_connection_pool(factory, config)!
71
72 // Acquire a connection
73 mut conn1 := p.get()!
74 assert p.stats().active_conns == 1
75
76 // Acquire multiple connections
77 mut conns := [p.get()!, p.get()!, p.get()!]
78 assert p.stats().active_conns == 4
79
80 // Return connections
81 for c in conns {
82 p.put(c)!
83 }
84 p.put(conn1)!
85 assert p.stats().active_conns == 0
86 assert p.stats().total_conns >= 4
87 p.close()
88 }
89}
90
91fn test_pool_exhaustion() {
92 for i in 0 .. 1 {
93 mut test_conns := []&pool.ConnectionPoolable{}
94 factory := create_mock_factory(mut test_conns, true, 0)
95 config := pool.ConnectionPoolConfig{
96 max_conns: 2
97 min_idle_conns: 1
98 get_timeout: 10 * time.millisecond
99 }
100
101 mut p := pool.new_connection_pool(factory, config)!
102
103 // Acquire all connections
104 c1 := p.get()!
105 c2 := p.get()!
106 assert p.stats().active_conns == 2
107
108 // Attempt to acquire third connection (should timeout)
109 p.get() or { assert err.msg().contains('timeout') }
110
111 // After returning, should be able to acquire again
112 p.put(c2)!
113 c3 := p.get()!
114 assert is_same_conn(c3, c2)
115 assert p.stats().active_conns == 2
116 p.close()
117 }
118}
119
120fn test_connection_validation() {
121 mut test_conns := []&pool.ConnectionPoolable{}
122 factory := create_mock_factory(mut test_conns, false, 0) // Create unhealthy connections
123 config := pool.ConnectionPoolConfig{
124 min_idle_conns: 1
125 }
126
127 mut p := pool.new_connection_pool(factory, config) or {
128 assert err.msg().contains('connection validation failed')
129 return
130 }
131 defer {
132 p.close()
133 }
134}
135
136fn test_eviction() {
137 mut test_conns := []&pool.ConnectionPoolable{}
138 factory := create_mock_factory(mut test_conns, true, 0)
139 config := pool.ConnectionPoolConfig{
140 max_lifetime: 10 * time.millisecond
141 idle_timeout: 10 * time.millisecond
142 min_idle_conns: 0
143 }
144
145 mut p := pool.new_connection_pool(factory, config)!
146 defer {
147 p.close()
148 }
149
150 // Acquire and return a connection
151 c1 := p.get()!
152 p.put(c1)!
153
154 // Wait longer than timeout thresholds
155 time.sleep(100 * time.millisecond)
156 p.send_eviction(.urgent)
157 time.sleep(10 * time.millisecond)
158 stats := p.stats()
159 assert stats.evicted_count > 0
160 assert stats.total_conns == 0
161 assert stats.idle_conns == 0
162 c2 := p.get()!
163 assert !is_same_conn(c1, c2)
164 assert p.stats().total_conns == 1
165 assert p.stats().evicted_count > 0
166}
167
168fn test_retry_mechanism() {
169 mut test_conns := []&pool.ConnectionPoolable{}
170 factory := create_mock_factory(mut test_conns, true, 3) // First 3 attempts fail
171 config := pool.ConnectionPoolConfig{
172 max_retry_attempts: 5
173 retry_base_delay: 10 * time.millisecond
174 min_idle_conns: 0 // No idle connections
175 }
176
177 mut p := pool.new_connection_pool(factory, config)!
178 defer {
179 p.close()
180 }
181 // Should successfully create connection after retries
182 conn := p.get()!
183 assert test_conns.len == 1
184 assert p.stats().creation_errors == 3
185}
186
187fn test_concurrent_access() {
188 mut test_conns := []&pool.ConnectionPoolable{}
189 factory := create_mock_factory(mut test_conns, true, 0)
190 config := pool.ConnectionPoolConfig{
191 max_conns: 10
192 }
193
194 mut p := pool.new_connection_pool(factory, config)!
195 defer {
196 p.close()
197 }
198 mut wg := sync.new_waitgroup()
199
200 for _ in 0 .. 20 {
201 wg.add(1)
202 spawn fn (mut p pool.ConnectionPool, mut wg sync.WaitGroup) ! {
203 defer { wg.done() }
204 conn := p.get()!
205 time.sleep(5 * time.millisecond)
206 p.put(conn)!
207 }(mut p, mut wg)
208 }
209
210 wg.wait()
211 stats := p.stats()
212 assert stats.total_conns <= 10
213 assert stats.active_conns == 0
214}
215
216fn test_pool_close() {
217 mut test_conns := []&pool.ConnectionPoolable{}
218 factory := create_mock_factory(mut test_conns, true, 0)
219
220 mut p := pool.new_connection_pool(factory, pool.ConnectionPoolConfig{})!
221 c := p.get()!
222 p.put(c)!
223 assert p.stats().active_conns == 0
224 assert p.stats().idle_conns >= 5
225
226 p.close()
227
228 // Attempt to acquire connection after close
229 p.get() or { assert err.msg().contains('closed') }
230 assert p.stats().active_conns == 0
231 assert p.stats().idle_conns == 0
232
233 // Verify connection was closed
234 mock_conn := test_conns[0] as MockConn
235 assert mock_conn.closed == 1
236}
237
238fn test_config_update() {
239 mut dummy := []&pool.ConnectionPoolable{}
240 mut p := pool.new_connection_pool(create_mock_factory(mut dummy, true, 0), pool.ConnectionPoolConfig{
241 max_conns: 2
242 min_idle_conns: 1
243 })!
244 defer {
245 p.close()
246 }
247 assert p.stats().idle_conns == 1
248
249 // Modify configuration
250 new_config := pool.ConnectionPoolConfig{
251 max_conns: 5
252 min_idle_conns: 3
253 }
254
255 p.update_config(new_config)!
256
257 // Trigger idle connection replenishment
258 time.sleep(100 * time.millisecond)
259 assert p.stats().idle_conns >= 3
260}
261
262fn test_error_handling() {
263 // Test close error handling
264 mut test_conns := []&pool.ConnectionPoolable{}
265 factory := create_mock_factory(mut test_conns, true, 0)
266 mut p := pool.new_connection_pool(factory, pool.ConnectionPoolConfig{})!
267 defer {
268 p.close()
269 }
270
271 // default configuration has 5 idle_conns
272 assert p.stats().idle_conns == 5
273
274 // Bug Fix Needed! msvc generated wrong code for `mut conn := p.get()! as MockConn`
275 mut connx := p.get()!
276 mut conn := connx as MockConn
277 assert p.stats().active_conns == 1
278 // it depend on `background_maintenance` thread to keep 5 idle_conns
279 assert p.stats().idle_conns >= 4
280 conn.close_flag = true
281 p.put(conn) or { assert err.msg().contains('simulated close error') }
282 assert p.stats().active_conns == 0
283 // it depend on `background_maintenance` thread to keep 5 idle_conns
284 assert p.stats().idle_conns >= 4
285
286 // Test reset error handling
287 conn.reset_flag = true
288 p.put(conn) or { assert err.msg().contains('reset') }
289 assert p.stats().active_conns == 0
290 // it depend on `background_maintenance` thread to keep 5 idle_conns
291 assert p.stats().idle_conns >= 4
292}
293