1
//! Tests request and connection timeouts
2

            
3
use std::net::UdpSocket;
4
use std::time::{Duration, Instant};
5

            
6
use bonsaidb::client::url::Url;
7
use bonsaidb::client::AsyncClient;
8
use bonsaidb_client::fabruic::Certificate;
9
use bonsaidb_client::{ApiError, BlockingClient};
10
use bonsaidb_core::api::Api;
11
use bonsaidb_core::async_trait::async_trait;
12
use bonsaidb_core::connection::{AsyncStorageConnection, StorageConnection};
13
use bonsaidb_core::networking;
14
use bonsaidb_core::test_util::{Basic, TestDirectory};
15
use bonsaidb_local::config::Builder;
16
use bonsaidb_server::api::{Handler, HandlerResult, HandlerSession};
17
use bonsaidb_server::{DefaultPermissions, Server, ServerConfiguration};
18
use once_cell::sync::Lazy;
19
use serde::{Deserialize, Serialize};
20

            
21
1
#[tokio::test]
22
#[cfg(feature = "websockets")]
23
1
async fn ws_connect_timeout() -> anyhow::Result<()> {
24
1
    use std::net::TcpListener;
25
1

            
26
1
    let start = Instant::now();
27
1
    let tcp = TcpListener::bind("0.0.0.0:0")?;
28
1
    let port = tcp.local_addr()?.port();
29
1
    let client = AsyncClient::build(Url::parse(&format!("ws://127.0.0.1:{port}"))?)
30
1
        .with_connect_timeout(Duration::from_secs(1))
31
1
        .build()?;
32
1

            
33
1
    match tokio::time::timeout(Duration::from_secs(60), client.list_databases()).await {
34
1
        Ok(Err(bonsaidb_core::Error::Networking(networking::Error::ConnectTimeout))) => {
35
1
            assert!(start.elapsed() < Duration::from_secs(5));
36
1
            Ok(())
37
1
        }
38
1
        other => unreachable!("expected connect timeout, got {other:?}"),
39
1
    }
40
1
}
41

            
42
1
#[tokio::test]
43
1
async fn quic_connect_timeout() -> anyhow::Result<()> {
44
1
    let start = Instant::now();
45
1
    let udp = UdpSocket::bind("0.0.0.0:0")?;
46
1
    let port = udp.local_addr()?.port();
47
1
    let client = AsyncClient::build(Url::parse(&format!("bonsaidb://127.0.0.1:{port}"))?)
48
1
        .with_connect_timeout(Duration::from_secs(1))
49
1
        .build()?;
50
1

            
51
1
    match tokio::time::timeout(Duration::from_secs(60), client.list_databases()).await {
52
1
        Ok(Err(bonsaidb_core::Error::Networking(networking::Error::ConnectTimeout))) => {
53
1
            assert!(start.elapsed() < Duration::from_secs(5));
54
1
            Ok(())
55
1
        }
56
1
        other => unreachable!("expected connect timeout, got {other:?}"),
57
1
    }
58
1
}
59

            
60
1
#[test]
61
#[cfg(feature = "websockets")]
62
1
fn blocking_ws_connect_timeout() -> anyhow::Result<()> {
63
1
    use std::net::TcpListener;
64
1

            
65
1
    let start = Instant::now();
66
1
    let tcp = TcpListener::bind("0.0.0.0:0")?;
67
1
    let port = tcp.local_addr()?.port();
68
1
    let client = BlockingClient::build(Url::parse(&format!("ws://127.0.0.1:{port}"))?)
69
1
        .with_connect_timeout(Duration::from_secs(1))
70
1
        .build()?;
71

            
72
1
    match client.list_databases() {
73
        Err(bonsaidb_core::Error::Networking(networking::Error::ConnectTimeout)) => {
74
1
            assert!(start.elapsed() < Duration::from_secs(5));
75
1
            Ok(())
76
        }
77
        other => unreachable!("expected connect timeout, got {other:?}"),
78
    }
79
1
}
80

            
81
1
#[test]
82
1
fn blocking_quic_connect_timeout() -> anyhow::Result<()> {
83
1
    let start = Instant::now();
84
1
    let udp = UdpSocket::bind("0.0.0.0:0")?;
85
1
    let port = udp.local_addr()?.port();
86
1
    let client = BlockingClient::build(Url::parse(&format!("bonsaidb://127.0.0.1:{port}"))?)
87
1
        .with_connect_timeout(Duration::from_secs(1))
88
1
        .build()?;
89

            
90
1
    match client.list_databases() {
91
        Err(bonsaidb_core::Error::Networking(networking::Error::ConnectTimeout)) => {
92
1
            assert!(start.elapsed() < Duration::from_secs(5));
93
1
            Ok(())
94
        }
95
        other => unreachable!("expected connect timeout, got {other:?}"),
96
    }
97
1
}
98

            
99
8
#[derive(Api, Debug, Serialize, Deserialize, Clone)]
100
#[api(name = "long-call")]
101
struct LongCall;
102

            
103
#[async_trait]
104
impl Handler<LongCall> for LongCall {
105
4
    async fn handle(_session: HandlerSession<'_>, _request: LongCall) -> HandlerResult<LongCall> {
106
4
        tokio::time::sleep(Duration::from_secs(10)).await;
107
        Ok(())
108
8
    }
109
}
110

            
111
4
fn shared_server() -> &'static Certificate {
112
4
    static SHARED_SERVER: Lazy<Certificate> = Lazy::new(|| {
113
1
        drop(env_logger::try_init());
114
1
        let dir = TestDirectory::new("timeouts.bonsaidb");
115
1

            
116
1
        let (server_sender, server_receiver) = tokio::sync::oneshot::channel();
117
1

            
118
1
        std::thread::spawn(move || {
119
1
            tokio::runtime::Runtime::new().unwrap().block_on(async {
120
4
                let server = Server::open(
121
1
                    ServerConfiguration::new(&dir)
122
1
                        .default_permissions(DefaultPermissions::AllowAll)
123
1
                        .with_schema::<Basic>()
124
1
                        .unwrap()
125
1
                        .with_api::<LongCall, LongCall>()
126
1
                        .unwrap(),
127
1
                )
128
4
                .await
129
4
                .unwrap();
130
10
                server.install_self_signed_certificate(false).await.unwrap();
131
1
                server_sender
132
1
                    .send(
133
1
                        server
134
1
                            .certificate_chain()
135
4
                            .await
136
4
                            .unwrap()
137
1
                            .into_end_entity_certificate(),
138
1
                    )
139
1
                    .unwrap();
140
1

            
141
1
                #[cfg(feature = "websockets")]
142
1
                tokio::task::spawn({
143
1
                    let server = server.clone();
144
2
                    async move { server.listen_for_websockets_on("0.0.0.0:7023", false).await }
145
1
                });
146
1

            
147
7
                server.listen_on(7024).await
148
4
            })
149
1
        });
150
1

            
151
1
        server_receiver.blocking_recv().unwrap()
152
1
    });
