Files
tuliprox/backend/src/api/model/active_user_manager.rs
T

554 lines
21 KiB
Rust
Raw Normal View History

2025-10-25 20:40:58 +02:00
use std::borrow::Cow;
2025-10-27 13:58:55 +01:00
use crate::api::model::{ActiveProviderManager};
2025-07-27 20:47:35 +02:00
use crate::api::model::SharedStreamManager;
2025-05-04 10:59:08 +02:00
use crate::model::Config;
2025-07-19 15:32:30 +02:00
use crate::model::ProxyUserCredentials;
use jsonwebtoken::get_current_timestamp;
2025-10-11 16:28:07 +02:00
use log::{debug, error, info};
2025-10-24 18:36:13 +02:00
use shared::model::{ActiveUserConnectionChange, StreamChannel, StreamInfo, UserConnectionPermission};
use shared::utils::{current_time_secs, default_grace_period_millis, default_grace_period_timeout_secs, sanitize_sensitive_info, strip_port};
2025-07-27 20:47:35 +02:00
use std::collections::HashMap;
2025-06-24 12:31:38 +02:00
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2025-06-09 13:23:27 +02:00
use std::sync::Arc;
use arc_swap::ArcSwapOption;
2025-10-27 13:58:55 +01:00
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
2025-07-27 20:47:35 +02:00
use tokio::sync::RwLock;
use crate::utils::GeoIp;
2025-10-11 16:28:07 +02:00
const USER_GC_TTL: u64 = 900; // 15 Min
const USER_CON_TTL: u64 = 10_800; // 3 hours
const USER_SESSION_LIMIT: usize = 50;
2025-10-27 08:27:35 +01:00
type ActiveUserConnectionChangeSender = tokio::sync::mpsc::UnboundedSender<ActiveUserConnectionChange>;
pub type ActiveUserConnectionChangeReceiver = tokio::sync::mpsc::UnboundedReceiver<ActiveUserConnectionChange>;
2025-07-22 17:37:56 +02:00
2025-06-24 12:31:38 +02:00
macro_rules! active_user_manager_shared_impl {
() => {
2025-10-11 17:07:39 +02:00
#[inline]
2025-07-27 20:47:35 +02:00
async fn get_active_connections(user: &Arc<RwLock<HashMap<String, UserConnectionData>>>) -> usize {
2025-10-11 16:28:07 +02:00
user.read().await.iter().filter(|(_, c)| c.connections > 0).map(|(_, c)| c.connections as usize).sum()
2025-06-24 12:31:38 +02:00
}
2025-10-11 17:07:39 +02:00
#[inline]
fn drop_connection(&self, addr: &str) {
if let Err(e) = self.close_signal_tx.send(addr.to_string()) {
debug!("No active receivers for close signal ({addr}): {e:?}");
}
}
2025-10-11 16:28:07 +02:00
async fn log_active_user(&self) {
2025-07-27 20:47:35 +02:00
let user = Arc::clone(&self.user);
let is_log_user_enabled = self.is_log_user_enabled();
2025-10-11 16:28:07 +02:00
let user_connection_count = Self::get_active_connections(&user).await;
let user_count = user.read().await.iter().filter(|(_, c)| c.connections > 0).count();
2025-10-27 08:27:35 +01:00
if let Err(err) = self.connection_change_tx.send(ActiveUserConnectionChange::Connections(user_count, user_connection_count)) {
2025-10-27 13:58:55 +01:00
error!("Failed to send active user connection change: user-count: {user_count}, user-connection-count: {user_connection_count} {err:?}");
2025-10-27 08:27:35 +01:00
}
2025-10-11 16:28:07 +02:00
if is_log_user_enabled {
info!("Active Users: {user_count}, Active User Connections: {user_connection_count}");
}
2025-06-24 12:31:38 +02:00
}
2025-07-15 12:28:25 +02:00
2025-07-27 20:47:35 +02:00
pub async fn remove_connection(&self, addr: &str) {
let username_opt = {
2025-10-11 16:28:07 +02:00
self.user_by_addr.write().await.remove(addr)
2025-07-27 20:47:35 +02:00
};
2025-07-15 12:28:25 +02:00
if let Some(username) = username_opt {
{
let mut user = self.user.write().await;
if let Some(connection_data) = user.get_mut(&username) {
if connection_data.connections > 0 {
connection_data.connections -= 1;
}
if connection_data.connections < connection_data.max_connections {
connection_data.granted_grace = false;
connection_data.grace_ts = 0;
}
connection_data.streams.retain(|c| c.addr != addr);
2025-07-15 12:28:25 +02:00
}
}
self.drop_connection(&addr);
self.shared_stream_manager.release_connection(addr, true).await;
self.provider_manager.release_connection(addr).await;
if let Err(err) = self.connection_change_tx.send(ActiveUserConnectionChange::Disconnected(addr.to_string())) {
error!("Failed to send active user connection change: {err:?}");
}
self.log_active_user().await;
2025-10-27 08:27:35 +01:00
}
2025-07-15 12:28:25 +02:00
}
2025-06-24 12:31:38 +02:00
};
}
fn get_grace_options(config: &Config) -> (u64, u64) {
let (grace_period_millis, grace_period_timeout_secs) = config.reverse_proxy.as_ref()
.and_then(|r| r.stream.as_ref())
.map_or_else(|| (default_grace_period_millis(), default_grace_period_timeout_secs()), |s| (s.grace_period_millis, s.grace_period_timeout_secs));
(grace_period_millis, grace_period_timeout_secs)
}
2025-10-27 13:58:55 +01:00
pub struct ConnectionGuardUserManager {
2025-06-24 12:31:38 +02:00
log_active_user: bool,
2025-07-27 20:47:35 +02:00
user: Arc<RwLock<HashMap<String, UserConnectionData>>>,
user_by_addr: Arc<RwLock<HashMap<String, String>>>,
2025-07-19 15:32:30 +02:00
shared_stream_manager: Arc<SharedStreamManager>,
provider_manager: Arc<ActiveProviderManager>,
2025-07-22 17:37:56 +02:00
connection_change_tx: ActiveUserConnectionChangeSender,
2025-10-11 17:07:39 +02:00
close_signal_tx: tokio::sync::broadcast::Sender<String>,
2025-06-24 12:31:38 +02:00
}
impl ConnectionGuardUserManager {
active_user_manager_shared_impl!();
2025-07-19 15:32:30 +02:00
fn is_log_user_enabled(&self) -> bool {
2025-06-24 12:31:38 +02:00
self.log_active_user
}
}
2025-04-13 16:02:55 +02:00
pub struct UserConnectionGuard {
2025-06-24 12:31:38 +02:00
manager: Arc<ConnectionGuardUserManager>,
2025-07-15 12:28:25 +02:00
// username: String,
addr: String,
2025-10-27 13:58:55 +01:00
release_tx: UnboundedSender<String>,
2025-04-13 16:02:55 +02:00
}
2025-10-27 13:58:55 +01:00
impl UserConnectionGuard {
pub fn new(manager: Arc<ConnectionGuardUserManager>, addr: &str, release_tx: UnboundedSender<String>) -> Self {
Self {
manager,
addr: addr.to_string(),
release_tx,
}
}
}
2025-04-13 16:02:55 +02:00
impl Drop for UserConnectionGuard {
fn drop(&mut self) {
let manager = self.manager.clone();
2025-07-15 12:28:25 +02:00
let addr = self.addr.clone();
2025-10-27 13:58:55 +01:00
if let Err(_err) = self.release_tx.send(addr.clone()) {
// fallback
tokio::spawn(async move {
2025-10-11 16:28:07 +02:00
manager.remove_connection(&addr).await;
});
}
2025-04-13 16:02:55 +02:00
}
}
#[derive(Clone, Debug)]
pub struct UserSession {
2025-06-05 17:51:03 +02:00
pub token: String,
pub virtual_id: u32,
pub provider: String,
pub stream_url: String,
pub addr: String,
pub ts: u64,
pub permission: UserConnectionPermission,
}
struct UserConnectionData {
2025-04-28 19:52:59 +02:00
max_connections: u32,
connections: u32,
2025-04-27 13:26:21 +02:00
granted_grace: bool,
grace_ts: u64,
sessions: Vec<UserSession>,
2025-10-24 18:36:13 +02:00
streams: Vec<StreamInfo>,
2025-10-11 16:28:07 +02:00
ts: u64,
}
impl UserConnectionData {
2025-04-30 11:04:01 +02:00
fn new(connections: u32, max_connections: u32) -> Self {
Self {
max_connections,
2025-04-30 11:04:01 +02:00
connections,
2025-04-27 13:26:21 +02:00
granted_grace: false,
grace_ts: 0,
sessions: Vec::new(),
2025-10-24 18:36:13 +02:00
streams: Vec::new(),
2025-10-11 16:28:07 +02:00
ts: current_time_secs(),
}
}
2025-06-09 13:23:27 +02:00
fn add_session(&mut self, session: UserSession) {
self.gc();
self.sessions.push(session);
}
fn gc(&mut self) {
if self.sessions.len() > USER_SESSION_LIMIT {
self.sessions.sort_by_key(|e| std::cmp::Reverse(e.ts));
self.sessions.truncate(USER_SESSION_LIMIT);
}
}
}
2025-02-06 20:31:51 +01:00
pub struct ActiveUserManager {
2025-06-24 12:31:38 +02:00
grace_period_millis: AtomicU64,
grace_period_timeout_secs: AtomicU64,
log_active_user: AtomicBool,
2025-07-27 20:47:35 +02:00
user: Arc<RwLock<HashMap<String, UserConnectionData>>>,
user_by_addr: Arc<RwLock<HashMap<String, String>>>,
2025-04-30 11:04:01 +02:00
gc_ts: Option<AtomicU64>,
close_signal_tx: tokio::sync::broadcast::Sender<String>,
2025-07-19 15:32:30 +02:00
shared_stream_manager: Arc<SharedStreamManager>,
provider_manager: Arc<ActiveProviderManager>,
geo_ip: Arc<ArcSwapOption<GeoIp>>,
2025-07-22 17:37:56 +02:00
connection_change_tx: ActiveUserConnectionChangeSender,
2025-10-27 13:58:55 +01:00
release_tx: UnboundedSender<String>,
2025-02-06 20:31:51 +01:00
}
impl ActiveUserManager {
pub fn new(config: &Config, shared_stream_manager: &Arc<SharedStreamManager>,
provider_manager: &Arc<ActiveProviderManager>,
geoip: &Arc<ArcSwapOption<GeoIp>>,
connection_change_tx: ActiveUserConnectionChangeSender) -> Arc<Self> {
2025-10-27 13:58:55 +01:00
let log_active_user: bool = config.log.as_ref().is_some_and(|l| l.log_active_user);
2025-06-24 12:31:38 +02:00
let (grace_period_millis, grace_period_timeout_secs) = get_grace_options(config);
let (close_signal_tx, _) = tokio::sync::broadcast::channel(10);
2025-10-27 13:58:55 +01:00
// Create the cleanup channel
let (cleanup_tx, mut cleanup_rx) = unbounded_channel::<String>();
let active_user_manager = Arc::new(Self {
2025-07-19 15:32:30 +02:00
grace_period_millis: AtomicU64::new(grace_period_millis),
2025-06-24 12:31:38 +02:00
grace_period_timeout_secs: AtomicU64::new(grace_period_timeout_secs),
log_active_user: AtomicBool::new(log_active_user),
2025-07-27 20:47:35 +02:00
user: Arc::new(RwLock::new(HashMap::new())),
user_by_addr: Arc::new(RwLock::new(HashMap::new())),
2025-04-30 11:04:01 +02:00
gc_ts: Some(AtomicU64::new(current_time_secs())),
2025-07-19 15:32:30 +02:00
close_signal_tx,
shared_stream_manager: Arc::clone(shared_stream_manager),
provider_manager: Arc::clone(provider_manager),
geo_ip: Arc::clone(geoip),
2025-07-22 17:37:56 +02:00
connection_change_tx,
2025-10-27 13:58:55 +01:00
release_tx: cleanup_tx,
});
let active_user_manager_clone = Arc::clone(&active_user_manager);
// Spawn the async cleanup worker
tokio::spawn(async move {
while let Some(addr) = cleanup_rx.recv().await {
2025-10-28 13:55:37 +01:00
debug!("🧹 User manager - connection releasing {addr:?}");
active_user_manager_clone.remove_connection(&addr).await;
2025-10-27 13:58:55 +01:00
}
debug!("User manager - cleanup worker terminated");
});
active_user_manager
2025-02-06 20:31:51 +01:00
}
2025-07-19 15:32:30 +02:00
active_user_manager_shared_impl!();
2025-10-27 13:58:55 +01:00
pub fn release_sender(&self) -> UnboundedSender<String> {
self.release_tx.clone()
}
2025-06-24 12:31:38 +02:00
pub fn update_config(&self, config: &Config) {
let log_active_user = config.log.as_ref().is_some_and(|l| l.log_active_user);
let (grace_period_millis, grace_period_timeout_secs) = get_grace_options(config);
self.grace_period_millis.store(grace_period_millis, Ordering::Relaxed);
self.grace_period_timeout_secs.store(grace_period_timeout_secs, Ordering::Relaxed);
self.log_active_user.store(log_active_user, Ordering::Relaxed);
}
fn clone_inner(&self) -> ConnectionGuardUserManager {
ConnectionGuardUserManager {
log_active_user: self.log_active_user.load(Ordering::Relaxed),
2025-04-16 20:28:02 +02:00
user: Arc::clone(&self.user),
2025-07-15 12:28:25 +02:00
user_by_addr: Arc::clone(&self.user_by_addr),
2025-07-19 15:32:30 +02:00
shared_stream_manager: Arc::clone(&self.shared_stream_manager),
2025-07-27 20:47:35 +02:00
provider_manager: Arc::clone(&self.provider_manager),
2025-07-22 17:37:56 +02:00
connection_change_tx: self.connection_change_tx.clone(),
2025-10-11 17:07:39 +02:00
close_signal_tx: self.close_signal_tx.clone(),
2025-04-16 20:28:02 +02:00
}
}
2025-07-27 20:47:35 +02:00
pub async fn user_connections(&self, username: &str) -> u32 {
if let Some(connection_data) = self.user.read().await.get(username) {
return connection_data.connections;
2025-02-06 20:31:51 +01:00
}
0
}
2025-04-30 11:04:01 +02:00
fn check_connection_permission(&self, username: &str, connection_data: &mut UserConnectionData) -> UserConnectionPermission {
let current_connections = connection_data.connections;
2025-04-30 11:04:01 +02:00
if current_connections < connection_data.max_connections {
2025-07-15 12:28:25 +02:00
// Reset grace period because the user is back under max_connections
2025-04-30 11:04:01 +02:00
connection_data.granted_grace = false;
connection_data.grace_ts = 0;
return UserConnectionPermission::Allowed;
}
2025-04-30 11:04:01 +02:00
let now = get_current_timestamp();
2025-07-15 12:28:25 +02:00
// Check if user already used a grace period
2025-04-30 11:04:01 +02:00
if connection_data.granted_grace {
2025-06-24 12:31:38 +02:00
if current_connections > connection_data.max_connections && now - connection_data.grace_ts <= self.grace_period_timeout_secs.load(Ordering::Relaxed) {
2025-07-15 12:28:25 +02:00
// Grace timeout, still active, deny connection
2025-04-30 11:04:01 +02:00
debug!("User access denied, grace exhausted, too many connections: {username}");
return UserConnectionPermission::Exhausted;
}
2025-04-30 11:04:01 +02:00
// Grace timeout expired, reset grace counters
connection_data.granted_grace = false;
connection_data.grace_ts = 0;
}
2025-06-24 12:31:38 +02:00
if self.grace_period_millis.load(Ordering::Relaxed) > 0 && current_connections == connection_data.max_connections {
2025-07-15 12:28:25 +02:00
// Allow a grace period once
2025-04-30 11:04:01 +02:00
connection_data.granted_grace = true;
connection_data.grace_ts = now;
2025-07-15 12:28:25 +02:00
debug!("Granted a grace period for user access: {username}");
2025-04-30 11:04:01 +02:00
return UserConnectionPermission::GracePeriod;
}
2025-04-30 11:04:01 +02:00
// Too many connections, no grace allowed
debug!("User access denied, too many connections: {username}");
UserConnectionPermission::Exhausted
}
2025-07-27 20:47:35 +02:00
pub async fn connection_permission(
2025-04-27 13:26:21 +02:00
&self,
username: &str,
max_connections: u32,
) -> UserConnectionPermission {
2025-04-27 15:23:22 +02:00
if max_connections > 0 {
2025-07-27 20:47:35 +02:00
if let Some(connection_data) = self.user.write().await.get_mut(username) {
return self.check_connection_permission(username, connection_data);
2025-04-27 15:23:22 +02:00
}
}
UserConnectionPermission::Allowed
}
2025-07-27 20:47:35 +02:00
pub async fn active_users(&self) -> usize {
2025-10-11 16:28:07 +02:00
self.user.read().await.iter().filter(|(_, c)| c.connections > 0).count()
2025-02-06 20:31:51 +01:00
}
2025-07-27 20:47:35 +02:00
pub async fn active_connections(&self) -> usize {
Self::get_active_connections(&self.user).await
2025-05-06 12:19:37 +02:00
}
2025-10-25 20:40:58 +02:00
pub async fn add_connection(&self, username: &str, max_connections: u32, addr: &str, provider: &str, stream_channel: StreamChannel, user_agent: Cow<'_, str>) -> UserConnectionGuard {
let country = {
let geoip = self.geo_ip.load();
if let Some(geoip_db) = (*geoip).as_ref() {
geoip_db.lookup(&strip_port(addr))
} else {
None
}
};
2025-10-24 18:36:13 +02:00
let stream_info = StreamInfo::new(
username,
addr,
provider,
stream_channel,
2025-10-25 20:40:58 +02:00
user_agent.to_string(),
country,
2025-10-24 18:36:13 +02:00
);
2025-07-27 20:47:35 +02:00
{
let mut user_map = self.user.write().await;
2025-10-24 18:36:13 +02:00
if let Some(connection_data) = user_map.get_mut(username) {
connection_data.connections += 1;
connection_data.max_connections = max_connections;
connection_data.streams.push(stream_info.clone());
} else {
let mut connection_data = UserConnectionData::new(1, max_connections);
connection_data.streams.push(stream_info.clone());
user_map.insert(username.to_string(), connection_data);
}
2025-07-27 20:47:35 +02:00
}
2025-07-19 15:32:30 +02:00
2025-07-27 20:47:35 +02:00
{
let mut user_by_addr = self.user_by_addr.write().await;
user_by_addr.insert(addr.to_owned(), username.to_owned());
}
2025-04-13 16:02:55 +02:00
2025-10-27 08:27:35 +01:00
if let Err(err) = self.connection_change_tx.send(ActiveUserConnectionChange::Connected(stream_info)) {
error!("Failed to send connection change: {err}");
}
2025-10-11 16:28:07 +02:00
self.log_active_user().await;
2025-04-13 16:02:55 +02:00
2025-10-27 13:58:55 +01:00
UserConnectionGuard::new(Arc::new(self.clone_inner()), addr, self.release_sender())
2025-02-06 20:31:51 +01:00
}
2025-07-19 15:32:30 +02:00
fn is_log_user_enabled(&self) -> bool {
2025-06-24 12:31:38 +02:00
self.log_active_user.load(Ordering::Relaxed)
2025-04-13 16:02:55 +02:00
}
fn new_user_session(session_token: &str, virtual_id: u32, provider: &str, stream_url: &str, addr: &str,
connection_permission: UserConnectionPermission) -> UserSession {
2025-04-30 11:04:01 +02:00
UserSession {
2025-06-05 17:51:03 +02:00
token: session_token.to_string(),
2025-04-30 11:04:01 +02:00
virtual_id,
provider: provider.to_string(),
stream_url: stream_url.to_string(),
addr: addr.to_string(),
2025-04-30 11:04:01 +02:00
ts: current_time_secs(),
permission: connection_permission,
}
}
#[allow(clippy::too_many_arguments)]
2025-07-27 20:47:35 +02:00
pub async fn create_user_session(&self, user: &ProxyUserCredentials, session_token: &str, virtual_id: u32,
2025-10-11 16:28:07 +02:00
provider: &str, stream_url: &str, addr: &str,
connection_permission: UserConnectionPermission) -> String {
self.gc();
2025-07-27 20:47:35 +02:00
let username = user.username.clone();
let mut user_map = self.user.write().await;
let connection_data = user_map.entry(username.clone()).or_insert_with(|| {
debug!("Creating session for user {username} with token {session_token} {}", sanitize_sensitive_info(stream_url));
2025-07-19 15:32:30 +02:00
let mut data = UserConnectionData::new(0, user.max_connections);
let session = Self::new_user_session(session_token, virtual_id, provider, stream_url, addr, connection_permission);
2025-07-19 15:32:30 +02:00
data.add_session(session);
data
});
// If a session exists, update it
for session in &mut connection_data.sessions {
if session.token == session_token {
session.ts = current_time_secs();
if session.stream_url != stream_url {
session.stream_url = stream_url.to_string();
}
if session.provider != provider {
session.provider = provider.to_string();
}
session.permission = connection_permission;
debug!("Using session for user {} with token {session_token} {}", user.username, sanitize_sensitive_info(stream_url));
return session.token.clone();
}
2025-04-28 19:52:59 +02:00
}
2025-07-19 15:32:30 +02:00
2025-10-24 18:36:13 +02:00
// If no session exists, create one
2025-07-19 15:32:30 +02:00
debug!("Creating session for user {} with token {session_token} {}", user.username, sanitize_sensitive_info(stream_url));
let session = Self::new_user_session(session_token, virtual_id, provider, stream_url, addr, connection_permission);
let token = session.token.clone();
connection_data.add_session(session);
token
}
2025-07-27 20:47:35 +02:00
pub async fn update_session_addr(&self, username: &str, token: &str, addr: &str) {
2025-10-11 17:07:39 +02:00
let mut user_map = self.user.write().await;
user_map.get_mut(username).and_then(|connection_data| {
connection_data.sessions.iter_mut().find_map(|session| {
if session.token == token {
let old_addr = session.addr.clone();
addr.clone_into(&mut session.addr);
Some(old_addr)
} else {
None
}
2025-10-11 16:28:07 +02:00
})
2025-10-11 17:07:39 +02:00
});
}
pub fn get_close_connection_channel(&self) -> tokio::sync::broadcast::Receiver<String> {
self.close_signal_tx.subscribe()
}
2025-10-11 16:28:07 +02:00
pub async fn get_and_update_user_session(&self, username: &str, token: &str) -> Option<UserSession> {
2025-07-27 20:47:35 +02:00
self.update_user_session(username, token).await
2025-04-28 19:52:59 +02:00
}
2025-07-27 20:47:35 +02:00
async fn update_user_session(&self, username: &str, token: &str) -> Option<UserSession> {
let mut users = self.user.write().await;
if let Some(connection_data) = users.get_mut(username) {
2025-10-11 16:28:07 +02:00
connection_data.ts = current_time_secs();
2025-10-11 16:28:07 +02:00
// Search for index of session
2025-07-27 20:47:35 +02:00
if let Some(index) = connection_data
.sessions
.iter()
.position(|s| s.token == token)
{
2025-10-11 17:07:39 +02:00
// Refresh session last access
connection_data.sessions[index].ts = current_time_secs();
// Only re-evaluate permission for limited users during grace
if connection_data.max_connections > 0
&& connection_data.sessions[index].permission == UserConnectionPermission::GracePeriod
{
2025-04-30 11:04:01 +02:00
let new_permission = self.check_connection_permission(username, connection_data);
connection_data.sessions[index].permission = new_permission;
}
return Some(connection_data.sessions[index].clone());
}
2025-04-28 19:52:59 +02:00
}
None
2025-04-28 19:52:59 +02:00
}
2025-10-24 18:36:13 +02:00
pub async fn active_streams(&self) -> Vec<StreamInfo> {
let user_map = self.user.read().await;
let mut streams = Vec::new();
for (_username, connection_data) in user_map.iter() {
for stream in &connection_data.streams {
streams.push(stream.clone());
}
}
streams
}
2025-10-11 16:28:07 +02:00
fn gc(&self) {
2025-04-30 11:04:01 +02:00
if let Some(gc_ts) = &self.gc_ts {
2025-06-09 13:23:27 +02:00
let ts = gc_ts.load(Ordering::Acquire);
2025-05-06 12:19:37 +02:00
let now = current_time_secs();
2025-07-27 20:47:35 +02:00
2025-10-11 16:28:07 +02:00
if now - ts > USER_GC_TTL {
if let Ok(mut users) = self.user.try_write() {
users.retain(|_k, v| now - v.ts < USER_CON_TTL && v.connections > 0);
for connection_data in users.values_mut() {
connection_data.sessions.retain(|s| now - s.ts < USER_CON_TTL);
}
2025-07-27 20:47:35 +02:00
2025-10-11 16:28:07 +02:00
gc_ts.store(now, Ordering::Release);
2025-04-30 11:04:01 +02:00
}
}
}
}
}
//
// mod tests {
// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
// use std::time::Instant;
// use std::thread;
//
// fn benchmark(ordering: Ordering, iterations: usize) -> u128 {
// let counter = Arc::new(AtomicUsize::new(0));
// let start = Instant::now();
//
// let handles: Vec<_> = (0..32)
// .map(|_| {
// let counter_ref = Arc::clone(&counter);
// thread::spawn(move || {
// for _ in 0..iterations {
// counter_ref.fetch_add(1, ordering);
// }
// })
// })
// .collect();
//
2025-04-28 20:28:33 +02:00
// for handle in handles {
// handle.join().unwrap();
// }
//
// let duration = start.elapsed();
// duration.as_millis()
// }
//
// #[test]
// fn test_ordering() {
// let iterations = 1_000_000;
//
// let time_acqrel = benchmark(Ordering::SeqCst, iterations);
// println!("AcqRel: {} ms", time_acqrel);
//
// let time_seqcst = benchmark(Ordering::SeqCst, iterations);
// println!("SeqCst: {} ms", time_seqcst);
// }
//
// }