A crash fix for a failed live source, State Threads moved to its own
source of truth with new feature tests, and the next round of
testability refactors.
## Bug fix
- **Live: Drop a live source whose initialize failed. v8.0.52**
`SrsLiveSourceManager::fetch_or_create()` adds a new source to the pool
before `initialize()`, and left it there when `initialize()` failed. The
next client for that stream got the half-built source as if it were
ready. With an unknown `dvr_plan`, which `check_config` accepts,
`SrsDvr::initialize()` fails with `plan_` NULL but `actived_` set. The
first RTMP publish was refused with 3083, and the second crashed SRS in
`SrsDvr::on_publish()`. Now a failed `initialize()` removes the source
from the pool, if the pool still holds that same source, so the next
client creates a new one. With `SRS_VHOST_DVR_DVR_PLAN=foo`, both
publishes are now refused with 3083 and the server stays up.
## State Threads
- `state-threads/` (a symlink to `../state-threads`) is now the source
of truth for ST. Changes start there, then sync into
`trunk/3rdparty/st-srs/`.
- `scripts/copy-from-st.sh` copies `state-threads/` into the mirror, and
`scripts/copy-to-st.sh` copies the other way. Both skip ST-only files
(`.github`, `.run`, `ide`, `gtest-fit`) and remove files that no longer
exist in the source.
- `trunk/3rdparty/st-srs/` is synced from State Threads 86c0e69
(v1.9.0):
- Feature tests for keys, TCP and UDP I/O, the scheduler, sync, epoll,
select and stacks.
- Fixes two missed switch callbacks and the restore of a randomized
stack's guard page.
- Retries blocking I/O on `EINTR`.
- Marks allocation failures and debugger code as excluded from coverage.
- Moves the change list to `CHANGELOG.md` and drops the old CentOS
Dockerfiles.
## Testability refactors (no behavior change)
- `SrsServer`: creates its RTC UDP listeners and SRT acceptors through
the app factory. Its HTTP server, ingester, signal manager, RTC session
manager, pid-file locker and SRT event loop are now typed as interfaces.
The libsrt log setup moves into `SrsSrtEventLoop`.
- Clocks and random generators are injected or captured in the live,
RTC, SRT and RTSP sources, the statistic stream, client and vhost, the
RTMP handshake blocks (`SrsKeyBlock`, `SrsDigestBlock`, `SrsC2S2`) and
`SrsRtcPlayerNegotiator`'s SSRC generator.
- Config reads go through a captured `ISrsAppConfig` in `SrsStatistic`,
`SrsStatisticVhost`, `SrsCoWorkers`, the summaries,
`SrsDtlsCertificate`, `SrsRtcBlackhole`, `SrsRtcPublishStream` and
`SrsRtcFrameBuilder`. `SrsRtcRecvTrack` reads its circuit breaker
through a captured `ISrsCircuitBreaker`.
- `assemble()` now does the construction work for the live, RTC, SRT and
RTSP source managers' timers, `SrsResourceManager`'s condition variable,
`SrsSharedTimer`'s fast timers, the SRT and HTTP receive threads,
`SrsUdpMuxListener`, `SrsSignalManager`, `SrsExecutorCoroutine`,
`SrsDynamicHttpConn`, `SrsGbMediaTcpConn`, `SrsGbSession`,
`SrsClockWallMonitor`, `SrsSrtFrameBuilder`, `SrsSrtConnection`,
`SrsSrtListener`, `SrsOriginHub`'s DVR and `SrsRtcRtpBuilder`'s audio
transcoder.
- `SrsHttpHeartbeat` reaches the statistic, and `SrsServer` reaches the
heartbeat, through interfaces. `SrsGbListener` takes the API server
owner in its constructor.
## AI skills
- A State Threads code map and docs route, a State Threads workflow in
`develop-code.md`, and ST version and changelog rules.
- SRS issue Truth Records condensed to one short format.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Reimplements the fix proposed in #4736, designed from its problem
statement rather than its diff.
When an `on_play` or `on_publish` HTTP callback rejects a client, SRS
blocked it but never reported why — and on RTMP it reported the opposite
first. Every hook rejection arrives as the same `ERROR_RESPONSE_CODE`,
so each protocol now translates that one error into whatever status its
transport can carry.
- **HTTP-FLV and HLS**: add a single root error handler,
`srs_http_stream_serve_error()`, which maps `ERROR_SYSTEM_AUTH` onto
`401` and leaves every other error closing the connection as SRS has
always done. A failing branch only transforms the error code; the root
of the handler decides the status, so no branch responds on its own. It
sends the error code and its short name only — never `srs_error_desc`,
which carries the callback URL and the backend's response. This required
exposing `header_wrote()` on `ISrsHttpResponseWriter`, so the handler
never rewrites a response already on the wire.
- **WHEP**: reuse the status mapping WHIP already had. The play path
used `srs_error_wrap()` where publish used
`srs_error_transform(ERROR_SYSTEM_AUTH, ...)`, which is the entire
reason a rejected viewer got `500` instead of `401`. The deprecated JSON
play and publish APIs are annotated, since they answer HTTP 200 with a
fixed `{"code":400}` for every failure and must not be extended.
- **RTSP**: map the same transformed error onto `401 Unauthorized` in
`DESCRIBE`.
- **RTMP**: no status to fix, only an ordering one. `start_play()` and
`start_fmle_publish()` sent `onStatus(NetStream.Play.Start /
Publish.Start)` before the hook was consulted, so a rejected client was
told it was live and then silently disconnected. Authorization now runs
first on both sides, with the publish check extracted into
`SrsRtmpConn::authorize_publish()`.
- **SRT**: unchanged. MPEG-TS over SRT has no application-level status
channel, so refusal without a reason is the final, correct behavior, and
a test locks that limitation in.
- **Testability**: `SrsHlsStream` and `SrsHlsVirtualConn` were not
mockable, so they were converted to the codebase's standard shape —
globals captured in the constructor, `security_` typed as the interface,
and the timer subscription moved out of the constructor into
`assemble()`, forwarded by `SrsVodStream` and called at every
construction site. Behavior-preserving.
- **Skill**: `references/testable-code.md` becomes the single owner of
the test-first contract and the C++ mockability patterns this change
followed, plus a rule to reimplement a rejected reference PR from its
problem statement rather than its diff.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>