2026-04-13 12:26:44 +02:00
|
|
|
# Session Handling Internals
|
|
|
|
|
|
|
|
|
|
This page is for maintainers working on Tuliprox session code.
|
|
|
|
|
|
|
|
|
|
If you only want the operator-facing behavior, read:
|
|
|
|
|
|
|
|
|
|
- [Sessions, HLS, Catchup and Reconnects](./connection-handling-sessions-and-reconnects.md)
|
2026-04-21 15:21:51 +02:00
|
|
|
- [Connection Handling Runtime Flow](./connection-handling-runtime-flow.md)
|
|
|
|
|
|
|
|
|
|
If you want the broader runtime orchestration around admission, placeholders, provider-open, and cleanup, also read:
|
|
|
|
|
|
|
|
|
|
- [Connection Handling Runtime Internals](./connection-handling-runtime-internals.md)
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
## Why this page exists
|
|
|
|
|
|
|
|
|
|
Tuliprox uses the word "session" for several related but different mechanisms:
|
|
|
|
|
|
|
|
|
|
- user admission reuse
|
|
|
|
|
- logical playback identity
|
|
|
|
|
- socket/address tracking
|
|
|
|
|
- provider/account affinity
|
|
|
|
|
- adaptive reconnect preservation
|
|
|
|
|
- provider-account affinity and reservation
|
|
|
|
|
|
|
|
|
|
These mechanisms interact, but they are not the same thing.
|
|
|
|
|
|
|
|
|
|
The most important rule for future changes is:
|
|
|
|
|
|
|
|
|
|
- do not derive session-based admission from socket-binding rules
|
|
|
|
|
- do not derive provider/account affinity from socket-binding rules either
|
|
|
|
|
|
|
|
|
|
That exact confusion breaks VOD, series, catchup, and local reopen/seek flows.
|
|
|
|
|
|
2026-04-21 15:21:51 +02:00
|
|
|
This page is deliberately narrower than the runtime-internals page:
|
|
|
|
|
|
|
|
|
|
- this page focuses on session identity, session admission, socket binding, and provider affinity
|
|
|
|
|
- the runtime-internals page focuses on the full admission/playback activity flow
|
|
|
|
|
|
2026-04-13 12:26:44 +02:00
|
|
|
## Core terms
|
|
|
|
|
|
|
|
|
|
### Session token
|
|
|
|
|
|
2026-05-13 09:01:26 +02:00
|
|
|
The session token is the playback identity used by admission, address tracking, and provider affinity.
|
|
|
|
|
|
|
|
|
|
It is stable for follow-up requests of the same playback, but it is not always derived from the same fields:
|
|
|
|
|
|
|
|
|
|
- plain TS live uses a socket-scoped playback token
|
|
|
|
|
- VOD, series, catchup, and local playback use a logical token suitable for reopen/seek/range workflows
|
2026-09-09 21:05:52 +02:00
|
|
|
- HLS playlist starts use a token scoped to that initial adaptive playback start, and rewritten segment URLs carry that token forward
|
|
|
|
|
- DASH playlist starts use an equally scoped token, but the redirected player fetches DASH segments directly from the provider
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
It is used to answer questions like:
|
|
|
|
|
|
|
|
|
|
- is this the same playback as before?
|
|
|
|
|
- may this request reopen an existing playback while the user is already at the hard limit?
|
|
|
|
|
- should Tuliprox keep using the same provider context if possible?
|
|
|
|
|
|
|
|
|
|
### Session admission
|
|
|
|
|
|
|
|
|
|
Session admission means user-limit checks are performed with `connection_admission_for_session(...)` instead of plain `connection_admission(...)`.
|
|
|
|
|
|
|
|
|
|
This happens in `resolve_admission_with_strategies(..., use_session_admission, session_token)`.
|
|
|
|
|
|
|
|
|
|
If `use_session_admission` is `true` and the session token already exists, the request may continue even when the user is
|
|
|
|
|
already at the nominal connection limit.
|
|
|
|
|
|
|
|
|
|
This is required for normal player behavior such as:
|
|
|
|
|
|
|
|
|
|
- VOD size checks
|
|
|
|
|
- VOD seeks
|
|
|
|
|
- pause/resume reopen
|
|
|
|
|
- HLS segment fetches
|
|
|
|
|
- local media reopen
|
|
|
|
|
|
|
|
|
|
### Socket-bound session
|
|
|
|
|
|
|
|
|
|
Socket binding only controls how Tuliprox tracks request addresses for the same session.
|
|
|
|
|
|
|
|
|
|
It does not decide:
|
|
|
|
|
|
|
|
|
|
- whether the request should use session admission
|
|
|
|
|
- whether the logical playback must stay on the same provider account
|
|
|
|
|
|
|
|
|
|
Current socket-binding policy:
|
|
|
|
|
|
|
|
|
|
- `PlaylistItemType::Live`
|
|
|
|
|
- `PlaylistItemType::LiveUnknown`
|
|
|
|
|
|
|
|
|
|
are socket-bound.
|
|
|
|
|
|
|
|
|
|
Everything else is not socket-bound:
|
|
|
|
|
|
|
|
|
|
- `LiveHls`
|
|
|
|
|
- `LiveDash`
|
|
|
|
|
- `Video`
|
|
|
|
|
- `Series`
|
|
|
|
|
- `Catchup`
|
|
|
|
|
- local playback types
|
|
|
|
|
|
|
|
|
|
### Provider/account affinity
|
|
|
|
|
|
|
|
|
|
Provider/account affinity answers a different question:
|
|
|
|
|
|
|
|
|
|
- when several requests belong to the same logical playback, must they keep using the same provider account?
|
|
|
|
|
|
|
|
|
|
The intended behavior is:
|
|
|
|
|
|
|
|
|
|
- TS-style live playback is socket-bound, but not provider-account-bound across requests
|
|
|
|
|
- VOD/movie/series playback is not socket-bound, but is provider/account-bound for the logical stream
|
|
|
|
|
- HLS/DASH playback is not socket-bound, but is provider/account-bound for the logical stream
|
|
|
|
|
|
|
|
|
|
Why:
|
|
|
|
|
|
|
|
|
|
- TS-style live playback has no seek/range workflow and each new request can be treated independently
|
|
|
|
|
- VOD uses range and seek requests that still belong to one playback
|
|
|
|
|
- HLS fetches many chunk requests that still belong to one playback
|
|
|
|
|
|
2026-05-13 09:01:26 +02:00
|
|
|
This means the same provider-affine HLS, VOD, series, or catchup stream must keep the same provider account for
|
|
|
|
|
its follow-up requests, even though those requests may arrive on different sockets.
|
2026-04-13 12:26:44 +02:00
|
|
|
|
2026-09-09 21:05:52 +02:00
|
|
|
### DASH delivery boundary (redirect only)
|
|
|
|
|
|
|
|
|
|
Tuliprox treats DASH as **redirect-only**:
|
|
|
|
|
|
|
|
|
|
- DASH requests (`PlaylistItemType::LiveDash` or requests with `.mpd` extension) always resolve to an HTTP redirect directly to
|
|
|
|
|
the upstream provider URL.
|
|
|
|
|
- DASH reverse-proxying and segment caching are **not supported**. Enabling reverse-proxy mode for a user or target does not route
|
|
|
|
|
DASH streams through the HLS proxy handler or cache segments in Tuliprox.
|
|
|
|
|
- DASH maintains provider-account affinity at the logical playback level (reusing session tokens and reserving the pinned
|
|
|
|
|
provider account across reopen/refresh within `hls_session_ttl_secs`).
|
|
|
|
|
- Because segments are fetched directly between the player and the upstream provider, Tuliprox cannot count individual segment
|
|
|
|
|
requests or confirm first-byte delivery for DASH segments. Full segment lease guarantees apply only to streams proxied
|
|
|
|
|
directly through Tuliprox (TS, HLS, VOD, Series, Catchup).
|
|
|
|
|
|
2026-04-13 12:26:44 +02:00
|
|
|
### Adaptive preserved session
|
|
|
|
|
|
|
|
|
|
Adaptive live playback (`LiveHls`, `LiveDash`) can stay logically alive for a short TTL even after the current socket disconnects.
|
|
|
|
|
|
|
|
|
|
That logic lives in `ActiveUserManager` and uses:
|
|
|
|
|
|
|
|
|
|
- `should_preserve_session_stream(...)`
|
|
|
|
|
- `build_preserved_stream_expiry(...)`
|
|
|
|
|
- `process_due_adaptive_expiry_entries(...)`
|
|
|
|
|
|
2026-09-09 10:28:42 +02:00
|
|
|
### Provider slot lease
|
2026-04-13 12:26:44 +02:00
|
|
|
|
2026-09-09 10:28:42 +02:00
|
|
|
A provider reservation is now a *slot lease* owned by a playback identity, not by a socket. It is a provider-slot
|
|
|
|
|
affinity mechanism, not a user admission mechanism. One owner holds at most one lease, so repeated requests of the same
|
|
|
|
|
playback reuse the slot instead of stacking a reservation per attempt.
|
2026-04-13 12:26:44 +02:00
|
|
|
|
2026-09-09 10:28:42 +02:00
|
|
|
A lease has three states:
|
|
|
|
|
|
|
|
|
|
- `Starting`: claimed when a provider allocation is acquired. It pins the provider for its own playback and blocks
|
|
|
|
|
nobody else. It expires after a short startup deadline regardless of the configured TTL, so an abandoned start or a
|
|
|
|
|
manifest-retry loop never accumulates reservations.
|
|
|
|
|
- `Active`: reached only when real media delivery is confirmed — the first provider media byte forwarded to a client as
|
|
|
|
|
an `OK`/`206` media response. Manifest, HEAD, key, map and error fetches never confirm a lease. Only an `Active` lease
|
|
|
|
|
with a configured reconnect window reserves capacity against other playbacks.
|
|
|
|
|
- `Idle`: a confirmed, reconnect-capable playback that ended cleanly keeps its slot for the reconnect window so it can
|
|
|
|
|
reattach to the same provider.
|
|
|
|
|
|
|
|
|
|
TTL mapping (the reconnect window):
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
- HLS/DASH use `hls_session_ttl_secs`
|
|
|
|
|
- Catchup uses `catchup_session_ttl_secs`
|
|
|
|
|
- other item types use `0`
|
|
|
|
|
|
2026-09-09 10:28:42 +02:00
|
|
|
Lease end is outcome-driven: provider failure, preemption, kick and timeout release capacity immediately; a clean finish
|
|
|
|
|
of a reconnect-capable playback keeps the idle window.
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
Important:
|
|
|
|
|
|
|
|
|
|
- VOD/movie/series still have strict provider affinity on follow-up requests
|
2026-09-09 10:28:42 +02:00
|
|
|
- they just do not currently get an extra post-request reconnect window like HLS/Catchup do
|
|
|
|
|
- An HTTP 200 manifest response is not playback and does not confirm a lease; neither do HEAD, key, map or error-video
|
|
|
|
|
responses. Only an `OK`/`206` media response confirms media delivery, which is what previously let entry
|
|
|
|
|
reservations block unrelated clients behind the same reverse proxy
|
|
|
|
|
|
|
|
|
|
### Multi-user socket isolation and allocation-based provider connections
|
|
|
|
|
|
|
|
|
|
When multiple users connect through the same reverse proxy socket:
|
|
|
|
|
|
|
|
|
|
- `ActiveUserManager` tracks user sessions per user key and stream UID. `SocketRegistration` records all registered
|
|
|
|
|
usernames for each `SocketAddr`, ensuring concurrent users on the same socket never overwrite or prematurely evict
|
|
|
|
|
each other's session state.
|
|
|
|
|
- `ActiveProviderManager` indexes single connections by unique `AllocationId` rather than grouping them under a single
|
|
|
|
|
socket address key (`ClientConnectionId`). Secondary socket indexes exist only for whole-transport close and kick
|
|
|
|
|
signals.
|
|
|
|
|
- Stream details and QoS fallbacks are updated directly by `stream_uid` (`update_stream_detail_by_uid`) rather than
|
|
|
|
|
relying on ambiguous socket lookups.
|
2026-04-13 12:26:44 +02:00
|
|
|
|
2026-09-10 21:41:05 +02:00
|
|
|
### Request ownership and binding generations
|
|
|
|
|
|
|
|
|
|
The runtime uses separate identities for separate ownership scopes:
|
|
|
|
|
|
|
|
|
|
| Identity | Scope |
|
|
|
|
|
| :--- | :--- |
|
|
|
|
|
| session token | one logical user playback |
|
|
|
|
|
| `PlaybackLeaseId` | one logical provider-capacity lease |
|
|
|
|
|
| `PlaybackRequestId` | one provider request attached to that lease |
|
|
|
|
|
| `ProviderBindingTag` | one generation of the owner's provider-account binding |
|
|
|
|
|
| `SharedSubscriberId` | one viewer attached to a shared MPEG-TS origin |
|
|
|
|
|
|
|
|
|
|
Parallel range, segment and reconnect requests may belong to the same logical stream while retaining independent request
|
|
|
|
|
claims. Completing one request removes only that claim. The logical stream and provider lease remain until their own
|
|
|
|
|
terminal conditions are met.
|
|
|
|
|
|
|
|
|
|
A provider binding increments its generation when the same playback moves to a different provider or reactivates an
|
|
|
|
|
idle lease. Cleanup created for an older generation cannot clear the successor binding. This protects new playback from
|
|
|
|
|
delayed body drops, provider errors and asynchronous HLS cleanup belonging to the preceding request.
|
|
|
|
|
|
|
|
|
|
### Cleanup ownership and overload behavior
|
|
|
|
|
|
|
|
|
|
Connection registration reserves a cleanup permit before publishing mutable user or provider state. Ownership then
|
|
|
|
|
moves into the response body or shared-subscriber body. Dropping an unpolled body, cancelling an await, reaching EOF or
|
|
|
|
|
reporting an error all converge on request-specific, idempotent cleanup.
|
|
|
|
|
|
|
|
|
|
The regular cleanup queue is bounded by `reverse_proxy.stream.cleanup_queue_capacity`. Admission waits for a bounded
|
|
|
|
|
interval when all permits are held and then rejects the request instead of registering a stream without guaranteed
|
|
|
|
|
cleanup. HLS origin I/O uses a separate bounded control lane that is preferred over ordinary body cleanup, preventing a
|
|
|
|
|
saturated subscriber workload from starving release of an origin provider handle.
|
|
|
|
|
|
|
|
|
|
Graceful shutdown first closes the admission gate, then removes shared origins and active user claims, releases provider
|
|
|
|
|
leases, flushes stream history and cancels and joins the owned background workers. Cleanup emitted by a later body drop
|
|
|
|
|
is harmless because the terminal indices have already been emptied.
|
|
|
|
|
|
|
|
|
|
### Shared MPEG-TS subscriber isolation
|
|
|
|
|
|
|
|
|
|
A shared origin is keyed by stream URL, but its viewers are keyed by `SharedSubscriberId`, never by `SocketAddr`. This
|
|
|
|
|
allows several users behind one reverse proxy connection to consume independent response bodies without replacing one
|
|
|
|
|
another.
|
|
|
|
|
|
|
|
|
|
Each subscriber has both a chunk limit and a byte semaphore for queued payload. Burst replay and live delivery use a
|
|
|
|
|
progress deadline that is renewed only after a successful send. Timeout, cancellation and a closed receiver are distinct
|
|
|
|
|
terminal outcomes; an incomplete replay never advances directly to the live tail. A pending-join guard also prevents
|
|
|
|
|
the last existing subscriber from tearing down an origin while another subscriber is committing.
|
|
|
|
|
|
2026-04-13 12:26:44 +02:00
|
|
|
## Where session tokens come from
|
|
|
|
|
|
|
|
|
|
### TS live, VOD, series
|
|
|
|
|
|
2026-05-13 09:01:26 +02:00
|
|
|
The regular M3U and Xtream playback endpoints build playback tokens with:
|
|
|
|
|
|
|
|
|
|
- `create_playback_session_fingerprint(...)`
|
|
|
|
|
|
|
|
|
|
The helper deliberately keeps the token matrix different per playback type:
|
2026-04-13 12:26:44 +02:00
|
|
|
|
2026-05-13 09:01:26 +02:00
|
|
|
- plain TS live is socket-scoped, so two TS sockets are two independent playback attempts
|
|
|
|
|
- VOD and series are logical, so range/seek/reopen requests can continue the same playback
|
|
|
|
|
- HLS/DASH playlist starts are scoped to the initial adaptive playback start, so two players behind the same
|
|
|
|
|
IP/user-agent can watch the same HLS/DASH channel independently
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
### Catchup
|
|
|
|
|
|
|
|
|
|
Catchup uses a different token namespace:
|
|
|
|
|
|
|
|
|
|
- `create_catchup_session_key(fingerprint, username, virtual_id)`
|
|
|
|
|
|
|
|
|
|
### HLS segment requests
|
|
|
|
|
|
|
|
|
|
Rewritten HLS URLs can carry the session token inside the encrypted segment token.
|
|
|
|
|
|
|
|
|
|
On segment fetch:
|
|
|
|
|
|
|
|
|
|
- Tuliprox decodes the HLS token
|
|
|
|
|
- extracts the session token if present
|
2026-05-13 09:01:26 +02:00
|
|
|
- falls back to a logical `create_session_fingerprint(...)` only for legacy or malformed tokens without an embedded session token
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
### Local playback
|
|
|
|
|
|
2026-05-13 09:01:26 +02:00
|
|
|
Local playback uses a stable logical `playback_session_token` passed into `local_stream_response(...)`.
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
## The three decisions that must stay separate
|
|
|
|
|
|
|
|
|
|
### 1. Should this request use session admission?
|
|
|
|
|
|
|
|
|
|
For playback endpoints, the answer is yes.
|
|
|
|
|
|
|
|
|
|
That means these paths should call `resolve_admission_with_strategies(..., true, Some(session_token))`:
|
|
|
|
|
|
|
|
|
|
- M3U playback
|
|
|
|
|
- Xtream playback
|
|
|
|
|
- HLS session/segment handling
|
|
|
|
|
- local playback reopen logic
|
|
|
|
|
|
|
|
|
|
Reason:
|
|
|
|
|
|
|
|
|
|
- a second request for the same logical playback must be admitted as the same playback
|
|
|
|
|
- not as a brand-new connection
|
|
|
|
|
|
2026-04-21 15:21:51 +02:00
|
|
|
Important:
|
|
|
|
|
|
|
|
|
|
- this is separate from background metadata/probe work
|
|
|
|
|
- probe/resolve tasks do not use normal playback session admission
|
|
|
|
|
- probe-capable background work instead uses provider-side probe handles and provider preemption rules
|
|
|
|
|
|
2026-04-13 12:26:44 +02:00
|
|
|
### 2. Should this session be bound to exactly one current socket?
|
|
|
|
|
|
|
|
|
|
This is controlled by `PlaylistItemType::uses_socket_bound_session()`.
|
|
|
|
|
|
|
|
|
|
Reason:
|
|
|
|
|
|
|
|
|
|
- plain TS-style live playback behaves like one active transport socket
|
|
|
|
|
- VOD, HLS, DASH, catchup, and local playback often use multiple short-lived sockets for one logical playback
|
|
|
|
|
|
|
|
|
|
Do not replace decision 1 with decision 2.
|
|
|
|
|
|
|
|
|
|
If you do that:
|
|
|
|
|
|
|
|
|
|
- existing VOD sessions stop reopening correctly at `max_connections: 1`
|
|
|
|
|
- seek/size-check flows start failing with `UserConnectionsExhausted`
|
|
|
|
|
|
|
|
|
|
### 3. Should this logical playback stay on the same provider account?
|
|
|
|
|
|
|
|
|
|
This is separate from both admission and socket tracking.
|
|
|
|
|
|
|
|
|
|
This policy is controlled by `PlaylistItemType::requires_provider_affinity()`.
|
|
|
|
|
|
|
|
|
|
Intended model:
|
|
|
|
|
|
|
|
|
|
- TS live: no provider/account pinning across requests
|
|
|
|
|
- VOD/movie/series: provider/account pinned across the logical stream
|
|
|
|
|
- HLS/DASH: provider/account pinned across the logical stream
|
|
|
|
|
- Catchup: provider/account pinned across the logical stream
|
|
|
|
|
|
|
|
|
|
If you collapse this into socket-binding, you get the wrong matrix:
|
|
|
|
|
|
|
|
|
|
- TS would be over-pinned
|
2026-05-13 09:01:26 +02:00
|
|
|
- VOD/series/HLS would be under-pinned
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
## Current code flow
|
|
|
|
|
|
|
|
|
|
### 1. Admission
|
|
|
|
|
|
|
|
|
|
Main entry point:
|
|
|
|
|
|
|
|
|
|
- `api_utils::resolve_admission_with_strategies(...)`
|
|
|
|
|
|
|
|
|
|
If `use_session_admission` is `true`, Tuliprox checks:
|
|
|
|
|
|
|
|
|
|
- `AppState::get_connection_admission_for_session(...)`
|
|
|
|
|
- `ActiveUserManager::connection_admission_for_session(...)`
|
|
|
|
|
|
|
|
|
|
If `false`, it falls back to:
|
|
|
|
|
|
|
|
|
|
- `AppState::get_connection_admission(...)`
|
|
|
|
|
- `ActiveUserManager::connection_admission(...)`
|
|
|
|
|
|
|
|
|
|
### 2. Session creation or refresh
|
|
|
|
|
|
|
|
|
|
Main entry point:
|
|
|
|
|
|
|
|
|
|
- `ActiveUserManager::create_user_session(...)`
|
|
|
|
|
|
|
|
|
|
This stores:
|
|
|
|
|
|
|
|
|
|
- session token
|
|
|
|
|
- virtual id
|
|
|
|
|
- provider
|
|
|
|
|
- stream url
|
|
|
|
|
- current address
|
|
|
|
|
- `socket_bound`
|
|
|
|
|
- `active_addrs`
|
|
|
|
|
- connection permission and kind
|
|
|
|
|
|
|
|
|
|
### 3. Stream registration
|
|
|
|
|
|
|
|
|
|
Main entry point:
|
|
|
|
|
|
|
|
|
|
- `ActiveUserManager::update_connection(...)`
|
|
|
|
|
|
2026-09-10 21:41:05 +02:00
|
|
|
This creates or reuses the tracked logical stream entry and attaches a request claim.
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
Important behavior:
|
|
|
|
|
|
|
|
|
|
- same `session_token` reuses the logical stream
|
2026-09-10 21:41:05 +02:00
|
|
|
- every response receives its own request UID even when it reuses that stream
|
|
|
|
|
- ending one response detaches only its request claim; the stream is removed when its final claim ends
|
2026-04-13 12:26:44 +02:00
|
|
|
- stream metrics and duration stay tied to the logical session
|
|
|
|
|
- for non-socket-bound sessions, the session remembers multiple active addresses
|
|
|
|
|
|
|
|
|
|
### 4. Lightweight HTTP activity
|
|
|
|
|
|
|
|
|
|
Main entry point:
|
|
|
|
|
|
|
|
|
|
- `ActiveUserManager::touch_http_activity(...)`
|
|
|
|
|
|
|
|
|
|
This is used for session continuity without creating a new logical stream, especially for HLS follow-up requests.
|
|
|
|
|
|
|
|
|
|
### 5. Seek / forced provider reopen
|
|
|
|
|
|
|
|
|
|
Main entry point:
|
|
|
|
|
|
|
|
|
|
- `api_utils::force_provider_stream_response(...)`
|
|
|
|
|
|
|
|
|
|
This path:
|
|
|
|
|
|
|
|
|
|
- releases the current provider-side connection for the session
|
|
|
|
|
- reacquires a provider connection
|
|
|
|
|
- moves the session to the request address with `update_session_addr(...)`
|
|
|
|
|
|
|
|
|
|
For non-socket-bound sessions, `update_session_addr(...)` does not forget the older active session addresses.
|
|
|
|
|
|
|
|
|
|
That is what allows a VOD session to survive:
|
|
|
|
|
|
|
|
|
|
- overlapping size-check requests
|
|
|
|
|
- seek requests
|
|
|
|
|
- overlapping teardown/start windows
|
|
|
|
|
|
|
|
|
|
The address move itself is not the provider-affinity rule.
|
|
|
|
|
|
2026-05-13 09:01:26 +02:00
|
|
|
Provider-affinity is the separate invariant that follow-up requests for the same VOD/series/HLS playback should
|
|
|
|
|
continue on the same provider account.
|
2026-04-13 12:26:44 +02:00
|
|
|
|
2026-04-21 15:21:51 +02:00
|
|
|
This also explains why seek/reopen behavior must not be confused with:
|
|
|
|
|
|
|
|
|
|
- ordinary new activation
|
|
|
|
|
- socket-bound TS admission
|
|
|
|
|
- background probe/resolve tasks
|
|
|
|
|
|
2026-04-13 12:26:44 +02:00
|
|
|
### 6. Disconnect handling
|
|
|
|
|
|
|
|
|
|
Main entry points:
|
|
|
|
|
|
|
|
|
|
- `ActiveUserManager::release_stream(...)`
|
|
|
|
|
- `ActiveUserManager::release_connection(...)`
|
|
|
|
|
|
|
|
|
|
For socket-bound sessions, closing the current address removes the old address from the session immediately.
|
|
|
|
|
|
|
|
|
|
For non-socket-bound sessions:
|
|
|
|
|
|
|
|
|
|
- the closed address is removed from `active_addrs`
|
|
|
|
|
- if another active address still belongs to that session, the session and logical stream fall back to that address
|
|
|
|
|
|
|
|
|
|
This is the key behavior that keeps VOD and local playback stable across multiple sockets.
|
|
|
|
|
|
2026-09-10 21:41:05 +02:00
|
|
|
Queued provider cleanup carries the `PlaybackRequestId` and owner captured during acquire, while provider handles retain
|
|
|
|
|
their `ProviderBindingTag`. The lease table validates both against the current binding, so cleanup does not need to
|
|
|
|
|
rediscover ownership from a possibly reused socket address.
|
|
|
|
|
|
2026-04-13 12:26:44 +02:00
|
|
|
## URL persistence rules
|
|
|
|
|
|
|
|
|
|
Session URL handling is intentionally different by stream type.
|
|
|
|
|
|
|
|
|
|
For:
|
|
|
|
|
|
|
|
|
|
- `Video`
|
|
|
|
|
- `Series`
|
|
|
|
|
- `Catchup`
|
|
|
|
|
- local VOD/series playback
|
|
|
|
|
|
|
|
|
|
Tuliprox stores the canonical request URL in the session, not an ephemeral redirect target.
|
|
|
|
|
|
|
|
|
|
Reason:
|
|
|
|
|
|
|
|
|
|
- provider-side redirects can resolve a request to a non-canonical final URL
|
|
|
|
|
- reusing that final URL later can change or break VOD seek/reopen behavior
|
|
|
|
|
|
|
|
|
|
For live playback, following the redirected provider URL is still acceptable and often desirable.
|
|
|
|
|
|
|
|
|
|
## File map for future changes
|
|
|
|
|
|
|
|
|
|
Read these files together before changing session logic:
|
|
|
|
|
|
2026-09-10 21:41:05 +02:00
|
|
|
- `backend/core/src/model/playback.rs`
|
|
|
|
|
- `backend/session/src/provider_leases.rs`
|
|
|
|
|
- `backend/session/src/active_user_manager/mod.rs`
|
|
|
|
|
- `backend/session/src/active_provider_manager.rs`
|
|
|
|
|
- `backend/session/src/connection_manager.rs`
|
|
|
|
|
- `backend/session/src/streams/shared_stream_manager.rs`
|
|
|
|
|
- `backend/app/src/api/api_utils/mod.rs`
|
|
|
|
|
- `backend/app/src/api/model/streams/active_client_stream.rs`
|
|
|
|
|
- `backend/app/src/api/endpoints/hls_api/session.rs`
|
|
|
|
|
- `backend/hls/src/origin.rs`
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
## Tests that should stay green
|
|
|
|
|
|
|
|
|
|
These tests cover the most fragile parts of the current logic:
|
|
|
|
|
|
|
|
|
|
- `resolve_admission_with_strategies_allows_existing_session_even_when_user_is_at_limit`
|
|
|
|
|
- `local_stream_response_reuses_stable_playback_session_token_across_reopens`
|
|
|
|
|
- `vod_session_survives_overlapping_and_seek_sockets`
|
2026-05-13 09:01:26 +02:00
|
|
|
- `playback_session_fingerprint_keeps_ts_socket_bound_but_vod_logical`
|
|
|
|
|
- `adaptive_playback_session_fingerprint_is_unique_per_initial_socket`
|
|
|
|
|
- `unlimited_user_can_open_same_and_different_live_streams_from_same_ip`
|
|
|
|
|
- `recently_evicted_vod_uses_session_reentry_guard`
|
2026-04-13 12:26:44 +02:00
|
|
|
- `update_session_addr_prunes_previous_registration_for_socket_bound_session`
|
|
|
|
|
- `test_adaptive_session_release_connection_preserves_logical_stream_and_start_time`
|
2026-09-10 21:41:05 +02:00
|
|
|
- `stale_direct_cleanup_after_rebind_preserves_successor_request`
|
|
|
|
|
- `subscribers_on_same_socket_do_not_replace_each_other`
|
|
|
|
|
- `send_client_chunk_respects_byte_budget`
|
|
|
|
|
- `control_cleanup_lane_precedes_a_ready_body_cleanup_backlog`
|
|
|
|
|
- `shutdown_with_all_cleanup_permits_held_completes`
|
|
|
|
|
- `http_provider_lease_downstream_smoke_test`
|
2026-04-13 12:26:44 +02:00
|
|
|
|
|
|
|
|
If you change session code and one of these assumptions no longer holds, update the documentation and the tests in the same change.
|
2026-04-21 15:21:51 +02:00
|
|
|
|
|
|
|
|
## Related pages
|
|
|
|
|
|
|
|
|
|
- [Connection Handling Runtime Flow](./connection-handling-runtime-flow.md)
|
|
|
|
|
- [Connection Handling Runtime Internals](./connection-handling-runtime-internals.md)
|
|
|
|
|
- [Sessions, HLS, Catchup and Reconnects](./connection-handling-sessions-and-reconnects.md)
|
|
|
|
|
- [Failures and User-Visible Behavior](./connection-handling-failures-and-user-visible-behavior.md)
|