@@ -212,23 +212,38 @@ mod tests {
212212 ) ;
213213 }
214214
215+ /// Number of encrypted inserts for the slow-connection test.
216+ const SLOW_CONN_INSERTS : usize = 10 ;
217+
215218 /// Verifies that a slow tenant connection does not block other tenants.
216219 ///
217- /// Tenant A: encrypted insert, signals readiness, then pg_sleep(0.5).
218- /// Tenant B (different keyset): waits for A's signal, then does 10 encrypted inserts, timed.
220+ /// First measures a solo baseline: one tenant doing N encrypted inserts alone.
221+ /// Then runs the contention scenario:
222+ /// Tenant A: encrypted insert, signals readiness, then pg_sleep(2).
223+ /// Tenant B (different keyset): waits for A's signal, then does N encrypted inserts, timed.
224+ ///
225+ /// Asserts that B's time under contention is within 2x of the solo baseline,
226+ /// proving B is not blocked by A's sleep. Uses a relative comparison instead
227+ /// of an absolute threshold to avoid CI environment speed sensitivity.
219228 ///
220229 /// Connection setup is excluded from timing. A `Notify` ensures B starts only after
221230 /// A has completed its encrypted insert and entered pg_sleep, avoiding timing fragility.
222- ///
223- /// With shared mutex contention, B may be blocked while A holds a lock.
224- /// After per-connection cipher fix, B should complete independently of A's sleep.
225231 #[ tokio:: test]
226232 async fn multitenant_slow_connection_does_not_block_other_tenants ( ) {
227233 trace ( ) ;
228234 clear ( ) . await ;
229235
230236 let keyset_ids = tenant_keyset_ids ( 2 ) ;
231237
238+ // --- Solo baseline: measure how long N inserts take with no contention ---
239+ let baseline_client = connect_as_tenant ( & keyset_ids[ 1 ] ) . await ;
240+ let baseline_duration =
241+ do_encrypted_inserts ( & baseline_client, SLOW_CONN_INSERTS ) . await ;
242+ drop ( baseline_client) ;
243+
244+ clear ( ) . await ;
245+
246+ // --- Contention scenario ---
232247 // Establish both connections before timing
233248 let client_a = connect_as_tenant ( & keyset_ids[ 0 ] ) . await ;
234249 let client_b = connect_as_tenant ( & keyset_ids[ 1 ] ) . await ;
@@ -237,7 +252,7 @@ mod tests {
237252 let a_ready = Arc :: new ( Notify :: new ( ) ) ;
238253 let a_ready_tx = a_ready. clone ( ) ;
239254
240- // Tenant A: encrypted insert, signal, then sleep
255+ // Tenant A: encrypted insert, signal, then sleep (2s to be clearly longer than inserts)
241256 let a_handle = tokio:: spawn ( async move {
242257 let id = random_id ( ) ;
243258 let val = random_string ( ) ;
@@ -252,47 +267,44 @@ mod tests {
252267 // Signal that the encrypted insert is done; A is now entering pg_sleep
253268 a_ready_tx. notify_one ( ) ;
254269
255- // Hold this connection busy with a sleep
256- client_a. simple_query ( "SELECT pg_sleep(0.5 )" ) . await . unwrap ( ) ;
270+ // Hold this connection busy with a long sleep
271+ client_a. simple_query ( "SELECT pg_sleep(2 )" ) . await . unwrap ( ) ;
257272 } ) ;
258273
259274 // Wait for A to complete its encrypted insert before starting B
260275 a_ready. notified ( ) . await ;
261276
262277 // Tenant B: encrypted inserts, timed
263278 let b_handle = tokio:: spawn ( async move {
264- let start = Instant :: now ( ) ;
265- for _ in 0 ..10 {
266- let id = random_id ( ) ;
267- let val = random_string ( ) ;
268- client_b
269- . query (
270- "INSERT INTO encrypted (id, encrypted_text) VALUES ($1, $2)" ,
271- & [ & id, & val] ,
272- )
273- . await
274- . unwrap ( ) ;
275- }
276- start. elapsed ( )
279+ do_encrypted_inserts ( & client_b, SLOW_CONN_INSERTS ) . await
277280 } ) ;
278281
279282 // Wait for both
280283 let b_duration = b_handle. await . unwrap ( ) ;
281284 a_handle. await . unwrap ( ) ;
282285
283286 // --- Diagnostics ---
287+ let contention_ratio = b_duration. as_secs_f64 ( ) / baseline_duration. as_secs_f64 ( ) ;
288+
284289 eprintln ! ( "=== multitenant_slow_connection_does_not_block_other_tenants ===" ) ;
285290 eprintln ! (
286- " Tenant B (10 encrypted inserts while Tenant A sleeps): {:.3}s" ,
291+ " Solo baseline ({SLOW_CONN_INSERTS} encrypted inserts): {:.3}s" ,
292+ baseline_duration. as_secs_f64( )
293+ ) ;
294+ eprintln ! (
295+ " Tenant B ({SLOW_CONN_INSERTS} encrypted inserts while A sleeps): {:.3}s" ,
287296 b_duration. as_secs_f64( )
288297 ) ;
289- eprintln ! ( " (After fix: expect B completes well under 0.5s, independent of A's sleep)" ) ;
298+ eprintln ! ( " Contention ratio (B / baseline): {contention_ratio:.3}" ) ;
299+ eprintln ! ( " (After fix: expect ratio < 2.0, B completes independently of A's sleep)" ) ;
290300 eprintln ! ( "=================================================================" ) ;
291301
292302 assert ! (
293- b_duration. as_secs_f64( ) < 0.5 ,
294- "Tenant B should not be blocked by Tenant A's sleep, took {:.3}s" ,
295- b_duration. as_secs_f64( )
303+ contention_ratio < 2.0 ,
304+ "Tenant B should not be blocked by Tenant A's sleep, \
305+ contention ratio={contention_ratio:.3} (B={:.3}s, baseline={:.3}s)",
306+ b_duration. as_secs_f64( ) ,
307+ baseline_duration. as_secs_f64( )
296308 ) ;
297309 }
298310}
0 commit comments