153
4

            
154
4
    &SHARED_SERVER
155
4
}
156

            
157
1
#[tokio::test]
158
#[cfg(feature = "websockets")]
159
1
async fn ws_request_timeout() {
160
1
    shared_server();
161
1
    // Give the server a moment to actually start up.
162
1
    tokio::time::sleep(Duration::from_millis(100)).await;
163
1

            
164
1
    let start = Instant::now();
165
1
    let client = AsyncClient::build(Url::parse("ws://127.0.0.1:7023").unwrap())
166
1
        .with_request_timeout(Duration::from_secs(1))
167
1
        .build()
168
1
        .unwrap();
169
1
    match client.send_api_request(&LongCall).await {
170
1
        Err(ApiError::Client(bonsaidb_client::Error::Core(bonsaidb_core::Error::Networking(
171
1
            networking::Error::RequestTimeout,
172
1
        )))) => {
173
1
            assert!(start.elapsed() < Duration::from_secs(5));
174
1
        }
175
1
        other => unreachable!("expected request timeout, got {other:?}"),
176
1
    }
177
1
}
178

            
179
1
#[test]
180
#[cfg(feature = "websockets")]
181
1
fn blocking_ws_request_timeout() {
182
1
    shared_server();
183
1
    // Give the server a moment to actually start up.
184
1
    std::thread::sleep(Duration::from_millis(100));
185
1

            
186
1
    let start = Instant::now();
187
1
    let client = BlockingClient::build(Url::parse("ws://127.0.0.1:7023").unwrap())
188
1
        .with_request_timeout(Duration::from_secs(1))
189
1
        .build()
190
1
        .unwrap();
191
1
    match client.send_api_request(&LongCall) {
192
        Err(ApiError::Client(bonsaidb_client::Error::Core(bonsaidb_core::Error::Networking(
193
            networking::Error::RequestTimeout,
194
        )))) => {
195
1
            assert!(start.elapsed() < Duration::from_secs(5));
196
        }
197
        other => unreachable!("expected request timeout, got {other:?}"),
198
    }
199
1
}
200

            
201
1
#[tokio::test]
202
1
async fn quic_request_timeout() {
203
1
    let cert_chain = shared_server();
204
1
    // Give the server a moment to actually start up.
205
1
    tokio::time::sleep(Duration::from_millis(100)).await;
206
1

            
207
1
    let start = Instant::now();
208
1
    let client = AsyncClient::build(Url::parse("bonsaidb://127.0.0.1:7024").unwrap())
209
1
        .with_request_timeout(Duration::from_secs(1))
210
1
        .with_certificate(cert_chain.clone())
211
1
        .build()
212
1
        .unwrap();
213
1
    match client.send_api_request(&LongCall).await {
214
1
        Err(ApiError::Client(bonsaidb_client::Error::Core(bonsaidb_core::Error::Networking(
215
1
            networking::Error::RequestTimeout,
216
1
        )))) => {
217
1
            assert!(start.elapsed() < Duration::from_secs(5));
218
1
        }
219
1
        other => unreachable!("expected request timeout, got {other:?}"),
220
1
    }
221
1
}
222

            
223
1
#[test]
224
1
fn blocking_quic_request_timeout() {
225
1
    let cert_chain = shared_server();
226
1
    // Give the server a moment to actually start up.
227
1
    std::thread::sleep(Duration::from_millis(100));
228
1

            
229
1
    let start = Instant::now();
230
1
    let client = BlockingClient::build(Url::parse("bonsaidb://127.0.0.1:7024").unwrap())
231
1
        .with_request_timeout(Duration::from_secs(1))
232
1
        .with_certificate(cert_chain.clone())
233
1
        .build()
234
1
        .unwrap();
235
1
    match client.send_api_request(&LongCall) {
236
        Err(ApiError::Client(bonsaidb_client::Error::Core(bonsaidb_core::Error::Networking(
237
            networking::Error::RequestTimeout,
238
        )))) => {
239
1
            assert!(start.elapsed() < Duration::from_secs(5));
240
        }
241
        other => unreachable!("expected request timeout, got {other:?}"),
242
    }
243
1
}