astraea_server/
connection.rs1use std::sync::Arc;
4use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
5use std::time::Duration;
6
7use tokio::sync::Semaphore;
8
9#[derive(Debug, Clone)]
11pub struct ConnectionConfig {
12 pub max_connections: usize,
14 pub max_concurrent_requests: usize,
16 pub idle_timeout: Duration,
18 pub request_timeout: Duration,
20 pub drain_timeout: Duration,
22}
23
24impl Default for ConnectionConfig {
25 fn default() -> Self {
26 Self {
27 max_connections: 1024,
28 max_concurrent_requests: 256,
29 idle_timeout: Duration::from_secs(300), request_timeout: Duration::from_secs(30), drain_timeout: Duration::from_secs(10), }
33 }
34}
35
36pub struct ConnectionManager {
38 config: ConnectionConfig,
39 connection_semaphore: Arc<Semaphore>,
41 request_semaphore: Arc<Semaphore>,
43 shutting_down: Arc<AtomicBool>,
45 active_connections: Arc<AtomicU64>,
47 rejected_connections: AtomicU64,
49}
50
51impl ConnectionManager {
52 pub fn new(config: ConnectionConfig) -> Self {
54 Self {
55 connection_semaphore: Arc::new(Semaphore::new(config.max_connections)),
56 request_semaphore: Arc::new(Semaphore::new(config.max_concurrent_requests)),
57 shutting_down: Arc::new(AtomicBool::new(false)),
58 active_connections: Arc::new(AtomicU64::new(0)),
59 rejected_connections: AtomicU64::new(0),
60 config,
61 }
62 }
63
64 pub fn try_accept(&self) -> Option<ConnectionGuard> {
67 if self.shutting_down.load(Ordering::Relaxed) {
68 return None;
69 }
70
71 match self.connection_semaphore.clone().try_acquire_owned() {
72 Ok(permit) => {
73 self.active_connections.fetch_add(1, Ordering::Relaxed);
74 Some(ConnectionGuard {
75 _permit: permit,
76 active_connections: Arc::clone(&self.active_connections),
77 })
78 }
79 Err(_) => {
80 self.rejected_connections.fetch_add(1, Ordering::Relaxed);
81 None
82 }
83 }
84 }
85
86 pub async fn acquire_request_permit(&self) -> Option<tokio::sync::OwnedSemaphorePermit> {
89 tokio::time::timeout(
90 self.config.request_timeout,
91 self.request_semaphore.clone().acquire_owned(),
92 )
93 .await
94 .ok()
95 .and_then(|r| r.ok())
96 }
97
98 pub fn idle_timeout(&self) -> Duration {
100 self.config.idle_timeout
101 }
102
103 pub fn request_timeout(&self) -> Duration {
105 self.config.request_timeout
106 }
107
108 pub fn is_shutting_down(&self) -> bool {
110 self.shutting_down.load(Ordering::Relaxed)
111 }
112
113 pub fn initiate_shutdown(&self) {
115 self.shutting_down.store(true, Ordering::Relaxed);
116 }
117
118 pub fn active_connections(&self) -> u64 {
120 self.active_connections.load(Ordering::Relaxed)
121 }
122
123 pub fn rejected_connections(&self) -> u64 {
125 self.rejected_connections.load(Ordering::Relaxed)
126 }
127
128 pub fn drain_timeout(&self) -> Duration {
130 self.config.drain_timeout
131 }
132
133 pub async fn wait_for_drain(&self) {
135 let start = tokio::time::Instant::now();
136 while self.active_connections() > 0 {
137 if start.elapsed() >= self.config.drain_timeout {
138 tracing::warn!(
139 "Drain timeout expired with {} active connections",
140 self.active_connections()
141 );
142 break;
143 }
144 tokio::time::sleep(Duration::from_millis(50)).await;
145 }
146 }
147}
148
149pub struct ConnectionGuard {
151 _permit: tokio::sync::OwnedSemaphorePermit,
152 active_connections: Arc<AtomicU64>,
153}
154
155impl Drop for ConnectionGuard {
156 fn drop(&mut self) {
157 self.active_connections.fetch_sub(1, Ordering::Relaxed);
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn default_config() {
167 let config = ConnectionConfig::default();
168 assert_eq!(config.max_connections, 1024);
169 assert_eq!(config.max_concurrent_requests, 256);
170 assert_eq!(config.idle_timeout, Duration::from_secs(300));
171 }
172
173 #[test]
174 fn connection_limit_enforced() {
175 let config = ConnectionConfig {
176 max_connections: 2,
177 max_concurrent_requests: 10,
178 ..Default::default()
179 };
180 let mgr = ConnectionManager::new(config);
181
182 let _g1 = mgr.try_accept().expect("first connection should succeed");
183 let _g2 = mgr.try_accept().expect("second connection should succeed");
184 assert!(
185 mgr.try_accept().is_none(),
186 "third connection should be rejected"
187 );
188 assert_eq!(mgr.active_connections(), 2);
189 assert_eq!(mgr.rejected_connections(), 1);
190 }
191
192 #[test]
193 fn connection_released_on_drop() {
194 let config = ConnectionConfig {
195 max_connections: 1,
196 ..Default::default()
197 };
198 let mgr = ConnectionManager::new(config);
199
200 {
201 let _g = mgr.try_accept().expect("should succeed");
202 assert_eq!(mgr.active_connections(), 1);
203 }
204 assert_eq!(mgr.active_connections(), 0);
206 let _g = mgr.try_accept().expect("should succeed after release");
207 }
208
209 #[test]
210 fn shutdown_rejects_new_connections() {
211 let mgr = ConnectionManager::new(ConnectionConfig::default());
212 assert!(!mgr.is_shutting_down());
213
214 mgr.initiate_shutdown();
215 assert!(mgr.is_shutting_down());
216 assert!(mgr.try_accept().is_none());
217 }
218
219 #[tokio::test]
220 async fn request_permit_works() {
221 let config = ConnectionConfig {
222 max_concurrent_requests: 2,
223 ..Default::default()
224 };
225 let mgr = ConnectionManager::new(config);
226
227 let _p1 = mgr
228 .acquire_request_permit()
229 .await
230 .expect("should get permit");
231 let _p2 = mgr
232 .acquire_request_permit()
233 .await
234 .expect("should get permit");
235 }
237
238 #[tokio::test]
239 async fn drain_completes_when_no_connections() {
240 let mgr = ConnectionManager::new(ConnectionConfig {
241 drain_timeout: Duration::from_millis(100),
242 ..Default::default()
243 });
244 mgr.wait_for_drain().await;
246 }
247}