mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-18 13:52:20 +02:00
First step provider and user connections.
-> Missing shared streams connections
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,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);
|
||||
|
||||
@@ -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,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
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user