postjevsql.git / tests / keepalive.rs
1//! Keep-alive pings on the backend's connection (contract §2): they go
2//! out while a scan runs, only then, and a ping left unanswered closes
3//! the connection, so the request on it is retried on a new one.
4
5use std::sync::atomic::{AtomicUsize, Ordering};
6use std::time::Duration;
7
8use support::mock_jev::{MockJev, Reply};
9use support::{jev_instance_with, noul};
10
11const SELECT: &str = "SELECT jev_prob(t, 'Urgent?') FROM tickets t";
12
13#[tokio::test(flavor = "multi_thread")]
14async fn pings_go_out_while_a_scan_waits_and_stop_after_it() {
15    let mock = MockJev::start(|_| Reply::json(200, noul(0.95)).after(Duration::from_secs(1))).await;
16    let (_pg, client) = jev_instance_with(
17        &mock,
18        &[("jev.keepalive_interval", "100ms"), ("jev.keepalive_timeout", "5s")],
19    )
20    .await;
21
22    client.query_one(SELECT, &[]).await.expect("answered");
23    // A second of silence from the server at a 100 ms interval.
24    let during = mock.pings();
25    assert!(during >= 5, "{during} pings while the answer stalled");
26
27    // Nothing runs the executor between statements, so nothing pings.
28    tokio::time::sleep(Duration::from_millis(100)).await;
29    let after = mock.pings();
30    tokio::time::sleep(Duration::from_millis(500)).await;
31    assert_eq!(mock.pings(), after, "no pings between statements");
32    assert_eq!(mock.connections(), 1);
33}
34
35#[tokio::test(flavor = "multi_thread")]
36async fn an_unanswered_ping_closes_the_connection_and_the_request_is_retried() {
37    let calls = AtomicUsize::new(0);
38    let mock = MockJev::start(move |_| match calls.fetch_add(1, Ordering::SeqCst) {
39        0 => Reply::json(200, noul(0.10)).after(Duration::from_secs(30)),
40        _ => Reply::json(200, noul(0.95)),
41    })
42    .await;
43    mock.ignore_pings_on_next(1);
44    let (_pg, client) = jev_instance_with(
45        &mock,
46        &[("jev.keepalive_interval", "100ms"), ("jev.keepalive_timeout", "200ms")],
47    )
48    .await;
49
50    let row = client.query_one(SELECT, &[]).await.expect("answered on a new connection");
51    assert_eq!(row.get::<_, f64>(0), 0.95);
52
53    // The 10 s attempt timeout would retry on the same connection; only
54    // the keep-alive closes it.
55    assert_eq!(mock.connections(), 2, "the silent connection was replaced");
56    let requests = mock.requests();
57    assert_eq!(requests.len(), 2);
58    // It may have been billed, so it is a retry, not a redial.
59    assert_eq!(requests[1].headers["x-typesafe-retry-count"], "1");
60    assert_eq!(requests[0].body, requests[1].body);
61}