fix: allow same-session HLS soft-preserve reactivation (#826)

* fix: allow same-session HLS soft-preserve reactivation

Since #807, LiveHls segment gaps soft-preserve the stream then Activate returned Exhausted and kick-evicted the same session, causing disconnects/retry storms. Restore 3.3.78 entitlement via session_has_stream (includes preserved) and drop unused session_has_active_stream.
This commit is contained in:
MaxPain99
2026-08-21 20:05:05 +02:00
committed by GitHub
parent a37468aab9
commit 95aeee4618
+14 -25
View File
@@ -1798,12 +1798,6 @@ impl ActiveUserManager {
connection_data.streams.iter().any(|stream| stream.session_token.as_deref() == Some(session_token))
}
fn session_has_active_stream(connection_data: &UserConnectionData, session_token: &str) -> bool {
connection_data
.streams
.iter()
.any(|stream| !stream.preserved && stream.session_token.as_deref() == Some(session_token))
}
fn clear_session_counted_without_stream(connection_data: &mut UserConnectionData, session_token: &str) {
if Self::session_has_stream(connection_data, session_token) {
@@ -1889,9 +1883,12 @@ impl ActiveUserManager {
return self.check_connection_admission(username, connection_data);
};
// Existing counted session or session with an active stream: entitled to its slot.
// Existing counted session or any stream row for this token (including soft-preserved
// HLS/Catchup between segments): entitled to its slot. #807 switched this to
// active-only stream checks, so every LiveHls segment gap returned Exhausted
// and kick-evicted/terminated the same session (retry storm since v3.3.79).
if connection_data.sessions[session_index].lifecycle.is_counted()
|| Self::session_has_active_stream(connection_data, session_token)
|| Self::session_has_stream(connection_data, session_token)
{
return ConnectionAdmission {
permission: UserConnectionPermission::Allowed,
@@ -1899,22 +1896,14 @@ impl ActiveUserManager {
};
}
// Existing uncounted session without a stream: check for a preserved stream.
// Preserved streams hold no counted slot — `effective_counts_for_admission`
// virtually counts them so strategies still treat them as evictable. Return
// Exhausted (without committing a new slot) so the eviction path clears the
// preserved row before reactivation.
let has_preserved =
connection_data.streams.iter().any(|s| s.session_token.as_deref() == Some(session_token) && s.preserved);
// Uncounted session with no stream row: run normal admission.
// Same-token soft-preserve is handled above via `session_has_stream` (3.3.78 semantics).
// Do not return Exhausted for own preserved rows — that forced self-eviction on HLS gaps.
let admission = self.check_connection_admission_with_counts(
username,
connection_data,
connection_data.effective_counts_for_admission(Some(session_token)),
);
if has_preserved {
return ConnectionAdmission { permission: UserConnectionPermission::Exhausted, kind: admission.kind };
}
if admission.permission == UserConnectionPermission::Allowed {
let session = &mut connection_data.sessions[session_index];
Self::update_session_admission(session, admission.permission, admission.kind);
@@ -8261,7 +8250,7 @@ mod tests {
)
.await;
assert_eq!(admission.permission, UserConnectionPermission::Exhausted);
assert_eq!(admission.permission, UserConnectionPermission::Allowed);
assert_eq!(admission.kind, Some(ConnectionKind::Normal));
let connections = manager.connections.read().await;
let connection_data = connections.by_key.get(&user.username).expect("user connection data");
@@ -8298,7 +8287,7 @@ mod tests {
session_token,
)
.await;
assert_eq!(admission.permission, UserConnectionPermission::Exhausted);
assert_eq!(admission.permission, UserConnectionPermission::Allowed);
{
let connections = manager.connections.read().await;
let connection_data = connections.by_key.get(&user.username).expect("user connection data");
@@ -8353,7 +8342,7 @@ mod tests {
session_token,
)
.await;
assert_eq!(admission.permission, UserConnectionPermission::Exhausted);
assert_eq!(admission.permission, UserConnectionPermission::Allowed);
{
let connections = manager.connections.read().await;
let connection_data = connections.by_key.get(&user.username).expect("user connection data");
@@ -8412,7 +8401,7 @@ mod tests {
session_token,
)
.await;
assert_eq!(admission.permission, UserConnectionPermission::Exhausted);
assert_eq!(admission.permission, UserConnectionPermission::Allowed);
let counter_changed = manager
.release_session_streams_and_counted_reservation(&user.username, session_token)
@@ -8458,7 +8447,7 @@ mod tests {
session_token,
)
.await;
assert_eq!(admission.permission, UserConnectionPermission::Exhausted);
assert_eq!(admission.permission, UserConnectionPermission::Allowed);
assert_eq!(manager.active_users_and_connections().await, (0, 0));
let connections = manager.connections.read().await;
@@ -8532,7 +8521,7 @@ mod tests {
session_token,
)
.await;
assert_eq!(admission.permission, UserConnectionPermission::Exhausted);
assert_eq!(admission.permission, UserConnectionPermission::Allowed);
assert_eq!(admission.kind, Some(ConnectionKind::Soft));
{