First step provider and user connections.

-> Missing shared streams connections
This commit is contained in:
euzu
2025-11-04 18:01:44 +01:00
parent be55b15872
commit 95dde2876d
19 changed files with 1412 additions and 1521 deletions
+54 -98
View File
@@ -1,17 +1,16 @@
use crate::api::endpoints::xtream_api::{get_xtream_player_api_stream_url, ApiStreamContext};
use crate::api::model::ProviderConfig;
use crate::api::model::{ProviderAllocation, ProviderConfig, ProviderStreamState, StreamDetails, StreamingStrategy};
use crate::api::model::UserSession;
use crate::api::model::{
create_channel_unavailable_stream, create_custom_video_stream_response,
create_provider_connections_exhausted_stream, create_provider_stream,
get_stream_response_with_headers, ActiveClientStream, AppState, BoxedProviderStream,
CustomVideoStreamType, PersistPipeStream, ProviderAllocation, ProviderConnectionGuard,
ProviderStreamFactoryOptions, ProviderStreamInfo, ProviderStreamResponse, SharedStreamManager,
get_stream_response_with_headers, ActiveClientStream, AppState,
CustomVideoStreamType, PersistPipeStream,
ProviderStreamFactoryOptions, SharedStreamManager,
StreamError, ThrottledStream, UserApiRequest,
};
use crate::model::ConfigInput;
use crate::model::{ConfigTarget, ProxyUserCredentials};
use crate::tools::atomic_once_flag::AtomicOnceFlag;
use crate::tools::lru_cache::LRUResourceCache;
use crate::utils::create_new_file_for_write;
use crate::utils::request;
@@ -318,51 +317,6 @@ async fn get_redirect_alternative_url<'a>(
Cow::Borrowed(redirect_url)
}
type StreamUrl = String;
type ProviderName = String;
enum ProviderStreamState {
Custom(ProviderStreamResponse),
Available(Option<ProviderName>, StreamUrl),
GracePeriod(Option<ProviderName>, StreamUrl),
}
pub struct StreamDetails {
pub stream: Option<BoxedProviderStream>,
stream_info: ProviderStreamInfo,
pub provider_name: Option<String>,
pub grace_period_millis: u64,
pub reconnect_flag: Option<Arc<AtomicOnceFlag>>,
pub provider_connection_guard: Option<Arc<ProviderConnectionGuard>>,
}
impl StreamDetails {
pub fn from_stream(stream: BoxedProviderStream) -> Self {
Self {
stream: Some(stream),
stream_info: None,
provider_name: None,
grace_period_millis: default_grace_period_millis(),
reconnect_flag: None,
provider_connection_guard: None,
}
}
#[inline]
pub fn has_stream(&self) -> bool {
self.stream.is_some()
}
#[inline]
pub fn has_grace_period(&self) -> bool {
self.grace_period_millis > 0
}
}
struct StreamingStrategy {
provider_connection_guard: Option<Arc<ProviderConnectionGuard>>,
provider_stream_state: ProviderStreamState,
input_headers: Option<HashMap<String, String>>,
}
/// Determines the appropriate streaming strategy for the given input and stream URL.
///
@@ -388,52 +342,54 @@ async fn resolve_streaming_strategy(
force_provider: Option<&str>,
) -> StreamingStrategy {
// allocate a provider connection
let provider_connection_guard = match force_provider {
Some(provider) => {
app_state
.active_provider
.force_exact_acquire_connection(provider, &fingerprint.addr, app_state.get_release_sender())
.await
}
None => {
app_state
.active_provider
.acquire_connection(&input.name, &fingerprint.addr, app_state.get_release_sender())
.await
}
let provider_connection_handle = match force_provider {
Some(provider) => app_state.active_provider.force_exact_acquire_connection(provider, &fingerprint.addr).await,
None => app_state.active_provider.acquire_connection(&input.name, &fingerprint.addr).await,
};
let stream_response_params = match &**provider_connection_guard {
ProviderAllocation::Exhausted => {
debug!("Input {} is exhausted. No connections allowed.", input.name);
let stream = create_provider_connections_exhausted_stream(&app_state.app_config, &[]);
ProviderStreamState::Custom(stream)
}
ProviderAllocation::Available(_, ref provider)
| ProviderAllocation::GracePeriod(_, ref provider) => {
// force_stream_provider means we keep the url and the provider.
// If force_stream_provider or the input is the same as the config we don't need to get new url
let (provider, url) = if force_provider.is_some() || provider.id == input.id {
(input.name.clone(), stream_url.to_string())
} else {
(
provider.name.clone(),
get_stream_alternative_url(stream_url, input, provider),
)
};
let stream_response_params =
if let Some(allocation) = provider_connection_handle.as_ref().map(|ph| &ph.allocation) {
match allocation {
ProviderAllocation::Exhausted => {
debug!("Input {} is exhausted. No connections allowed.", input.name);
let stream = create_provider_connections_exhausted_stream(&app_state.app_config, &[]);
ProviderStreamState::Custom(stream)
}
ProviderAllocation::Available(ref provider)
| ProviderAllocation::GracePeriod(ref provider) => {
// force_stream_provider means we keep the url and the provider.
// If force_stream_provider or the input is the same as the config we don't need to get new url
let (provider, url) = if force_provider.is_some() || provider.id == input.id {
(input.name.clone(), stream_url.to_string())
} else {
(
provider.name.clone(),
get_stream_alternative_url(stream_url, input, provider),
)
};
if matches!(
&**provider_connection_guard,
ProviderAllocation::Available(_, _)
) {
ProviderStreamState::Available(Some(provider), url)
} else {
ProviderStreamState::GracePeriod(Some(provider), url)
let available = match allocation {
ProviderAllocation::Exhausted
| ProviderAllocation::Available(_) => true,
ProviderAllocation::GracePeriod(_) => false,
};
if available {
ProviderStreamState::Available(Some(provider), url)
} else {
ProviderStreamState::GracePeriod(Some(provider), url)
}
}
}
}
} else {
debug!("Input {} is exhausted. No connections allowed.", input.name);
let stream = create_provider_connections_exhausted_stream(&app_state.app_config, &[]);
ProviderStreamState::Custom(stream)
};
StreamingStrategy {
provider_connection_guard: Some(provider_connection_guard),
provider_handle: provider_connection_handle,
provider_stream_state: stream_response_params,
input_headers: Some(input.headers.clone()),
}
@@ -485,9 +441,9 @@ async fn create_stream_response_details(
config_grace_period_millis,
);
let guard_provider_name = streaming_strategy
.provider_connection_guard
.provider_handle
.as_ref()
.and_then(|guard| guard.get_provider_name());
.and_then(|guard| guard.allocation.get_provider_name());
match streaming_strategy.provider_stream_state {
// custom stream means we display our own stream like connection exhausted, channel-unavailable...
@@ -499,7 +455,7 @@ async fn create_stream_response_details(
provider_name: guard_provider_name.clone(),
grace_period_millis,
reconnect_flag: None,
provider_connection_guard: streaming_strategy.provider_connection_guard.clone(),
provider_handle: streaming_strategy.provider_handle.clone(),
}
}
ProviderStreamState::Available(_provider_name, request_url)
@@ -553,7 +509,7 @@ async fn create_stream_response_details(
// if we have no stream, we should release the provider
if stream.is_none() {
if let Some(guard) = streaming_strategy.provider_connection_guard.take() {
if let Some(guard) = streaming_strategy.provider_handle.take() {
drop(guard);
}
error!("Cant open stream {}", sanitize_sensitive_info(&request_url));
@@ -565,7 +521,7 @@ async fn create_stream_response_details(
provider_name: guard_provider_name.clone(),
grace_period_millis,
reconnect_flag,
provider_connection_guard: streaming_strategy.provider_connection_guard.take(),
provider_handle: streaming_strategy.provider_handle.take(),
}
}
}
@@ -800,7 +756,7 @@ pub async fn force_provider_stream_response(
);
return try_unwrap_body!(response.body(body_stream));
}
drop(stream_details.provider_connection_guard.take());
drop(stream_details.provider_handle.take());
if let (Some(stream), _stream_info) = create_channel_unavailable_stream(
&app_state.app_config,
&[],
@@ -876,7 +832,7 @@ pub async fn stream_response(
let provider_name = stream_details.provider_name.clone();
let provider_guard = if share_stream {
stream_details.provider_connection_guard.take()
stream_details.provider_handle.take()
} else {
None
};
@@ -974,7 +930,7 @@ pub async fn stream_response(
return stream_resp.into_response();
}
drop(stream_details.provider_connection_guard.take());
drop(stream_details.provider_handle.take());
axum::http::StatusCode::BAD_REQUEST.into_response()
}
+14 -9
View File
@@ -3,7 +3,7 @@ use crate::api::api_utils::{
force_provider_stream_response, get_stream_alternative_url, is_seek_request,
};
use crate::api::api_utils::{get_headers_from_request, try_option_bad_request, HeaderFilter};
use crate::api::model::UserSession;
use crate::api::model::{ProviderAllocation, UserSession};
use crate::api::model::{create_custom_video_stream_response, CustomVideoStreamType};
use crate::api::model::AppState;
use crate::auth::Fingerprint;
@@ -56,15 +56,20 @@ pub(in crate::api) async fn handle_hls_stream_request(
let (request_url, session_token) = match user_session {
Some(session) => {
match app_state
let handle = app_state
.active_provider
.force_exact_acquire_connection(&session.provider, &fingerprint.addr, app_state.get_release_sender())
.await
.get_provider_config()
{
Some(provider_cfg) => {
let stream_url = get_stream_alternative_url(&url, input, &provider_cfg);
(stream_url, Some(session.token.clone()))
.force_exact_acquire_connection(&session.provider, &fingerprint.addr)
.await;
match handle {
Some(provider_handle) => {
match provider_handle.allocation {
ProviderAllocation::Exhausted => (url, None),
ProviderAllocation::Available(cfg)
| ProviderAllocation::GracePeriod(cfg) => {
let stream_url = get_stream_alternative_url(&url, input, &cfg);
(stream_url, Some(session.token.clone()))
}
}
}
None => (url, None),
}
+6 -24
View File
@@ -11,7 +11,7 @@ use crate::api::endpoints::xmltv_api::xmltv_api_register;
use crate::api::endpoints::xtream_api::xtream_api_register;
use crate::api::hdhomerun_proprietary::spawn_proprietary_tasks;
use crate::api::hdhomerun_ssdp::spawn_ssdp_discover_task;
use crate::api::model::{create_cache, create_http_client, ActiveProviderManager, ActiveUserManager, AppState, CancelTokens, DownloadQueue, EventManager, HdHomerunAppState, PlaylistStorageState, ProviderConnectionGuard, ReleaseTask, SharedStreamManager};
use crate::api::model::{create_cache, create_http_client, ActiveProviderManager, ActiveUserManager, AppState, CancelTokens, DownloadQueue, EventManager, HdHomerunAppState, PlaylistStorageState, SharedStreamManager};
use crate::api::scheduler::exec_scheduler;
use crate::api::serve::serve;
use crate::model::{AppConfig, Config, Healthcheck, ProcessTargets, RateLimitConfig};
@@ -26,7 +26,6 @@ use std::io::ErrorKind;
use std::path::PathBuf;
use std::sync::atomic::AtomicI8;
use std::sync::Arc;
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
use tokio_util::sync::CancellationToken;
use tower_governor::key_extractor::SmartIpKeyExtractor;
use crate::repository::storage::get_geoip_path;
@@ -85,9 +84,9 @@ fn create_shared_data(
};
let cache = create_cache(&config);
let shared_stream_manager = Arc::new(SharedStreamManager::new());
let (provider_change_tx, provider_change_rx) = tokio::sync::mpsc::unbounded_channel();
let active_provider = Arc::new(ActiveProviderManager::new(app_config, provider_change_tx));
let shared_stream_manager = Arc::new(SharedStreamManager::new(Arc::clone(&active_provider)));
let (active_user_change_tx, active_user_change_rx) = tokio::sync::mpsc::unbounded_channel();
let active_users = ActiveUserManager::new(
&config,
@@ -99,7 +98,6 @@ fn create_shared_data(
let event_manager = Arc::new(EventManager::new(active_user_change_rx, provider_change_rx, ));
let client = create_http_client(app_config);
let release_tx = create_release_task();
AppState {
forced_targets: Arc::new(ArcSwap::new(Arc::clone(forced_targets))),
@@ -113,26 +111,10 @@ fn create_shared_data(
event_manager,
cancel_tokens: Arc::new(ArcSwap::from_pointee(CancelTokens::default())),
playlists: Arc::new(PlaylistStorageState::new()),
geoip,
release_tx
geoip
}
}
fn create_release_task() -> UnboundedSender<ReleaseTask> {
let (cleanup_tx, mut cleanup_rx) = unbounded_channel::<ReleaseTask>();
// Spawn the async cleanup worker
tokio::spawn(async move {
while let Some(release) = cleanup_rx.recv().await {
match release {
ReleaseTask::ForceProvider(guard) => guard.force_release().await,
ReleaseTask::ProviderAllocation(allocation) => ProviderConnectionGuard::release_allocation(allocation).await,
ReleaseTask::UserConnection(manager, addr) => manager.remove_connection(&addr).await,
}
}
});
cleanup_tx
}
fn exec_update_on_boot(
client: Arc<reqwest::Client>,
app_state: &Arc<AppState>,
@@ -228,6 +210,7 @@ pub(in crate::api) fn start_hdhomerun(
));
let c_token = cancel_token.clone();
let active_user_manager = Arc::clone(&app_data.active_users);
let active_provider_manager = Arc::clone(&app_data.active_provider);
tokio::spawn(async move {
let router = axum::Router::<Arc<HdHomerunAppState>>::new()
.layer(create_cors_layer())
@@ -245,7 +228,7 @@ pub(in crate::api) fn start_hdhomerun(
.await
{
Ok(listener) => {
serve(listener, router, Some(c_token), active_user_manager).await;
serve(listener, router, Some(c_token), &active_user_manager, &active_provider_manager).await;
}
Err(err) => error!("{err}"),
}
@@ -381,8 +364,7 @@ pub async fn start_server(
let router: axum::Router<()> = router.with_state(shared_data.clone());
let listener = tokio::net::TcpListener::bind(format!("{host}:{port}")).await?;
let active_user_manager = Arc::clone(&shared_data.active_users);
serve(listener, router, None, active_user_manager).await;
serve(listener, router, None, &shared_data.active_users, &shared_data.active_provider).await;
Ok(())
}
File diff suppressed because it is too large Load Diff
+74 -148
View File
@@ -1,21 +1,21 @@
use std::borrow::Cow;
use crate::api::model::{ActiveProviderManager, ReleaseTask};
use crate::api::model::SharedStreamManager;
use crate::api::model::ActiveProviderManager;
use crate::auth::Fingerprint;
use crate::model::Config;
use crate::model::ProxyUserCredentials;
use crate::utils::GeoIp;
use arc_swap::ArcSwapOption;
use jsonwebtoken::get_current_timestamp;
use log::{debug, error, info};
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};
use std::borrow::Cow;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use arc_swap::ArcSwapOption;
use tokio::runtime::Handle;
use tokio::sync::mpsc::{UnboundedSender, UnboundedReceiver};
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::sync::RwLock;
use crate::auth::Fingerprint;
use crate::utils::GeoIp;
const USER_GC_TTL: u64 = 900; // 15 Min
const USER_CON_TTL: u64 = 10_800; // 3 hours
@@ -24,66 +24,6 @@ const USER_SESSION_LIMIT: usize = 50;
type ActiveUserConnectionChangeSender = UnboundedSender<ActiveUserConnectionChange>;
pub type ActiveUserConnectionChangeReceiver = UnboundedReceiver<ActiveUserConnectionChange>;
macro_rules! active_user_manager_shared_impl {
() => {
#[inline]
async fn get_active_connections(user: &Arc<RwLock<HashMap<String, UserConnectionData>>>) -> usize {
user.read().await.iter().filter(|(_, c)| c.connections > 0).map(|(_, c)| c.connections as usize).sum()
}
async fn log_active_user(&self) {
let user = Arc::clone(&self.user);
let is_log_user_enabled = self.is_log_user_enabled();
let user_connection_count = Self::get_active_connections(&user).await;
let user_count = user.read().await.iter().filter(|(_, c)| c.connections > 0).count();
if let Err(err) = self.connection_change_tx.send(ActiveUserConnectionChange::Connections(user_count, user_connection_count)) {
error!("Failed to send active user connection change: user-count: {user_count}, user-connection-count: {user_connection_count} {err:?}");
}
if is_log_user_enabled {
info!("Active Users: {user_count}, Active User Connections: {user_connection_count}");
}
}
pub async fn remove_connection(&self, addr: &str) {
let username_opt = {
self.user_by_addr.write().await.remove(addr)
};
if let Some(username) = username_opt.as_ref() {
{
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);
}
}
}
self.shared_stream_manager.release_connection(addr, true).await;
self.provider_manager.release_connection(addr).await;
if let Err(e) = self.close_socket_signal_tx.send(addr.to_string()) {
debug!("No active receivers for close signal ({addr}): {e:?}");
}
if let Err(err) = self.connection_change_tx.send(ActiveUserConnectionChange::Disconnected(addr.to_string())) {
error!("Failed to send active user connection change: {err:?}");
}
if username_opt.is_some() {
self.log_active_user().await;
}
}
};
}
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())
@@ -91,67 +31,13 @@ fn get_grace_options(config: &Config) -> (u64, u64) {
(grace_period_millis, grace_period_timeout_secs)
}
#[derive(Clone)]
pub struct ConnectionGuardUserManager {
log_active_user: bool,
user: Arc<RwLock<HashMap<String, UserConnectionData>>>,
user_by_addr: Arc<RwLock<HashMap<String, String>>>,
shared_stream_manager: Arc<SharedStreamManager>,
provider_manager: Arc<ActiveProviderManager>,
connection_change_tx: ActiveUserConnectionChangeSender,
close_socket_signal_tx: tokio::sync::broadcast::Sender<String>,
}
impl ConnectionGuardUserManager {
active_user_manager_shared_impl!();
fn is_log_user_enabled(&self) -> bool {
self.log_active_user
}
}
pub struct UserConnectionGuard {
manager: Arc<ConnectionGuardUserManager>,
// username: String,
addr: String,
release_tx: UnboundedSender<ReleaseTask>,
}
impl UserConnectionGuard {
pub fn new(manager: Arc<ConnectionGuardUserManager>, addr: &str, release_tx: UnboundedSender<ReleaseTask>) -> Self {
Self {
manager,
addr: addr.to_string(),
release_tx,
}
}
}
impl Drop for UserConnectionGuard {
fn drop(&mut self) {
let manager = self.manager.clone();
let addr = self.addr.clone();
if let Err(_err) = self.release_tx.send(ReleaseTask::UserConnection(manager, addr)) {
if let Ok(handle) = Handle::try_current() {
let manager = self.manager.clone();
let addr = self.addr.clone();
handle.spawn(async move {
manager.remove_connection(&addr).await;
});
} else {
// No runtime
error!("💥💥💥 Dropping UserConnectionGuard without async runtime — User Connection not freed");
}
}
}
}
#[derive(Clone, Debug)]
pub struct UserSession {
pub token: String,
pub virtual_id: u32,
pub provider: String,
pub stream_url: String,
pub addr: String,
pub addr: SocketAddr,
pub ts: u64,
pub permission: UserConnectionPermission,
}
@@ -197,12 +83,12 @@ pub struct ActiveUserManager {
grace_period_timeout_secs: AtomicU64,
log_active_user: AtomicBool,
user: Arc<RwLock<HashMap<String, UserConnectionData>>>,
user_by_addr: Arc<RwLock<HashMap<String, String>>>,
user_by_addr: Arc<RwLock<HashMap<SocketAddr, String>>>,
gc_ts: Option<AtomicU64>,
shared_stream_manager: Arc<SharedStreamManager>,
provider_manager: Arc<ActiveProviderManager>,
geo_ip: Arc<ArcSwapOption<GeoIp>>,
close_socket_signal_tx: tokio::sync::broadcast::Sender<String>,
close_socket_signal_tx: tokio::sync::broadcast::Sender<SocketAddr>,
connection_change_tx: ActiveUserConnectionChangeSender,
}
@@ -230,7 +116,61 @@ impl ActiveUserManager {
})
}
active_user_manager_shared_impl!();
#[inline]
async fn get_active_connections(user: &Arc<RwLock<HashMap<String, UserConnectionData>>>) -> usize {
user.read().await.iter().filter(|(_, c)| c.connections > 0).map(|(_, c)| c.connections as usize).sum()
}
async fn log_active_user(&self) {
let user = Arc::clone(&self.user);
let is_log_user_enabled = self.is_log_user_enabled();
let user_connection_count = Self::get_active_connections(&user).await;
let user_count = user.read().await.iter().filter(|(_, c)| c.connections > 0).count();
if let Err(err) = self.connection_change_tx.send(ActiveUserConnectionChange::Connections(user_count, user_connection_count)) {
error!("Failed to send active user connection change: user-count: {user_count}, user-connection-count: {user_connection_count} {err:?}");
}
if is_log_user_enabled {
info!("Active Users: {user_count}, Active User Connections: {user_connection_count}");
}
}
pub async fn remove_connection(&self, addr: &SocketAddr) {
let username_opt = {
self.user_by_addr.write().await.remove(addr)
};
if let Some(username) = username_opt.as_ref() {
{
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);
}
}
}
self.shared_stream_manager.release_connection(addr, true).await;
self.provider_manager.release_connection(addr).await;
if let Err(e) = self.close_socket_signal_tx.send(addr.clone()) {
debug!("No active receivers for close signal ({addr}): {e:?}");
}
if let Err(err) = self.connection_change_tx.send(ActiveUserConnectionChange::Disconnected(addr.clone())) {
error!("Failed to send active user connection change: {err:?}");
}
if username_opt.is_some() {
self.log_active_user().await;
}
}
pub fn update_config(&self, config: &Config) {
let log_active_user = config.log.as_ref().is_some_and(|l| l.log_active_user);
@@ -240,18 +180,6 @@ impl ActiveUserManager {
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),
user: Arc::clone(&self.user),
user_by_addr: Arc::clone(&self.user_by_addr),
shared_stream_manager: Arc::clone(&self.shared_stream_manager),
provider_manager: Arc::clone(&self.provider_manager),
connection_change_tx: self.connection_change_tx.clone(),
close_socket_signal_tx: self.close_socket_signal_tx.clone(),
}
}
pub async fn user_connections(&self, username: &str) -> u32 {
if let Some(connection_data) = self.user.read().await.get(username) {
return connection_data.connections;
@@ -318,8 +246,7 @@ impl ActiveUserManager {
#[allow(clippy::too_many_arguments)]
pub async fn add_connection(&self, username: &str, max_connections: u32, fingerprint: &Fingerprint,
provider: &str, stream_channel: StreamChannel, user_agent: Cow<'_, str>,
release_tx: UnboundedSender<ReleaseTask>) -> UserConnectionGuard {
provider: &str, stream_channel: StreamChannel, user_agent: Cow<'_, str>) {
let country = {
let geoip = self.geo_ip.load();
if let Some(geoip_db) = (*geoip).as_ref() {
@@ -361,21 +288,20 @@ impl ActiveUserManager {
}
self.log_active_user().await;
UserConnectionGuard::new(Arc::new(self.clone_inner()), &fingerprint.addr, release_tx)
}
fn is_log_user_enabled(&self) -> bool {
self.log_active_user.load(Ordering::Relaxed)
}
fn new_user_session(session_token: &str, virtual_id: u32, provider: &str, stream_url: &str, addr: &str,
fn new_user_session(session_token: &str, virtual_id: u32, provider: &str, stream_url: &str, addr: &SocketAddr,
connection_permission: UserConnectionPermission) -> UserSession {
UserSession {
token: session_token.to_string(),
virtual_id,
provider: provider.to_string(),
stream_url: stream_url.to_string(),
addr: addr.to_string(),
addr: addr.clone(),
ts: current_time_secs(),
permission: connection_permission,
}
@@ -383,7 +309,7 @@ impl ActiveUserManager {
#[allow(clippy::too_many_arguments)]
pub async fn create_user_session(&self, user: &ProxyUserCredentials, session_token: &str, virtual_id: u32,
provider: &str, stream_url: &str, addr: &str,
provider: &str, stream_url: &str, addr: &SocketAddr,
connection_permission: UserConnectionPermission) -> String {
self.gc();
@@ -421,13 +347,13 @@ impl ActiveUserManager {
token
}
pub async fn update_session_addr(&self, username: &str, token: &str, addr: &str) {
pub async fn update_session_addr(&self, username: &str, token: &str, addr: &SocketAddr) {
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);
let old_addr = session.addr;
session.addr = addr.clone();
Some(old_addr)
} else {
None
@@ -436,7 +362,7 @@ impl ActiveUserManager {
});
}
pub fn get_close_connection_channel(&self) -> tokio::sync::broadcast::Receiver<String> {
pub fn get_close_connection_channel(&self) -> tokio::sync::broadcast::Receiver<SocketAddr> {
self.close_socket_signal_tx.subscribe()
}
@@ -459,7 +385,7 @@ impl ActiveUserManager {
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
&& connection_data.sessions[index].permission == UserConnectionPermission::GracePeriod
{
let new_permission = self.check_connection_permission(username, connection_data);
connection_data.sessions[index].permission = new_permission;
+1 -6
View File
@@ -1,5 +1,5 @@
use crate::api::config_watch::exec_config_watch;
use crate::api::model::{ActiveProviderManager, EventManager, PlaylistStorage, PlaylistStorageState, ReleaseTask, SharedStreamManager};
use crate::api::model::{ActiveProviderManager, EventManager, PlaylistStorage, PlaylistStorageState, SharedStreamManager};
use crate::api::model::{ActiveUserManager, DownloadQueue};
use crate::api::scheduler::exec_scheduler;
use crate::model::{
@@ -19,7 +19,6 @@ use std::collections::HashMap;
use std::sync::atomic::AtomicI8;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use crate::repository::storage::get_geoip_path;
@@ -261,13 +260,9 @@ pub struct AppState {
pub cancel_tokens: Arc<ArcSwap<CancelTokens>>,
pub playlists: Arc<PlaylistStorageState>,
pub geoip: Arc<ArcSwapOption<GeoIp>>,
pub(crate) release_tx: UnboundedSender<ReleaseTask>
}
impl AppState {
pub fn get_release_sender(&self) -> UnboundedSender<ReleaseTask> {
self.release_tx.clone()
}
pub(in crate::api::model) async fn set_config(&self,config: Config) -> Result<UpdateChanges, TuliproxError> {
let changes = self.detect_changes_for_config(&config);
+3 -3
View File
@@ -11,7 +11,7 @@ mod stream;
mod provider_config;
mod event_manager;
mod playlist_mem_cache;
mod release_task;
mod provider_lineup_manager;
pub use self::app_state::*;
pub use self::playlist_mem_cache::*;
@@ -23,7 +23,7 @@ pub(in crate::api) use self::stream_error::*;
pub(crate) use self::streams::*;
pub(in crate::api) use self::active_user_manager::*;
pub(in crate::api) use self::active_provider_manager::*;
pub(in crate::api) use self::stream::*;
pub(in crate::api) use self::provider_config::*;
pub use self::stream::*;
pub use self::event_manager::*;
pub use self::release_task::*;
pub use self::provider_lineup_manager::*;
+1 -1
View File
@@ -1,5 +1,4 @@
use std::fmt;
use crate::api::model::ProviderAllocation;
use crate::model::{ConfigInput, ConfigInputAlias, InputUserInfo};
use jsonwebtoken::get_current_timestamp;
use log::{debug};
@@ -8,6 +7,7 @@ use std::sync::{Arc};
use tokio::sync::RwLock;
use shared::model::InputType;
use shared::write_if_some;
use crate::api::model::ProviderAllocation;
pub type ProviderConnectionChangeSender = tokio::sync::mpsc::UnboundedSender<(String, usize)>;
pub type ProviderConnectionChangeReceiver = tokio::sync::mpsc::UnboundedReceiver<(String, usize)>;
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
use std::sync::Arc;
use crate::api::model::{ConnectionGuardUserManager, ProviderAllocation, ProviderConnectionGuard};
#[derive(Clone)]
pub enum ReleaseTask {
ForceProvider(Arc<ProviderConnectionGuard>),
ProviderAllocation(ProviderAllocation),
UserConnection(Arc<ConnectionGuardUserManager>, String)
}
+52 -1
View File
@@ -1,8 +1,12 @@
use crate::api::model::StreamError;
use std::collections::HashMap;
use std::sync::Arc;
use crate::api::model::{ProviderHandle, StreamError};
use axum::http::StatusCode;
use bytes::Bytes;
use futures::stream::BoxStream;
use url::Url;
use shared::utils::default_grace_period_millis;
use crate::tools::atomic_once_flag::AtomicOnceFlag;
pub type BoxedProviderStream = BoxStream<'static, Result<Bytes, StreamError>>;
pub type ProviderStreamHeader = Vec<(String, String)>;
@@ -11,3 +15,50 @@ pub type ProviderStreamInfo = Option<(ProviderStreamHeader, StatusCode, Option<U
pub type ProviderStreamResponse = (Option<BoxedProviderStream>, ProviderStreamInfo);
pub type ProviderStreamFactoryResponse = (BoxedProviderStream, ProviderStreamInfo);
type StreamUrl = String;
type ProviderName = String;
pub enum ProviderStreamState {
Custom(ProviderStreamResponse),
Available(Option<ProviderName>, StreamUrl),
GracePeriod(Option<ProviderName>, StreamUrl),
}
pub struct StreamDetails {
pub stream: Option<BoxedProviderStream>,
pub(crate) stream_info: ProviderStreamInfo,
pub provider_name: Option<String>,
pub grace_period_millis: u64,
pub reconnect_flag: Option<Arc<AtomicOnceFlag>>,
pub provider_handle: Option<ProviderHandle>,
}
impl StreamDetails {
pub fn from_stream(stream: BoxedProviderStream) -> Self {
Self {
stream: Some(stream),
stream_info: None,
provider_name: None,
grace_period_millis: default_grace_period_millis(),
reconnect_flag: None,
provider_handle: None,
}
}
#[inline]
pub fn has_stream(&self) -> bool {
self.stream.is_some()
}
#[inline]
pub fn has_grace_period(&self) -> bool {
self.grace_period_millis > 0
}
}
pub struct StreamingStrategy {
pub provider_handle: Option<ProviderHandle>,
pub provider_stream_state: ProviderStreamState,
pub input_headers: Option<HashMap<String, String>>,
}
@@ -1,10 +1,8 @@
use crate::api::api_utils::StreamDetails;
use crate::api::model::AppState;
use crate::api::model::{AppState, ProviderHandle, StreamDetails};
use crate::api::model::BoxedProviderStream;
use crate::api::model::StreamError;
use crate::api::model::TimedClientStream;
use crate::api::model::TransportStreamBuffer;
use crate::api::model::{ProviderConnectionGuard, UserConnectionGuard};
use crate::model::ProxyUserCredentials;
use bytes::Bytes;
use futures::Stream;
@@ -28,10 +26,8 @@ const PROVIDER_EXHAUSTED_STREAM: u8 = 3_u8;
pub(in crate::api) struct ActiveClientStream {
inner: BoxedProviderStream,
send_custom_stream_flag: Option<Arc<AtomicU8>>,
#[allow(unused)]
user_connection_guard: Option<UserConnectionGuard>,
#[allow(dead_code)]
provider_connection_guard: Option<Arc<ProviderConnectionGuard>>,
provider_handle: Option<ProviderHandle>,
custom_video: (Option<TransportStreamBuffer>, Option<TransportStreamBuffer>),
waker: Option<Arc<AtomicWaker>>,
}
@@ -52,11 +48,11 @@ impl ActiveClientStream {
let provider_name = stream_details.provider_name.as_ref().map_or_else(String::new, ToString::to_string);
let user_agent = req_headers.get(USER_AGENT).map(|h| String::from_utf8_lossy(h.as_bytes())).unwrap_or_default();
let user_connection_guard = Some(app_state.active_users.add_connection(username, user.max_connections, fingerprint, &provider_name, stream_channel, user_agent, app_state.get_release_sender()).await);
app_state.active_users.add_connection(username, user.max_connections, fingerprint, &provider_name, stream_channel, user_agent).await;
let cfg = &app_state.app_config;
let waker = Some(Arc::new(AtomicWaker::new()));
let waker_clone = waker.clone();
let grace_stop_flag = Self::stream_grace_period(app_state, &stream_details, grant_user_grace_period, user, &fingerprint.addr, waker_clone.clone());
let grace_stop_flag = Self::stream_grace_period(app_state, &stream_details, grant_user_grace_period, user, &fingerprint, waker_clone.clone());
let custom_response = cfg.custom_stream_response.load();
let custom_video = custom_response.as_ref()
.map_or((None, None), |c|
@@ -67,8 +63,8 @@ impl ActiveClientStream {
let stream = match stream_details.stream.take() {
None => {
if let Some(guard) = stream_details.provider_connection_guard.as_ref() {
guard.release().await;
if let Some(guard) = stream_details.provider_handle.as_ref() {
app_state.active_provider.release_handle(guard).await;
}
futures::stream::empty::<Result<Bytes, StreamError>>().boxed()
}
@@ -89,8 +85,7 @@ impl ActiveClientStream {
Self {
inner: stream,
user_connection_guard,
provider_connection_guard: stream_details.provider_connection_guard,
provider_handle: stream_details.provider_handle,
send_custom_stream_flag: grace_stop_flag,
custom_video,
waker,
@@ -101,7 +96,7 @@ impl ActiveClientStream {
stream_details: &StreamDetails,
user_grace_period: bool,
user: &ProxyUserCredentials,
addr: &str,
fingerprint: &Fingerprint,
waker: Option<Arc<AtomicWaker>>) -> Option<Arc<AtomicU8>> {
let active_users = Arc::clone(&app_state.active_users);
let active_provider = Arc::clone(&app_state.active_provider);
@@ -127,7 +122,7 @@ impl ActiveClientStream {
let stream_strategy_flag_copy = Arc::clone(&stream_strategy_flag);
let grace_period_millis = stream_details.grace_period_millis;
let address = addr.to_string();
let address = fingerprint.addr.clone();
let user_manager = Arc::clone(&active_users);
let provider_manager = Arc::clone(&active_provider);
let share_manager = Arc::clone(&shared_stream_manager);
@@ -1,4 +1,4 @@
use crate::api::model::{ReleaseTask, StreamError};
use crate::api::model::{ActiveProviderManager, ProviderHandle, StreamError};
use crate::api::model::AppState;
use crate::utils::debug_if_enabled;
use bytes::Bytes;
@@ -7,18 +7,17 @@ use futures::{Stream, StreamExt};
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::fmt::{Debug, Formatter};
use std::net::SocketAddr;
use std::sync::Arc;
use crate::api::model::streams::buffered_stream::CHANNEL_SIZE;
use crate::api::model::BoxedProviderStream;
use crate::api::model::ProviderConnectionGuard;
use crate::utils::trace_if_enabled;
use log::{debug, error, trace};
use log::{debug, trace};
use shared::utils::sanitize_sensitive_info;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::runtime::Handle;
use tokio::sync::mpsc::{Sender, UnboundedSender};
use tokio::sync::mpsc::{Sender};
use tokio::sync::{mpsc, Mutex, RwLock};
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::sync::CancellationToken;
@@ -54,7 +53,7 @@ fn convert_stream(stream: BoxStream<Bytes>) -> BoxStream<Result<Bytes, StreamErr
ReceiverStreamWrapper { stream }.boxed()
}
type SubscriberId = String;
type SubscriberId = SocketAddr;
struct BurstBuffer {
@@ -100,6 +99,21 @@ impl BurstBuffer {
}
}
async fn send_burst_buffer(
start_buffer: &VecDeque<Bytes>,
client_tx: &Sender<Bytes>,
cancellation_token: &CancellationToken) {
for buf in start_buffer {
if cancellation_token.is_cancelled() { return; }
if let Err(err) = client_tx.send(buf.clone()).await {
debug!("Error sending current chunk: {err}");
return; // stop on send error
}
}
}
/// Represents the state of a shared provider URL.
///
/// - `headers`: The initial connection headers used during the setup of the shared stream.
@@ -107,40 +121,16 @@ impl BurstBuffer {
pub struct SharedStreamState {
headers: Vec<(String, String)>,
buf_size: usize,
provider_guard: Option<Arc<ProviderConnectionGuard>>,
provider_guard: Option<ProviderHandle>,
subscribers: RwLock<HashMap<SubscriberId, CancellationToken>>,
broadcaster: tokio::sync::broadcast::Sender<Bytes>,
stop_token: CancellationToken,
burst_buffer: Arc<Mutex<BurstBuffer>>,
release_tx: UnboundedSender<ReleaseTask>,
}
impl Drop for SharedStreamState {
fn drop(&mut self) {
if let Some(guard) = self.provider_guard.as_ref() {
if let Err(_err) = self.release_tx.send(ReleaseTask::ForceProvider(Arc::clone(guard))) {
// fallback
if let Ok(handle) = Handle::try_current() {
let guard = guard.clone();
handle.spawn(async move {
guard.force_release().await;
});
} else {
// No runtime
error!("💥💥💥 Dropping Shared Stream ProviderConnectionGuard without async runtime — Provider Connection not freed");
}
}
}
}
}
impl SharedStreamState {
fn new(headers: Vec<(String, String)>, buf_size: usize,
provider_guard: Option<Arc<ProviderConnectionGuard>>,
release_tx: UnboundedSender<ReleaseTask>) -> Self {
if let Some(guard) = &provider_guard {
guard.disable_release();
}
provider_guard: Option<ProviderHandle>) -> Self {
let (broadcaster, _) = tokio::sync::broadcast::channel(buf_size);
// TODO channel size versus byte size, channels are chunk sized, burst_buffer byte sized
let burst_buffer_size_in_bytes = MIN_SHARED_BUFFER_SIZE.max(buf_size * 1024 * 12);
@@ -152,20 +142,19 @@ impl SharedStreamState {
broadcaster,
stop_token: CancellationToken::new(),
burst_buffer: Arc::new(Mutex::new(BurstBuffer::new(burst_buffer_size_in_bytes))),
release_tx
}
}
async fn subscribe(&self, addr: &str, manager: Arc<SharedStreamManager>) -> (BoxedProviderStream, Option<String>) {
async fn subscribe(&self, addr: &SocketAddr, manager: Arc<SharedStreamManager>) -> (BoxedProviderStream, Option<String>) {
let (client_tx, client_rx) = mpsc::channel(self.buf_size);
let mut broadcast_rx = self.broadcaster.subscribe();
let cancel_token = CancellationToken::new();
self.subscribers.write().await.insert(addr.to_string(), cancel_token.clone());
self.subscribers.write().await.insert(addr.clone(), cancel_token.clone());
let address = addr.to_string();
let client_tx_clone = client_tx.clone();
let burst_buffer = self.burst_buffer.clone();
let address = addr.clone();
tokio::spawn(async move {
let snapshot = {
let buffer = burst_buffer.lock().await;
@@ -179,14 +168,14 @@ impl SharedStreamState {
biased;
() = cancel_token.cancelled() => {
debug!("Client disconnected from shared stream: {address}");
debug!("Client disconnected from shared stream: {}", address.to_string());
break;
}
result = broadcast_rx.recv() => {
match result {
Ok(data) => {
if let Err(err) = client_tx.send(data).await {
debug!("Shared stream client send error: {address} {err}");
debug!("Shared stream client send error: {} {err}", address.to_string());
break;
}
loop_cnt += 1;
@@ -196,7 +185,7 @@ impl SharedStreamState {
}
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
trace!("Client lagged behind. Skipped {skipped} messages. {address}");
trace!("Client lagged behind. Skipped {skipped} messages. {}", address.to_string());
loop_cnt += 1;
if loop_cnt >= YIELD_COUNTER {
tokio::task::yield_now().await;
@@ -211,10 +200,7 @@ impl SharedStreamState {
manager.release_connection(&address, false).await;
});
let provider = match &self.provider_guard {
None => None,
Some(connection_guard) => connection_guard.get_provider_name()
};
let provider = self.provider_guard.as_ref().and_then(|h| h.allocation.get_provider_name());
(convert_stream(ReceiverStream::new(client_rx).boxed()), provider)
}
@@ -292,17 +278,19 @@ impl SharedStreamState {
}
type SharedStreamRegister = HashMap<String, Arc<SharedStreamState>>;
type SharedStreamAddrRegister = HashMap<String, String>;
type SharedStreamAddrRegister = HashMap<SubscriberId, String>;
pub struct SharedStreamManager {
provider_manager: Arc<ActiveProviderManager>,
shared_streams: RwLock<SharedStreamRegister>,
shared_streams_by_addr: RwLock<SharedStreamAddrRegister>,
}
impl SharedStreamManager {
pub(crate) fn new() -> Self {
pub(crate) fn new(provider_manager: Arc<ActiveProviderManager>) -> Self {
Self {
provider_manager,
shared_streams: RwLock::new(SharedStreamRegister::new()),
shared_streams_by_addr: RwLock::new(SharedStreamAddrRegister::new()),
}
@@ -321,7 +309,7 @@ impl SharedStreamManager {
let shared_state = self.shared_streams.write().await.remove(stream_url);
{
let mut by_addr = self.shared_streams_by_addr.write().await;
let remove_keys: Vec<String> = by_addr.iter()
let remove_keys: Vec<SocketAddr> = by_addr.iter()
.filter_map(|(addr, url)| if url == stream_url { Some(addr.clone()) } else { None })
.collect();
for k in remove_keys {
@@ -332,8 +320,8 @@ impl SharedStreamManager {
if let Some(shared_state) = shared_state {
debug_if_enabled!("Unregistering shared stream {}", sanitize_sensitive_info(stream_url));
if let Some(guard) = &shared_state.provider_guard {
guard.force_release().await;
if let Some(provider_handle) = &shared_state.provider_guard {
self.provider_manager.release_handle(provider_handle).await;
}
if send_stop_signal {
@@ -343,7 +331,7 @@ impl SharedStreamManager {
}
}
pub async fn release_connection(&self, addr: &str, send_stop_signal: bool) {
pub async fn release_connection(&self, addr: &SocketAddr, send_stop_signal: bool) {
let stream_url = {
self.shared_streams_by_addr.write().await.remove(addr)
};
@@ -379,7 +367,7 @@ impl SharedStreamManager {
}
}
async fn subscribe_stream(&self, stream_url: &str, addr: &str, manager: Arc<SharedStreamManager>) -> Option<(BoxedProviderStream, Option<String>)> {
async fn subscribe_stream(&self, stream_url: &str, addr: &SocketAddr, manager: Arc<SharedStreamManager>) -> Option<(BoxedProviderStream, Option<String>)> {
let shared_stream_state = {
self.shared_streams.read().await.get(stream_url).map(Arc::clone)
};
@@ -387,7 +375,7 @@ impl SharedStreamManager {
None => None,
Some(stream_state) => {
debug_if_enabled!("Responding to existing shared client stream {addr} {}", sanitize_sensitive_info(stream_url));
self.shared_streams_by_addr.write().await.insert(addr.to_string(), stream_url.to_owned());
self.shared_streams_by_addr.write().await.insert(addr.clone(), stream_url.to_owned());
Some(stream_state.subscribe(addr, manager).await)
}
}
@@ -401,20 +389,20 @@ impl SharedStreamManager {
app_state: &AppState,
stream_url: &str,
bytes_stream: S,
addr: &str,
addr: &SocketAddr,
headers: Vec<(String, String)>,
buffer_size: usize,
provider_guard: Option<Arc<ProviderConnectionGuard>>) -> Option<(BoxedProviderStream, Option<String>)>
provider_guard: Option<ProviderHandle>) -> Option<(BoxedProviderStream, Option<String>)>
where
S: Stream<Item=Result<Bytes, E>> + Unpin + 'static + Send,
E: std::fmt::Debug + Send,
{
let buf_size = CHANNEL_SIZE.max(buffer_size);
let shared_state = Arc::new(SharedStreamState::new(headers, buf_size, provider_guard, app_state.get_release_sender()));
let shared_state = Arc::new(SharedStreamState::new(headers, buf_size, provider_guard));
app_state.shared_stream_manager.register(stream_url, Arc::clone(&shared_state)).await;
debug_if_enabled!("Created shared provider stream {}", sanitize_sensitive_info(stream_url));
let subscribed_stream = Self::subscribe_shared_stream(app_state, stream_url, addr).await;
shared_state.broadcast(stream_url, bytes_stream, Arc::clone(&app_state.shared_stream_manager));
debug_if_enabled!("Created shared provider stream {}", sanitize_sensitive_info(stream_url));
subscribed_stream
}
@@ -422,23 +410,9 @@ impl SharedStreamManager {
pub async fn subscribe_shared_stream(
app_state: &AppState,
stream_url: &str,
addr: &str,
addr: &SocketAddr,
) -> Option<(BoxedProviderStream, Option<String>)> {
let manager = Arc::clone(&app_state.shared_stream_manager);
app_state.shared_stream_manager.subscribe_stream(stream_url, addr, manager).await
}
}
async fn send_burst_buffer(
start_buffer: &VecDeque<Bytes>,
client_tx: &Sender<Bytes>,
cancellation_token: &CancellationToken) {
for buf in start_buffer {
if cancellation_token.is_cancelled() { return; }
if let Err(err) = client_tx.send(buf.clone()).await {
debug!("Error sending current chunk: {err}");
return; // stop on send error
}
}
}
+15 -9
View File
@@ -17,7 +17,7 @@ use std::time::Duration;
use tokio::sync::watch;
use tokio_util::sync::CancellationToken;
use tower::{Service, ServiceExt};
use crate::api::model::ActiveUserManager;
use crate::api::model::{ActiveProviderManager, ActiveUserManager};
#[derive(Debug)]
struct IncomingStream
@@ -41,7 +41,8 @@ impl axum::extract::connect_info::Connected<IncomingStream> for SocketAddr {
pub async fn serve(listener: tokio::net::TcpListener,
router: axum::Router<()>,
cancel_token: Option<CancellationToken>,
user_manager: Arc<ActiveUserManager>) {
user_manager: &Arc<ActiveUserManager>,
provider_manager: &Arc<ActiveProviderManager>) {
let (signal_tx, _signal_rx) = watch::channel(());
let mut make_service = router.into_make_service_with_connect_info::<SocketAddr>();
@@ -54,7 +55,7 @@ pub async fn serve(listener: tokio::net::TcpListener,
}
accept_result = listener.accept() => {
let Ok((socket, remote_addr)) = accept_result else { continue };
handle_connection(&mut make_service, &signal_tx, socket, remote_addr, Arc::clone(&user_manager)).await;
handle_connection(&mut make_service, &signal_tx, socket, remote_addr, Arc::clone(user_manager), Arc::clone(provider_manager)).await;
}
}
}
@@ -62,7 +63,7 @@ pub async fn serve(listener: tokio::net::TcpListener,
None => {
loop {
let Ok((socket, remote_addr)) = listener.accept().await else { continue };
handle_connection(&mut make_service, &signal_tx, socket, remote_addr, Arc::clone(&user_manager)).await;
handle_connection(&mut make_service, &signal_tx, socket, remote_addr, Arc::clone(user_manager), Arc::clone(provider_manager)).await;
}
}
}
@@ -74,6 +75,7 @@ async fn handle_connection<M, S>(
socket: tokio::net::TcpStream,
remote_addr: SocketAddr,
user_manager: Arc<ActiveUserManager>,
provider_manager: Arc<ActiveProviderManager>,
)
where
M: for<'a> Service<IncomingStream, Error=Infallible, Response=S> + Send + 'static,
@@ -124,7 +126,7 @@ where
let hyper_service = TowerToHyperService::new(tower_service);
let signal_tx = signal_tx.clone();
let addr_str = remote_addr.to_string();
let addr = remote_addr;
tokio::spawn(async move {
#[allow(unused_mut)]
@@ -133,9 +135,10 @@ where
let mut signal_closed = pin!(signal_tx.closed().fuse());
let user_manager_clone = Arc::clone(&user_manager);
let provider_manager_clone = Arc::clone(&provider_manager);
let mut addr_close_rx = user_manager_clone.get_close_connection_channel();
trace!("Connection opened: {addr_str}");
trace!("Connection opened: {}", addr.to_string());
loop {
tokio::select! {
@@ -143,18 +146,21 @@ where
if let Err(err) = result {
trace!("failed to serve connection: {err:#}");
}
user_manager_clone.remove_connection(&addr_str).await;
user_manager_clone.remove_connection(&addr).await;
provider_manager_clone.release_connection(&addr).await;
break;
}
() = &mut signal_closed => {
user_manager_clone.remove_connection(&addr_str).await;
user_manager_clone.remove_connection(&addr).await;
provider_manager_clone.release_connection(&addr).await;
debug!("Connection gracefully closed: {remote_addr}");
conn.as_mut().graceful_shutdown();
}
Ok(msg) = addr_close_rx.recv() => {
// this comes from user manager itself when a user connection is closed
// no need to call `user_manager.remove_connection()`
if msg == addr_str {
if msg == addr {
provider_manager_clone.release_connection(&addr).await;
debug!("Forced client disconnect {msg}");
conn.as_mut().graceful_shutdown();
break;
+3 -3
View File
@@ -19,11 +19,11 @@ fn validate_header(value: &str) -> Option<String> {
pub struct Fingerprint {
pub key: String,
pub client_ip: String,
pub addr: String,
pub addr: SocketAddr,
}
impl Fingerprint {
pub fn new(key: String, client_ip: String, addr: String) -> Self {
pub fn new(key: String, client_ip: String, addr: SocketAddr) -> Self {
Self {
key,
client_ip,
@@ -82,6 +82,6 @@ impl Fingerprint {
// debug!("{key}, {client_ip}, {addr}");
Ok(Fingerprint::new(key, client_ip, addr.to_string()))
Ok(Fingerprint::new(key, client_ip, addr))
}
}
+5
View File
@@ -361,6 +361,8 @@
--brick-port-invalid-background-color: #e74c3c;
--source-editor-delete-color: #FF0000FF;
--source-editor-line-color: #ffffff;
--source-editor-pending-line-color: yellow;
}
@@ -628,4 +630,7 @@ body[data-theme="bright"] {
--floating-particle-gradient-color-1: rgba(0,0,0,0.3);
--floating-particle-gradient-color-2: rgba(0,0,0,0);
--source-editor-line-color: #000000;
--source-editor-pending-line-color: yellow;
}
@@ -427,7 +427,7 @@ pub fn SourceEditor() -> Html {
Some(html! {
<g>
<path d={d} stroke="white" fill="transparent" stroke-width="2"/>
<path d={d} stroke="var(--source-editor-line-color)" fill="transparent" stroke-width="2"/>
{ if *delete_mode {
let mid_x = (from_x + to_x) / 2.0;
let mid_y = (from_y + to_y) / 2.0;
@@ -454,7 +454,7 @@ pub fn SourceEditor() -> Html {
<line
x1={x1.to_string()} y1={y1.to_string()}
x2={x2.to_string()} y2={y2.to_string()}
stroke="yellow"
stroke="var(--source-editor-pending-line-color)"
stroke-width="2"
stroke-dasharray="4 2" />
}
@@ -1,3 +1,4 @@
use std::net::SocketAddr;
use crate::model::StreamInfo;
#[allow(clippy::large_enum_variant)]
@@ -5,6 +6,6 @@ use crate::model::StreamInfo;
#[serde(tag = "type", content = "payload", rename_all = "camelCase")]
pub enum ActiveUserConnectionChange {
Connected(StreamInfo),
Disconnected(String), // addr
Disconnected(SocketAddr), // addr
Connections(usize, usize) // user_count, connection_count
}
+4 -3
View File
@@ -1,3 +1,4 @@
use std::net::SocketAddr;
use serde::{Deserialize, Serialize};
use crate::model::{M3uPlaylistItem, PlaylistEntry, PlaylistItemType, XtreamCluster, XtreamPlaylistItem};
use crate::utils::{current_time_secs, longest};
@@ -55,7 +56,7 @@ pub struct StreamInfo {
pub username: String,
pub channel: StreamChannel,
pub provider: String,
pub addr: String,
pub addr: SocketAddr,
pub client_ip: String,
#[serde(default)]
pub user_agent: String,
@@ -66,12 +67,12 @@ pub struct StreamInfo {
}
impl StreamInfo {
pub fn new(username: &str, addr: &str, client_ip: &str, provider: &str, stream_channel: StreamChannel, user_agent: String, country: Option<String>) -> Self {
pub fn new(username: &str, addr: &SocketAddr, client_ip: &str, provider: &str, stream_channel: StreamChannel, user_agent: String, country: Option<String>) -> Self {
Self {
username: username.to_string(),
channel: stream_channel,
provider: provider.to_string(),
addr: addr.to_string(),
addr: addr.clone(),
client_ip: client_ip.to_string(),
user_agent,
ts: current_time_secs(),