1use dashmap::DashMap;
2use linkrs_core::event_dispatch::{EventFilter, EventSubscriptions, SubscriptionId};
3use linkrs_query::{SessionEvent, SessionEventCallback};
4use log::{info, warn};
5use parking_lot::RwLock;
6use rand::RngCore;
7use std::collections::HashMap;
8use std::sync::{
9 atomic::{AtomicBool, AtomicU64, Ordering},
10 Arc,
11};
12use std::time::{Duration, Instant, SystemTime};
13use tokio::time;
14
15use super::{SessionError, SessionResult};
16use crate::client::{ClientSession, Session};
17
18pub const DEFAULT_MAX_ALLOWED_CONNECTIONS: usize = 100; pub const DEFAULT_SESSION_IDLE_TIMEOUT: Duration = Duration::from_secs(600); #[derive(Debug, Clone)]
23pub struct SessionInfo {
24 pub session_id: i64,
25 pub user_name: String,
26 pub space_name: Option<String>,
27 pub graph_addr: Option<String>,
28 pub create_time: SystemTime,
29 pub last_access_time: SystemTime,
30 pub active_queries: usize,
31 pub timezone: Option<i32>,
32}
33
34impl SessionInfo {
35 pub fn from_client_session(session: &ClientSession, create_time: SystemTime) -> Self {
36 Self {
37 session_id: session.id(),
38 user_name: session.user(),
39 space_name: session.space_name(),
40 graph_addr: session.graph_addr(),
41 create_time,
42 last_access_time: SystemTime::now() - Duration::from_millis(session.idle_seconds()),
43 active_queries: session.active_queries_count(),
44 timezone: session.timezone(),
45 }
46 }
47
48 pub fn from_params(
49 session_id_str: &str,
50 user_name: &str,
51 space_name: Option<String>,
52 client_ip: &str,
53 client_port: u16,
54 ) -> Result<Self, String> {
55 let session_id = session_id_str
56 .parse::<i64>()
57 .map_err(|_| format!("Invalid session ID: {}", session_id_str))?;
58
59 let graph_addr = if client_ip.is_empty() {
60 None
61 } else {
62 Some(format!("{}:{}", client_ip, client_port))
63 };
64
65 let now = SystemTime::now();
66 Ok(Self {
67 session_id,
68 user_name: user_name.to_string(),
69 space_name,
70 graph_addr,
71 create_time: now,
72 last_access_time: now,
73 active_queries: 0,
74 timezone: None,
75 })
76 }
77
78 pub fn touch(&mut self) {
79 self.last_access_time = SystemTime::now();
80 }
81}
82
83pub struct GraphSessionManager {
84 sessions: Arc<DashMap<i64, Arc<ClientSession>>>,
86 active_sessions: Arc<DashMap<i64, Instant>>, session_create_times: Arc<RwLock<HashMap<i64, SystemTime>>>, host_addr: String,
90 max_connections: usize,
91 session_idle_timeout: Duration,
92 default_auto_commit: AtomicBool,
95 cleanup_task_running: Arc<AtomicBool>,
97 total_sessions_created: Arc<AtomicU64>,
99 session_callbacks: Arc<EventSubscriptions<SessionEvent>>,
102}
103
104impl std::fmt::Debug for GraphSessionManager {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.debug_struct("GraphSessionManager")
107 .field("sessions_count", &self.sessions.len())
108 .field("host_addr", &self.host_addr)
109 .field("max_connections", &self.max_connections)
110 .field("session_callbacks", &self.session_callbacks.len())
111 .finish()
112 }
113}
114
115impl GraphSessionManager {
116 pub fn new(
121 host_addr: String,
122 max_connections: usize,
123 session_idle_timeout: Duration,
124 ) -> Arc<Self> {
125 Self::new_with_shared(
126 host_addr,
127 max_connections,
128 session_idle_timeout,
129 Arc::new(EventSubscriptions::new()),
130 )
131 }
132
133 pub fn new_with_shared(
135 host_addr: String,
136 max_connections: usize,
137 session_idle_timeout: Duration,
138 shared: Arc<EventSubscriptions<SessionEvent>>,
139 ) -> Arc<Self> {
140 Arc::new(Self {
141 sessions: Arc::new(DashMap::new()),
142 active_sessions: Arc::new(DashMap::new()),
143 session_create_times: Arc::new(RwLock::new(HashMap::new())),
144 host_addr,
145 max_connections,
146 session_idle_timeout,
147 default_auto_commit: AtomicBool::new(true),
148 cleanup_task_running: Arc::new(AtomicBool::new(false)),
149 total_sessions_created: Arc::new(AtomicU64::new(0)),
150 session_callbacks: shared,
151 })
152 }
153
154 pub fn shared_session_callbacks(&self) -> Arc<EventSubscriptions<SessionEvent>> {
156 Arc::clone(&self.session_callbacks)
157 }
158
159 pub fn mask_session_id(session_id: i64) -> String {
161 use std::collections::hash_map::DefaultHasher;
162 use std::hash::{Hash, Hasher};
163 let mut hasher = DefaultHasher::new();
164 session_id.hash(&mut hasher);
165 format!("{:x}..{:04x}", session_id >> 48, hasher.finish() & 0xffff)
166 }
167
168 pub fn idle_timeout(&self) -> Duration {
170 self.session_idle_timeout
171 }
172
173 pub fn set_default_auto_commit(&self, auto_commit: bool) {
175 self.default_auto_commit
176 .store(auto_commit, Ordering::SeqCst);
177 }
178
179 pub fn set_shared_session_callbacks(&mut self, shared: Arc<EventSubscriptions<SessionEvent>>) {
185 self.session_callbacks = shared;
186 }
187
188 pub fn register_session_callback(&self, callback: SessionEventCallback) -> SubscriptionId {
190 self.session_callbacks.add(callback)
191 }
192
193 pub fn register_session_callback_filtered(
195 &self,
196 callback: SessionEventCallback,
197 filter: EventFilter<SessionEvent>,
198 ) -> SubscriptionId {
199 self.session_callbacks.add_filtered(callback, Some(filter))
200 }
201
202 pub fn unregister_session_callback(&self, id: SubscriptionId) -> bool {
204 self.session_callbacks.remove(id)
205 }
206
207 pub fn session_callback_count(&self) -> usize {
209 self.session_callbacks.len()
210 }
211
212 fn emit_session_event(&self, event: SessionEvent) {
213 if self.session_callbacks.is_empty() {
214 return;
215 }
216 self.session_callbacks.dispatch("session", &event);
217 }
218
219 pub async fn start_cleanup_task(self: &Arc<Self>) {
223 if self.cleanup_task_running.swap(true, Ordering::SeqCst) {
224 info!("Session cleanup task is already running");
225 return;
226 }
227
228 info!("Starting session cleanup task");
229 let manager_clone = Arc::clone(self);
230 tokio::spawn(async move {
231 manager_clone.background_reclamation_task().await;
232 });
233 }
234
235 pub fn stop_cleanup_task(&self) {
239 info!("Stopping session cleanup task");
240 self.cleanup_task_running.store(false, Ordering::SeqCst);
241 }
242
243 pub fn is_cleanup_task_running(&self) -> bool {
245 self.cleanup_task_running.load(Ordering::SeqCst)
246 }
247
248 pub async fn create_session(
250 &self,
251 user_name: String,
252 _client_ip: String,
253 ) -> Result<Arc<ClientSession>, String> {
254 info!("Creating new session for user: {}", user_name);
255
256 if self.is_out_of_connections().await {
258 warn!(
259 "Failed to create session for user {}: maximum connections exceeded",
260 user_name
261 );
262 return Err("Exceeded maximum allowed connections".to_string());
263 }
264
265 let session_id = loop {
268 let candidate = self.generate_session_id();
269 if !self.sessions.contains_key(&candidate) {
270 break candidate;
271 }
272 };
273 info!(
274 "Generated session {} for user: {}",
275 Self::mask_session_id(session_id),
276 user_name
277 );
278
279 let session = Session {
280 session_id,
281 user_name: user_name.clone(),
282 space_name: None,
283 graph_addr: Some(self.host_addr.clone()),
284 timezone: None,
285 };
286
287 let client_session = ClientSession::new(session);
288 client_session.set_auto_commit(self.default_auto_commit.load(Ordering::SeqCst));
289
290 let create_time = SystemTime::now();
292
293 self.sessions
295 .insert(session_id, Arc::clone(&client_session));
296 self.active_sessions.insert(session_id, Instant::now());
297 self.total_sessions_created.fetch_add(1, Ordering::Relaxed);
298
299 {
301 let mut create_times = self.session_create_times.write();
302 create_times.insert(session_id, create_time);
303 }
304
305 info!(
306 "Successfully created session {} for user: {}",
307 Self::mask_session_id(session_id),
308 user_name
309 );
310 self.emit_session_event(SessionEvent::SessionCreated {
311 session_id,
312 user_name,
313 });
314 Ok(client_session)
315 }
316
317 pub fn find_session(&self, session_id: i64) -> Option<Arc<ClientSession>> {
319 self.sessions.get(&session_id).map(|entry| entry.clone())
321 }
322
323 pub fn find_session_from_cache(&self, session_id: i64) -> Option<Arc<ClientSession>> {
325 self.find_session(session_id)
326 }
327
328 pub async fn remove_session(&self, session_id: i64) {
330 info!("Removing session {}", Self::mask_session_id(session_id));
331
332 if let Some(entry) = self.sessions.get(&session_id) {
333 entry.value().mark_all_queries_killed();
334 entry.value().close_all_cursors();
335 }
336 let existed = self.sessions.remove(&session_id).is_some();
338 self.active_sessions.remove(&session_id);
339
340 {
342 let mut create_times = self.session_create_times.write();
343 create_times.remove(&session_id);
344 }
345
346 info!(
347 "Successfully removed session {}",
348 Self::mask_session_id(session_id)
349 );
350 if existed {
351 self.emit_session_event(SessionEvent::SessionDestroyed { session_id });
352 }
353 }
354
355 pub async fn remove_sessions_by_username(
360 &self,
361 username: &str,
362 except_session_id: Option<i64>,
363 ) -> Vec<i64> {
364 let targets: Vec<i64> = self
365 .sessions
366 .iter()
367 .filter(|entry| {
368 entry.value().user() == username && Some(*entry.key()) != except_session_id
369 })
370 .map(|entry| *entry.key())
371 .collect();
372 for session_id in &targets {
373 self.remove_session(*session_id).await;
374 }
375 targets
376 }
377
378 pub fn get_sessions_from_local_cache(&self) -> Vec<Session> {
380 self.sessions
382 .iter()
383 .map(|entry| entry.value().get_session())
384 .collect()
385 }
386
387 pub async fn list_sessions(&self) -> Vec<SessionInfo> {
389 let create_times = self.session_create_times.read();
391
392 self.sessions
394 .iter()
395 .filter_map(|entry| {
396 let session_id = entry.key();
397 let client_session = entry.value();
398 create_times.get(session_id).map(|&create_time| {
399 SessionInfo::from_client_session(client_session, create_time)
400 })
401 })
402 .collect()
403 }
404
405 pub async fn get_session_info(&self, session_id: i64) -> Option<SessionInfo> {
407 let client_session = self.sessions.get(&session_id)?;
409
410 let create_times = self.session_create_times.read();
412 create_times
413 .get(&session_id)
414 .map(|&create_time| SessionInfo::from_client_session(&client_session, create_time))
415 }
416
417 pub async fn kill_session(
428 &self,
429 session_id: i64,
430 current_user: &str,
431 is_admin: bool,
432 ) -> SessionResult<()> {
433 info!(
434 "Attempting to kill session {} by user: {} (is_admin: {})",
435 Self::mask_session_id(session_id),
436 current_user,
437 is_admin
438 );
439
440 let target_session = self
442 .find_session(session_id)
443 .ok_or(SessionError::session_not_found(session_id))?;
444
445 let target_user = target_session.user();
446
447 if !is_admin && target_user != current_user {
449 warn!(
450 "User {} attempted to kill session {} without permission (target user: {})",
451 current_user, session_id, target_user
452 );
453 return Err(SessionError::insufficient_permission());
454 }
455
456 info!(
457 "Killing session {} (user: {}, active queries: {})",
458 Self::mask_session_id(session_id),
459 target_user,
460 target_session.active_queries_count()
461 );
462
463 self.remove_session(session_id).await;
466
467 info!(
468 "Successfully killed session {} by user: {}",
469 Self::mask_session_id(session_id),
470 current_user
471 );
472 Ok(())
473 }
474
475 pub async fn kill_multiple_sessions(
477 &self,
478 session_ids: &[i64],
479 current_user: &str,
480 is_admin: bool,
481 ) -> Vec<SessionResult<()>> {
482 let mut results = Vec::with_capacity(session_ids.len());
483 for &session_id in session_ids {
484 results.push(self.kill_session(session_id, current_user, is_admin).await);
485 }
486 results
487 }
488
489 pub async fn is_out_of_connections(&self) -> bool {
491 self.active_sessions.len() >= self.max_connections
493 }
494
495 pub async fn active_session_count(&self) -> usize {
497 self.active_sessions.len()
498 }
499
500 pub fn total_sessions_created(&self) -> u64 {
502 self.total_sessions_created.load(Ordering::Relaxed)
503 }
504
505 pub fn max_connections(&self) -> usize {
507 self.max_connections
508 }
509
510 fn generate_session_id(&self) -> i64 {
515 let mut rng = rand::thread_rng();
516 loop {
517 let raw = rng.next_u64();
518 let candidate = (raw & 0x7FFF_FFFF_FFFF_FFFF) as i64;
520 if candidate > 0 {
521 return candidate;
522 }
523 }
524 }
525
526 async fn background_reclamation_task(self: Arc<Self>) {
531 let mut interval = time::interval(Duration::from_secs(30));
532
533 loop {
534 interval.tick().await;
535
536 if !self.cleanup_task_running.load(Ordering::SeqCst) {
538 info!("Session cleanup task is stopping");
539 break;
540 }
541
542 self.reclaim_expired_sessions().await;
543 }
544
545 info!("Session cleanup task has stopped");
546 }
547
548 async fn reclaim_expired_sessions(&self) {
550 let expired_sessions: Vec<i64> = self
552 .active_sessions
553 .iter()
554 .filter(|entry| entry.value().elapsed() > self.session_idle_timeout)
555 .map(|entry| *entry.key())
556 .collect();
557
558 if !expired_sessions.is_empty() {
559 info!(
560 "Found {} expired sessions to reclaim",
561 expired_sessions.len()
562 );
563 }
564
565 for session_id in expired_sessions {
567 info!(
568 "Reclaiming expired session {}",
569 Self::mask_session_id(session_id)
570 );
571 self.remove_session(session_id).await;
572 }
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 fn create_test_session_manager() -> Arc<GraphSessionManager> {
581 GraphSessionManager::new(
582 "127.0.0.1:9669".to_string(),
583 DEFAULT_MAX_ALLOWED_CONNECTIONS,
584 DEFAULT_SESSION_IDLE_TIMEOUT,
585 )
586 }
587
588 #[tokio::test]
589 async fn test_session_manager_creation() {
590 let session_manager = create_test_session_manager();
591
592 assert_eq!(session_manager.host_addr, "127.0.0.1:9669");
593 assert_eq!(session_manager.get_sessions_from_local_cache().len(), 0);
594 assert!(!session_manager.is_cleanup_task_running());
595 }
596
597 #[tokio::test]
598 async fn test_default_auto_commit_applies_to_new_sessions() {
599 let session_manager = create_test_session_manager();
600
601 let session = session_manager
602 .create_session("autocommit".into(), "127.0.0.1".to_string())
603 .await
604 .expect("Failed to create session");
605 assert!(session.is_auto_commit());
606
607 session_manager.set_default_auto_commit(false);
608 let session = session_manager
609 .create_session("explicit".into(), "127.0.0.1".to_string())
610 .await
611 .expect("Failed to create session");
612 assert!(!session.is_auto_commit());
613 }
614
615 #[tokio::test]
616 async fn test_create_and_find_session() {
617 let session_manager = create_test_session_manager();
618
619 let session = session_manager
620 .create_session("testuser".into(), "127.0.0.1".to_string())
621 .await
622 .expect("Failed to create session");
623
624 assert_eq!(session.user(), "testuser");
625 assert!(!session_manager.is_out_of_connections().await);
626
627 let found_session = session_manager
628 .find_session(session.id())
629 .expect("Failed to find session");
630 assert_eq!(found_session.user(), "testuser");
631
632 assert!(session_manager.find_session(999999).is_none());
634 }
635
636 #[tokio::test]
637 async fn test_remove_session() {
638 let session_manager = create_test_session_manager();
639
640 let session = session_manager
641 .create_session("testuser".into(), "127.0.0.1".to_string())
642 .await
643 .expect("Failed to create session");
644
645 assert!(session_manager.find_session(session.id()).is_some());
646
647 session_manager.remove_session(session.id()).await;
648 assert!(session_manager.find_session(session.id()).is_none());
649 }
650
651 #[tokio::test]
652 async fn test_max_connections() {
653 let session_manager = GraphSessionManager::new(
654 "127.0.0.1:9669".to_string(),
655 5,
656 DEFAULT_SESSION_IDLE_TIMEOUT,
657 );
658
659 assert!(!session_manager.is_out_of_connections().await);
660
661 for i in 0..5 {
662 let _ = session_manager
663 .create_session(format!("user{}", i), "127.0.0.1".to_string())
664 .await;
665 }
666
667 assert!(session_manager.is_out_of_connections().await);
668
669 let result = session_manager
671 .create_session("user6".into(), "127.0.0.1".to_string())
672 .await;
673 assert!(result.is_err());
674 }
675
676 #[tokio::test]
677 async fn test_kill_session() {
678 let session_manager = create_test_session_manager();
679
680 let session = session_manager
681 .create_session("testuser".into(), "127.0.0.1".to_string())
682 .await
683 .expect("Failed to create session");
684
685 let session_id = session.id();
686
687 let result = session_manager
689 .kill_session(session_id, "testuser", false)
690 .await;
691 assert!(result.is_ok());
692 assert!(session_manager.find_session(session_id).is_none());
693
694 let session2 = session_manager
696 .create_session("user2".into(), "127.0.0.1".to_string())
697 .await
698 .expect("Failed to create session");
699
700 let result = session_manager
702 .kill_session(session2.id(), "otheruser", false)
703 .await;
704 assert!(result.is_err());
705
706 let result = session_manager
708 .kill_session(session2.id(), "admin", true)
709 .await;
710 assert!(result.is_ok());
711 }
712
713 #[tokio::test]
714 async fn test_list_sessions() {
715 let session_manager = create_test_session_manager();
716
717 for i in 0..3 {
719 let _ = session_manager
720 .create_session(format!("user{}", i), "127.0.0.1".to_string())
721 .await;
722 }
723
724 let sessions = session_manager.list_sessions().await;
725 assert_eq!(sessions.len(), 3);
726 }
727
728 #[tokio::test]
729 async fn test_concurrent_access() {
730 use tokio::task;
731
732 let session_manager = create_test_session_manager();
733 let mut handles = vec![];
734
735 for i in 0..10 {
737 let manager = Arc::clone(&session_manager);
738 let handle = task::spawn(async move {
739 manager
740 .create_session(format!("user{}", i), "127.0.0.1".to_string())
741 .await
742 });
743 handles.push(handle);
744 }
745
746 for handle in handles {
748 let _ = handle.await.expect("Task should complete");
749 }
750
751 assert_eq!(session_manager.get_sessions_from_local_cache().len(), 10);
753 }
754}