1use std::sync::Arc;
2
3use anyhow::{
4 Result,
5 anyhow,
6};
7use arc_swap::ArcSwap;
8use futures_util::{
9 SinkExt,
10 StreamExt,
11};
12use http::{
13 HeaderMap,
14 Uri,
15};
16use hyper::upgrade::{
17 OnUpgrade,
18 Upgraded,
19};
20use hyper_util::rt::TokioIo;
21use kikiutils::{
22 atomic::enum_cell::AtomicEnumCell,
23 types::fx_collections::FxDashMap,
24};
25use num_enum::{
26 IntoPrimitive,
27 TryFromPrimitive,
28};
29use roaring::RoaringTreemap;
30use serde::Serialize;
31use tokio::{
32 join,
33 select,
34 spawn,
35 time::timeout,
36};
37use tokio_tungstenite::{
38 WebSocketStream,
39 tungstenite::{
40 Message,
41 protocol::Role,
42 },
43};
44use tokio_util::task::TaskTracker;
45
46pub(crate) mod builder;
47mod config;
48pub mod operators;
49
50use self::{
51 config::WsIoServerNamespaceConfig,
52 operators::broadcast::WsIoServerNamespaceBroadcastOperator,
53};
54use crate::{
55 WsIoServer,
56 connection::WsIoServerConnection,
57 core::{
58 packet::WsIoPacket,
59 utils::lifecycle::WsIoLifecycleCompletionSlot,
60 },
61 runtime::{
62 WsIoServerRuntime,
63 WsIoServerRuntimeStatus,
64 },
65};
66
67#[repr(u8)]
69#[derive(Debug, Eq, IntoPrimitive, PartialEq, TryFromPrimitive)]
70enum NamespaceStatus {
71 Running,
72 Stopped,
73 Stopping,
74}
75
76#[derive(Debug)]
78pub struct WsIoServerNamespace {
79 pub(crate) config: WsIoServerNamespaceConfig,
80 connection_ids: ArcSwap<RoaringTreemap>,
81 connections: FxDashMap<u64, Arc<WsIoServerConnection>>,
82 connection_task_tracker: TaskTracker,
83 rooms: FxDashMap<String, RoaringTreemap>,
84 runtime: Arc<WsIoServerRuntime>,
85 shutdown_completion: Arc<WsIoLifecycleCompletionSlot>,
86 status: AtomicEnumCell<NamespaceStatus>,
87}
88
89impl WsIoServerNamespace {
90 fn new(config: WsIoServerNamespaceConfig, runtime: Arc<WsIoServerRuntime>) -> Arc<Self> {
91 Arc::new(Self {
92 config,
93 connection_ids: ArcSwap::new(Arc::new(RoaringTreemap::new())),
94 connections: FxDashMap::default(),
95 connection_task_tracker: TaskTracker::new(),
96 rooms: FxDashMap::default(),
97 runtime,
98 shutdown_completion: Arc::new(WsIoLifecycleCompletionSlot::default()),
99 status: AtomicEnumCell::new(NamespaceStatus::Running),
100 })
101 }
102
103 async fn handle_upgraded_request(
105 self: &Arc<Self>,
106 headers: HeaderMap,
107 request_uri: Uri,
108 upgraded: Upgraded,
109 ) -> Result<()> {
110 #[cfg(feature = "tracing")]
111 tracing::debug!(
112 namespace = self.config.path,
113 request_path = request_uri.path(),
114 "handling upgraded WebSocket request"
115 );
116
117 let mut ws_stream =
119 WebSocketStream::from_raw_socket(TokioIo::new(upgraded), Role::Server, Some(self.config.websocket_config))
120 .await;
121
122 if !self.runtime.status.is(WsIoServerRuntimeStatus::Running) || !self.status.is(NamespaceStatus::Running) {
124 #[cfg(feature = "tracing")]
125 tracing::debug!(
126 namespace = self.config.path,
127 runtime_status = ?self.runtime.status.get(),
128 namespace_status = ?self.status.get(),
129 "rejecting upgraded request because server or namespace is not running"
130 );
131
132 ws_stream
133 .send((*self.encode_packet_to_message(&WsIoPacket::new_disconnect()).await?).clone())
134 .await?;
135
136 let _ = ws_stream.close(None).await;
137 return Ok(());
138 }
139
140 let (connection, mut message_rx, event_queue_rx) =
142 WsIoServerConnection::new(headers, Arc::clone(self), request_uri);
143
144 connection.start_event_dispatcher(event_queue_rx).await;
145
146 #[cfg(feature = "tracing")]
147 tracing::debug!(
148 namespace = self.config.path,
149 connection_id = connection.id(),
150 "accepted WebSocket connection"
151 );
152
153 let (mut ws_stream_writer, mut ws_stream_reader) = ws_stream.split();
155 let connection_clone = Arc::clone(&connection);
156 let mut read_ws_stream_task = spawn(async move {
157 while let Some(message) = ws_stream_reader.next().await {
158 if match message {
159 Ok(Message::Binary(bytes)) => {
160 if bytes.len() == 1 {
162 continue;
163 }
164
165 connection_clone.handle_incoming_packet(bytes).await
166 },
167 Ok(Message::Close(_)) => {
168 #[cfg(feature = "tracing")]
169 tracing::debug!(connection_id = connection_clone.id(), "server read task received close frame");
170 break;
171 },
172 Err(_err) => {
173 #[cfg(feature = "tracing")]
174 tracing::debug!(connection_id = connection_clone.id(), error = %_err, "server read task failed");
175 break;
176 },
177 Ok(Message::Text(_)) => Err(anyhow!("text WebSocket frames are not supported")),
178 _ => Ok(()),
179 }
180 .is_err()
181 {
182 #[cfg(feature = "tracing")]
183 tracing::debug!(
184 connection_id = connection_clone.id(),
185 "server read task stopped after packet handling error"
186 );
187
188 break;
189 }
190 }
191 });
192
193 let mut write_ws_stream_task = spawn(async move {
194 while let Some(message) = message_rx.recv().await {
195 let message = (*message).clone();
196 let is_close = matches!(message, Message::Close(_));
197 if ws_stream_writer.send(message).await.is_err() {
198 #[cfg(feature = "tracing")]
199 tracing::debug!("server write task failed to send message");
200 break;
201 }
202
203 if is_close {
204 #[cfg(feature = "tracing")]
205 tracing::debug!("server write task sent close frame");
206 let _ = ws_stream_writer.close().await;
207 break;
208 }
209 }
210 });
211
212 match connection.init().await {
214 Ok(()) => {
215 #[cfg(feature = "tracing")]
216 tracing::debug!(connection_id = connection.id(), "server connection initialized");
217 select! {
219 _ = &mut read_ws_stream_task => {
220 #[cfg(feature = "tracing")]
221 tracing::debug!(connection_id = connection.id(), "server read task finished; aborting write task");
222 write_ws_stream_task.abort();
223 let _ = write_ws_stream_task.await;
224 },
225 _ = &mut write_ws_stream_task => {
226 #[cfg(feature = "tracing")]
227 tracing::debug!(connection_id = connection.id(), "server write task finished; aborting read task");
228 read_ws_stream_task.abort();
229 let _ = read_ws_stream_task.await;
230 },
231 }
232 },
233 Err(_err) => {
234 #[cfg(feature = "tracing")]
235 tracing::debug!(connection_id = connection.id(), error = %_err, "server connection initialization failed");
236 read_ws_stream_task.abort();
238 connection.close();
239 let _ = join!(read_ws_stream_task, write_ws_stream_task);
240 },
241 }
242
243 connection.cleanup().await;
245
246 #[cfg(feature = "tracing")]
247 tracing::debug!(connection_id = connection.id(), "server connection stopped");
248 Ok(())
249 }
250
251 async fn shutdown_inner(self: &Arc<Self>) {
252 match self.status.get() {
253 NamespaceStatus::Stopped => return,
254 NamespaceStatus::Running => {
255 #[cfg(feature = "tracing")]
256 tracing::info!(namespace = self.config.path, "shutting down namespace");
257 self.status.store(NamespaceStatus::Stopping);
258 },
259 NamespaceStatus::Stopping => unreachable!(),
260 }
261
262 self.close_all().await;
263 self.connection_task_tracker.close();
264 self.connection_task_tracker.wait().await;
265
266 self.status.store(NamespaceStatus::Stopped);
267
268 #[cfg(feature = "tracing")]
269 tracing::info!(namespace = self.config.path, "namespace stopped");
270 }
271
272 #[inline]
274 pub(crate) fn add_connection_id_to_room(&self, room_name: &str, connection_id: u64) {
275 self.rooms
276 .entry(room_name.to_owned())
277 .or_default()
278 .insert(connection_id);
279 }
280
281 #[inline]
282 pub(crate) async fn encode_packet_to_message(&self, packet: &WsIoPacket) -> Result<Arc<Message>> {
283 let bytes = self.config.packet_codec.encode(packet)?;
284 let bytes = self.config.packet_transformer.encode(bytes).await?;
285 Ok(Arc::new(Message::Binary(bytes)))
286 }
287
288 #[inline]
289 pub(crate) fn handle_on_upgrade_request(
290 self: &Arc<Self>,
291 headers: HeaderMap,
292 on_upgrade: OnUpgrade,
293 request_uri: Uri,
294 ) {
295 #[cfg(feature = "tracing")]
296 tracing::trace!(
297 namespace = self.config.path,
298 request_path = request_uri.path(),
299 "spawning WebSocket upgrade task"
300 );
301
302 let namespace = Arc::clone(self);
303 self.connection_task_tracker.spawn(async move {
304 match timeout(namespace.config.http_request_upgrade_timeout, on_upgrade).await {
305 Ok(Ok(upgraded)) => {
306 if let Err(_err) = namespace.handle_upgraded_request(headers, request_uri, upgraded).await {
307 #[cfg(feature = "tracing")]
308 tracing::debug!(namespace = namespace.config.path, error = %_err, "upgraded request handling failed");
309 }
310 },
311 Ok(Err(_err)) => {
312 #[cfg(feature = "tracing")]
313 tracing::warn!(namespace = namespace.config.path, error = %_err, "HTTP upgrade failed");
314 },
315 Err(_err) => {
316 #[cfg(feature = "tracing")]
317 tracing::warn!(
318 namespace = namespace.config.path,
319 error = %_err,
320 timeout_ms = u64::try_from(namespace.config.http_request_upgrade_timeout.as_millis())
321 .unwrap_or(u64::MAX),
322 "HTTP upgrade timed out"
323 );
324 },
325 }
326 });
327 }
328
329 #[inline]
330 pub(crate) fn insert_connection(&self, connection: &Arc<WsIoServerConnection>) {
331 self.connections.insert(connection.id(), Arc::clone(connection));
332 self.runtime.insert_connection_id(connection.id());
333 self.connection_ids.rcu(|old_connection_ids| {
334 let mut new_connection_ids = (**old_connection_ids).clone();
335 new_connection_ids.insert(connection.id());
336 new_connection_ids
337 });
338 }
339
340 #[inline]
341 pub(crate) fn remove_connection(&self, id: u64) {
342 self.connections.remove(&id);
343 self.runtime.remove_connection_id(id);
344 self.connection_ids.rcu(|old_connection_ids| {
345 let mut new_connection_ids = (**old_connection_ids).clone();
346 new_connection_ids.remove(id);
347 new_connection_ids
348 });
349 }
350
351 #[inline]
352 pub(crate) fn remove_connection_id_from_room(&self, room_name: &str, connection_id: u64) {
353 if let Some(mut entry) = self.rooms.get_mut(room_name) {
354 entry.remove(connection_id);
355 }
356
357 self.rooms.remove_if(room_name, |_, entry| entry.is_empty());
358 }
359
360 pub async fn close_all(self: &Arc<Self>) {
362 WsIoServerNamespaceBroadcastOperator::new(Arc::clone(self))
363 .close()
364 .await;
365 }
366
367 #[inline]
368 pub fn connection_count(&self) -> usize {
369 self.connections.len()
370 }
371
372 pub async fn disconnect_all(self: &Arc<Self>) -> Result<()> {
373 WsIoServerNamespaceBroadcastOperator::new(Arc::clone(self))
374 .disconnect()
375 .await
376 }
377
378 pub async fn emit<D: Serialize>(self: &Arc<Self>, event: impl AsRef<str>, data: Option<&D>) -> Result<()> {
379 WsIoServerNamespaceBroadcastOperator::new(Arc::clone(self))
380 .emit(event, data)
381 .await
382 }
383
384 #[inline]
385 pub fn except(
386 self: &Arc<Self>,
387 room_names: impl IntoIterator<Item = impl Into<String>>,
388 ) -> WsIoServerNamespaceBroadcastOperator {
389 WsIoServerNamespaceBroadcastOperator::new(Arc::clone(self)).except(room_names)
390 }
391
392 #[inline]
393 pub fn path(&self) -> &str {
394 &self.config.path
395 }
396
397 #[inline]
398 pub fn server(&self) -> WsIoServer {
399 WsIoServer(Arc::clone(&self.runtime))
400 }
401
402 pub async fn shutdown(self: &Arc<Self>) {
403 let namespace = Arc::clone(self);
404 self.shutdown_completion
405 .wait_or_spawn(move || async move { namespace.shutdown_inner().await })
406 .await;
407 }
408
409 #[inline]
410 pub fn to(
411 self: &Arc<Self>,
412 room_names: impl IntoIterator<Item = impl Into<String>>,
413 ) -> WsIoServerNamespaceBroadcastOperator {
414 WsIoServerNamespaceBroadcastOperator::new(Arc::clone(self)).to(room_names)
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use std::time::Duration;
421
422 use tokio::{
423 task::yield_now,
424 time::sleep,
425 };
426
427 use super::*;
428 use crate::WsIoServer;
429
430 fn create_test_namespace() -> Arc<WsIoServerNamespace> {
431 WsIoServer::builder()
432 .build()
433 .new_namespace_builder("/test")
434 .register()
435 .unwrap()
436 }
437
438 #[test]
439 fn test_namespace_new() {
440 let namespace = create_test_namespace();
441 assert_eq!(namespace.path(), "/test");
442 assert_eq!(namespace.connection_count(), 0);
443 }
444
445 #[test]
446 fn test_namespace_add_remove_connection_id_to_room() {
447 let namespace = create_test_namespace();
448 namespace.add_connection_id_to_room("room1", 1);
449 namespace.add_connection_id_to_room("room1", 2);
450 namespace.add_connection_id_to_room("room2", 3);
451
452 assert_eq!(namespace.rooms.get("room1").unwrap().len(), 2);
453 assert!(namespace.rooms.get("room1").unwrap().contains(1));
454 assert!(namespace.rooms.get("room1").unwrap().contains(2));
455 assert_eq!(namespace.rooms.get("room2").unwrap().len(), 1);
456
457 namespace.remove_connection_id_from_room("room1", 1);
458 assert_eq!(namespace.rooms.get("room1").unwrap().len(), 1);
459 assert!(namespace.rooms.get("room1").unwrap().contains(2));
460
461 namespace.remove_connection_id_from_room("room1", 2);
462 namespace.remove_connection_id_from_room("room2", 3);
463
464 assert!(!namespace.rooms.contains_key("room1"));
465 assert!(!namespace.rooms.contains_key("room2"));
466 }
467
468 #[tokio::test]
469 async fn test_namespace_encode_packet_to_message() {
470 let namespace = create_test_namespace();
471 let packet = WsIoPacket::new_disconnect();
472 let message = namespace.encode_packet_to_message(&packet).await.unwrap();
473
474 assert!(matches!(&*message, Message::Binary(_)));
475 }
476
477 #[tokio::test]
478 async fn test_namespace_shutdown_idempotent() {
479 let namespace = create_test_namespace();
480 Arc::clone(&namespace).shutdown().await;
481 namespace.shutdown().await;
483 }
484
485 #[tokio::test]
486 async fn test_namespace_concurrent_shutdown_is_idempotent() {
487 let namespace = create_test_namespace();
488 namespace.connection_task_tracker.spawn(async {
489 sleep(Duration::from_millis(10)).await;
490 });
491
492 let first = Arc::clone(&namespace);
493 let first_task = spawn(async move { first.shutdown().await });
494 while !namespace.status.is(NamespaceStatus::Stopping) {
495 yield_now().await;
496 }
497
498 namespace.shutdown().await;
499 first_task.await.unwrap();
500 }
501
502 #[tokio::test]
503 async fn test_broadcast_operator_disconnect_with_no_connections() {
504 let namespace = create_test_namespace();
505 namespace.to(["room1"]).disconnect().await.unwrap();
506 }
507
508 #[tokio::test]
509 async fn test_broadcast_operator_emit_requires_running() {
510 let namespace = create_test_namespace();
511 Arc::clone(&namespace).shutdown().await;
513
514 let err = namespace
515 .to(["room1"])
516 .emit("event", Option::<&()>::None)
517 .await
518 .unwrap_err();
519
520 assert!(err.to_string().contains("invalid status"));
521 }
522}