Skip to content

Commit a0c7784

Browse files
committed
fix(integration): use relative threshold in slow-connection contention test
The absolute 0.5s threshold failed in CI where 10 encrypted inserts take ~1.0s baseline. Replace with solo baseline measurement and relative assertion (contention_ratio < 2.0), matching the approach used by the other two passing contention tests.
1 parent c39464e commit a0c7784

1 file changed

Lines changed: 38 additions & 28 deletions

File tree

  • packages/cipherstash-proxy-integration/src/multitenant

packages/cipherstash-proxy-integration/src/multitenant/contention.rs

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -212,23 +212,37 @@ 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 = do_encrypted_inserts(&baseline_client, SLOW_CONN_INSERTS).await;
241+
drop(baseline_client);
242+
243+
clear().await;
244+
245+
// --- Contention scenario ---
232246
// Establish both connections before timing
233247
let client_a = connect_as_tenant(&keyset_ids[0]).await;
234248
let client_b = connect_as_tenant(&keyset_ids[1]).await;
@@ -237,7 +251,7 @@ mod tests {
237251
let a_ready = Arc::new(Notify::new());
238252
let a_ready_tx = a_ready.clone();
239253

240-
// Tenant A: encrypted insert, signal, then sleep
254+
// Tenant A: encrypted insert, signal, then sleep (2s to be clearly longer than inserts)
241255
let a_handle = tokio::spawn(async move {
242256
let id = random_id();
243257
let val = random_string();
@@ -252,47 +266,43 @@ mod tests {
252266
// Signal that the encrypted insert is done; A is now entering pg_sleep
253267
a_ready_tx.notify_one();
254268

255-
// Hold this connection busy with a sleep
256-
client_a.simple_query("SELECT pg_sleep(0.5)").await.unwrap();
269+
// Hold this connection busy with a long sleep
270+
client_a.simple_query("SELECT pg_sleep(2)").await.unwrap();
257271
});
258272

259273
// Wait for A to complete its encrypted insert before starting B
260274
a_ready.notified().await;
261275

262276
// Tenant B: encrypted inserts, timed
263-
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()
277-
});
277+
let b_handle =
278+
tokio::spawn(async move { do_encrypted_inserts(&client_b, SLOW_CONN_INSERTS).await });
278279

279280
// Wait for both
280281
let b_duration = b_handle.await.unwrap();
281282
a_handle.await.unwrap();
282283

283284
// --- Diagnostics ---
285+
let contention_ratio = b_duration.as_secs_f64() / baseline_duration.as_secs_f64();
286+
284287
eprintln!("=== multitenant_slow_connection_does_not_block_other_tenants ===");
285288
eprintln!(
286-
" Tenant B (10 encrypted inserts while Tenant A sleeps): {:.3}s",
289+
" Solo baseline ({SLOW_CONN_INSERTS} encrypted inserts): {:.3}s",
290+
baseline_duration.as_secs_f64()
291+
);
292+
eprintln!(
293+
" Tenant B ({SLOW_CONN_INSERTS} encrypted inserts while A sleeps): {:.3}s",
287294
b_duration.as_secs_f64()
288295
);
289-
eprintln!(" (After fix: expect B completes well under 0.5s, independent of A's sleep)");
296+
eprintln!(" Contention ratio (B / baseline): {contention_ratio:.3}");
297+
eprintln!(" (After fix: expect ratio < 2.0, B completes independently of A's sleep)");
290298
eprintln!("=================================================================");
291299

292300
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()
301+
contention_ratio < 2.0,
302+
"Tenant B should not be blocked by Tenant A's sleep, \
303+
contention ratio={contention_ratio:.3} (B={:.3}s, baseline={:.3}s)",
304+
b_duration.as_secs_f64(),
305+
baseline_duration.as_secs_f64()
296306
);
297307
}
298308
}

0 commit comments

Comments
 (0)