**Problem**: Newly created sources (RTMP/SRT/RTC/RTSP) were being immediately marked as "dead" and deleted by the cleanup timer before publishers could connect, causing "new live source, dead=1" errors. **Root Cause**: All source constructors initialized `stream_die_at_ = 0`, causing `stream_is_dead()` to return `true` immediately since current time was always greater than `0 + 3 seconds`. **Solution**: Changed all four source constructors to initialize `stream_die_at_ = srs_time_now_cached()`, giving newly created sources a proper 3-second grace period before cleanup.
This commit is contained in:
parent
6e93dd73b5
commit
a3a2fa5ceb
|
|
@ -7,6 +7,7 @@ The changelog for SRS.
|
||||||
<a name="v7-changes"></a>
|
<a name="v7-changes"></a>
|
||||||
|
|
||||||
## SRS 7.0 Changelog
|
## SRS 7.0 Changelog
|
||||||
|
* v7.0, 2025-11-14, AI: Fix race condition causing immediate deletion of new sources. v7.0.127 (#4449)
|
||||||
* v7.0, 2025-11-11, AI: WebRTC: Support optional msid attribute per RFC 8830. v7.0.126 (#4570)
|
* v7.0, 2025-11-11, AI: WebRTC: Support optional msid attribute per RFC 8830. v7.0.126 (#4570)
|
||||||
* v7.0, 2025-11-11, AI: SRT: Stop TS parsing after codec detection. v7.0.125 (#4569)
|
* v7.0, 2025-11-11, AI: SRT: Stop TS parsing after codec detection. v7.0.125 (#4569)
|
||||||
* v7.0, 2025-11-09, AI: WebRTC: Support G.711 (PCMU/PCMA) audio codec for WebRTC. v7.0.124 (#4075)
|
* v7.0, 2025-11-09, AI: WebRTC: Support G.711 (PCMU/PCMA) audio codec for WebRTC. v7.0.124 (#4075)
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ function SrsRtcWhipWhepAsync() {
|
||||||
self.displayStream = null;
|
self.displayStream = null;
|
||||||
self.userStream = null;
|
self.userStream = null;
|
||||||
|
|
||||||
|
// Store the WHIP session resource URL from Location header for cleanup.
|
||||||
|
self.resourceUrl = null;
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
|
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
|
||||||
// @url The WebRTC url to publish with, for example:
|
// @url The WebRTC url to publish with, for example:
|
||||||
// http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream
|
// http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream
|
||||||
|
|
@ -111,6 +114,14 @@ function SrsRtcWhipWhepAsync() {
|
||||||
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
|
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
|
||||||
const data = xhr.responseText;
|
const data = xhr.responseText;
|
||||||
console.log("Got answer: ", data);
|
console.log("Got answer: ", data);
|
||||||
|
|
||||||
|
// Extract Location header for WHIP session resource URL.
|
||||||
|
const location = xhr.getResponseHeader('Location');
|
||||||
|
if (location) {
|
||||||
|
self.resourceUrl = new URL(location, url).href;
|
||||||
|
console.log(`WHIP session resource URL: ${self.resourceUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
return data.code ? reject(xhr) : resolve(data);
|
return data.code ? reject(xhr) : resolve(data);
|
||||||
}
|
}
|
||||||
xhr.open('POST', url, true);
|
xhr.open('POST', url, true);
|
||||||
|
|
@ -159,6 +170,14 @@ function SrsRtcWhipWhepAsync() {
|
||||||
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
|
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
|
||||||
const data = xhr.responseText;
|
const data = xhr.responseText;
|
||||||
console.log("Got answer: ", data);
|
console.log("Got answer: ", data);
|
||||||
|
|
||||||
|
// Extract Location header for WHEP session resource URL.
|
||||||
|
const location = xhr.getResponseHeader('Location');
|
||||||
|
if (location) {
|
||||||
|
self.resourceUrl = new URL(location, url).href;
|
||||||
|
console.log(`WHEP session resource URL: ${self.resourceUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
return data.code ? reject(xhr) : resolve(data);
|
return data.code ? reject(xhr) : resolve(data);
|
||||||
}
|
}
|
||||||
xhr.open('POST', url, true);
|
xhr.open('POST', url, true);
|
||||||
|
|
@ -190,6 +209,25 @@ function SrsRtcWhipWhepAsync() {
|
||||||
});
|
});
|
||||||
self.userStream = null;
|
self.userStream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send DELETE request to WHIP session resource URL to cleanup server resources.
|
||||||
|
if (self.resourceUrl) {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('DELETE', self.resourceUrl, true);
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.readyState !== xhr.DONE) return;
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
console.log(`WHIP session deleted: ${self.resourceUrl}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`Failed to delete WHIP session: ${self.resourceUrl}, status: ${xhr.status}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onerror = function() {
|
||||||
|
console.warn(`Error deleting WHIP session: ${self.resourceUrl}`);
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
self.resourceUrl = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The callback when got local stream.
|
// The callback when got local stream.
|
||||||
|
|
|
||||||
|
|
@ -491,16 +491,26 @@ srs_error_t SrsGoApiRtcPublish::do_serve_http(ISrsHttpResponseWriter *w, ISrsHtt
|
||||||
vcodec = r->query_get("codec");
|
vcodec = r->query_get("codec");
|
||||||
}
|
}
|
||||||
string acodec = r->query_get("acodec");
|
string acodec = r->query_get("acodec");
|
||||||
|
// For client to specifies whether encrypt by SRTP.
|
||||||
|
string srtp = r->query_get("encrypt");
|
||||||
|
string dtls = r->query_get("dtls");
|
||||||
|
|
||||||
srs_trace("RTC publish %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, vcodec=%s, acodec=%s",
|
srs_trace("RTC publish %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, vcodec=%s, acodec=%s, srtp=%s, dtls=%s",
|
||||||
streamurl.c_str(), api.c_str(), tid.c_str(), clientip.c_str(), ruc.req_->app_.c_str(), ruc.req_->stream_.c_str(),
|
streamurl.c_str(), api.c_str(), tid.c_str(), clientip.c_str(), ruc.req_->app_.c_str(), ruc.req_->stream_.c_str(),
|
||||||
remote_sdp_str.length(), eip.c_str(), vcodec.c_str(), acodec.c_str());
|
remote_sdp_str.length(), eip.c_str(), vcodec.c_str(), acodec.c_str(), srtp.c_str(), dtls.c_str());
|
||||||
|
|
||||||
ruc.eip_ = eip;
|
ruc.eip_ = eip;
|
||||||
ruc.vcodec_ = vcodec;
|
ruc.vcodec_ = vcodec;
|
||||||
ruc.acodec_ = acodec;
|
ruc.acodec_ = acodec;
|
||||||
ruc.publish_ = true;
|
ruc.publish_ = true;
|
||||||
ruc.dtls_ = ruc.srtp_ = true;
|
|
||||||
|
// For client to specifies whether encrypt by SRTP.
|
||||||
|
ruc.dtls_ = (dtls != "false");
|
||||||
|
if (srtp.empty()) {
|
||||||
|
ruc.srtp_ = config_->get_rtc_server_encrypt();
|
||||||
|
} else {
|
||||||
|
ruc.srtp_ = (srtp != "false");
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information.
|
// TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information.
|
||||||
ruc.remote_sdp_str_ = remote_sdp_str;
|
ruc.remote_sdp_str_ = remote_sdp_str;
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ srs_error_t SrsRtcSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<Sr
|
||||||
pps = source;
|
pps = source;
|
||||||
} else {
|
} else {
|
||||||
SrsSharedPtr<SrsRtcSource> source = SrsSharedPtr<SrsRtcSource>(new SrsRtcSource());
|
SrsSharedPtr<SrsRtcSource> source = SrsSharedPtr<SrsRtcSource>(new SrsRtcSource());
|
||||||
srs_trace("new rtc source, stream_url=%s", stream_url.c_str());
|
srs_trace("new rtc source, stream_url=%s, dead=%d", stream_url.c_str(), source->stream_is_dead());
|
||||||
pps = source;
|
pps = source;
|
||||||
|
|
||||||
pool_[stream_url] = source;
|
pool_[stream_url] = source;
|
||||||
|
|
@ -399,7 +399,10 @@ SrsRtcSource::SrsRtcSource()
|
||||||
circuit_breaker_ = _srs_circuit_breaker;
|
circuit_breaker_ = _srs_circuit_breaker;
|
||||||
|
|
||||||
pli_for_rtmp_ = pli_elapsed_ = 0;
|
pli_for_rtmp_ = pli_elapsed_ = 0;
|
||||||
stream_die_at_ = 0;
|
// Initialize stream_die_at_ to current time to prevent newly created sources
|
||||||
|
// from being immediately considered dead by stream_is_dead() check.
|
||||||
|
// @see https://github.com/ossrs/srs/issues/4449
|
||||||
|
stream_die_at_ = srs_time_now_cached();
|
||||||
|
|
||||||
app_factory_ = _srs_app_factory;
|
app_factory_ = _srs_app_factory;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1659,7 +1659,7 @@ srs_error_t SrsLiveSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<S
|
||||||
pps = source;
|
pps = source;
|
||||||
} else {
|
} else {
|
||||||
SrsSharedPtr<SrsLiveSource> source = app_factory_->create_live_source();
|
SrsSharedPtr<SrsLiveSource> source = app_factory_->create_live_source();
|
||||||
srs_trace("new live source, stream_url=%s", stream_url.c_str());
|
srs_trace("new live source, stream_url=%s, dead=%d", stream_url.c_str(), source->stream_is_dead());
|
||||||
pps = source;
|
pps = source;
|
||||||
|
|
||||||
// Callback to notify request of source creation
|
// Callback to notify request of source creation
|
||||||
|
|
@ -1781,7 +1781,10 @@ SrsLiveSource::SrsLiveSource()
|
||||||
mix_queue_ = new SrsMixQueue();
|
mix_queue_ = new SrsMixQueue();
|
||||||
|
|
||||||
can_publish_ = true;
|
can_publish_ = true;
|
||||||
stream_die_at_ = 0;
|
// Initialize stream_die_at_ to current time to prevent newly created sources
|
||||||
|
// from being immediately considered dead by stream_is_dead() check.
|
||||||
|
// @see https://github.com/ossrs/srs/issues/4449
|
||||||
|
stream_die_at_ = srs_time_now_cached();
|
||||||
publisher_idle_at_ = 0;
|
publisher_idle_at_ = 0;
|
||||||
|
|
||||||
rtmp_bridge_ = NULL;
|
rtmp_bridge_ = NULL;
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ srs_error_t SrsRtspSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<S
|
||||||
pps = source;
|
pps = source;
|
||||||
} else {
|
} else {
|
||||||
SrsSharedPtr<SrsRtspSource> source = SrsSharedPtr<SrsRtspSource>(new SrsRtspSource());
|
SrsSharedPtr<SrsRtspSource> source = SrsSharedPtr<SrsRtspSource>(new SrsRtspSource());
|
||||||
srs_trace("new rtsp source, stream_url=%s", stream_url.c_str());
|
srs_trace("new rtsp source, stream_url=%s, dead=%d", stream_url.c_str(), source->stream_is_dead());
|
||||||
pps = source;
|
pps = source;
|
||||||
|
|
||||||
pool_[stream_url] = source;
|
pool_[stream_url] = source;
|
||||||
|
|
@ -249,7 +249,10 @@ SrsRtspSource::SrsRtspSource()
|
||||||
|
|
||||||
req_ = NULL;
|
req_ = NULL;
|
||||||
|
|
||||||
stream_die_at_ = 0;
|
// Initialize stream_die_at_ to current time to prevent newly created sources
|
||||||
|
// from being immediately considered dead by stream_is_dead() check.
|
||||||
|
// @see https://github.com/ossrs/srs/issues/4449
|
||||||
|
stream_die_at_ = srs_time_now_cached();
|
||||||
|
|
||||||
stat_ = _srs_stat;
|
stat_ = _srs_stat;
|
||||||
circuit_breaker_ = _srs_circuit_breaker;
|
circuit_breaker_ = _srs_circuit_breaker;
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ srs_error_t SrsSrtSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<Sr
|
||||||
pps = source;
|
pps = source;
|
||||||
} else {
|
} else {
|
||||||
SrsSharedPtr<SrsSrtSource> source(new SrsSrtSource());
|
SrsSharedPtr<SrsSrtSource> source(new SrsSrtSource());
|
||||||
srs_trace("new srt source, stream_url=%s", stream_url.c_str());
|
srs_trace("new srt source, stream_url=%s, dead=%d", stream_url.c_str(), source->stream_is_dead());
|
||||||
pps = source;
|
pps = source;
|
||||||
|
|
||||||
pool_[stream_url] = source;
|
pool_[stream_url] = source;
|
||||||
|
|
@ -1230,7 +1230,10 @@ SrsSrtSource::SrsSrtSource()
|
||||||
req_ = NULL;
|
req_ = NULL;
|
||||||
can_publish_ = true;
|
can_publish_ = true;
|
||||||
srt_bridge_ = NULL;
|
srt_bridge_ = NULL;
|
||||||
stream_die_at_ = 0;
|
// Initialize stream_die_at_ to current time to prevent newly created sources
|
||||||
|
// from being immediately considered dead by stream_is_dead() check.
|
||||||
|
// @see https://github.com/ossrs/srs/issues/4449
|
||||||
|
stream_die_at_ = srs_time_now_cached();
|
||||||
|
|
||||||
stat_ = _srs_stat;
|
stat_ = _srs_stat;
|
||||||
format_ = new SrsSrtFormat();
|
format_ = new SrsSrtFormat();
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@
|
||||||
|
|
||||||
#define VERSION_MAJOR 7
|
#define VERSION_MAJOR 7
|
||||||
#define VERSION_MINOR 0
|
#define VERSION_MINOR 0
|
||||||
#define VERSION_REVISION 126
|
#define VERSION_REVISION 127
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -8,6 +8,11 @@
|
||||||
|
|
||||||
#include <srs_app_circuit_breaker.hpp>
|
#include <srs_app_circuit_breaker.hpp>
|
||||||
#include <srs_app_rtc_source.hpp>
|
#include <srs_app_rtc_source.hpp>
|
||||||
|
#include <srs_app_rtmp_source.hpp>
|
||||||
|
#include <srs_app_srt_source.hpp>
|
||||||
|
#ifdef SRS_RTSP
|
||||||
|
#include <srs_app_rtsp_source.hpp>
|
||||||
|
#endif
|
||||||
#include <srs_core_autofree.hpp>
|
#include <srs_core_autofree.hpp>
|
||||||
#include <srs_kernel_buffer.hpp>
|
#include <srs_kernel_buffer.hpp>
|
||||||
#include <srs_kernel_codec.hpp>
|
#include <srs_kernel_codec.hpp>
|
||||||
|
|
@ -1790,7 +1795,6 @@ VOID TEST(AppTest2, RtcSourceManagerFetchOrCreateInitializeFailure)
|
||||||
mock_source->set_initialize_error(initialize_error_);
|
mock_source->set_initialize_error(initialize_error_);
|
||||||
}
|
}
|
||||||
SrsSharedPtr<SrsRtcSource> source = SrsSharedPtr<SrsRtcSource>(mock_source);
|
SrsSharedPtr<SrsRtcSource> source = SrsSharedPtr<SrsRtcSource>(mock_source);
|
||||||
srs_trace("new rtc source, stream_url=%s", stream_url.c_str());
|
|
||||||
pps = source;
|
pps = source;
|
||||||
|
|
||||||
pool_[stream_url] = source;
|
pool_[stream_url] = source;
|
||||||
|
|
@ -1870,7 +1874,6 @@ VOID TEST(AppTest2, RtcSourceManagerFetchOrCreateErrorWrapping)
|
||||||
srs_freep(init_error);
|
srs_freep(init_error);
|
||||||
|
|
||||||
SrsSharedPtr<SrsRtcSource> source = SrsSharedPtr<SrsRtcSource>(mock_source);
|
SrsSharedPtr<SrsRtcSource> source = SrsSharedPtr<SrsRtcSource>(mock_source);
|
||||||
srs_trace("new rtc source, stream_url=%s", stream_url.c_str());
|
|
||||||
pps = source;
|
pps = source;
|
||||||
|
|
||||||
pool_[stream_url] = source;
|
pool_[stream_url] = source;
|
||||||
|
|
@ -2176,14 +2179,16 @@ VOID TEST(AppTest2, RtcSourceOnConsumerDestroyStreamDeath)
|
||||||
|
|
||||||
// Verify initial state - stream is not created (no publisher)
|
// Verify initial state - stream is not created (no publisher)
|
||||||
EXPECT_FALSE(source->is_created_);
|
EXPECT_FALSE(source->is_created_);
|
||||||
EXPECT_EQ(0, source->stream_die_at_);
|
// After fix #4449: stream_die_at_ is initialized to current time, not 0
|
||||||
|
srs_utime_t initial_die_at = source->stream_die_at_;
|
||||||
|
EXPECT_GT(initial_die_at, 0);
|
||||||
|
|
||||||
// Remove consumer when stream is not created - should set stream_die_at_
|
// Remove consumer when stream is not created - should update stream_die_at_
|
||||||
srs_utime_t before_time = srs_time_now_cached();
|
srs_utime_t before_time = srs_time_now_cached();
|
||||||
source->on_consumer_destroy(consumer);
|
source->on_consumer_destroy(consumer);
|
||||||
srs_utime_t after_time = srs_time_now_cached();
|
srs_utime_t after_time = srs_time_now_cached();
|
||||||
|
|
||||||
// Verify stream death time was set
|
// Verify stream death time was updated to current time
|
||||||
EXPECT_TRUE(source->stream_die_at_ >= before_time);
|
EXPECT_TRUE(source->stream_die_at_ >= before_time);
|
||||||
EXPECT_TRUE(source->stream_die_at_ <= after_time);
|
EXPECT_TRUE(source->stream_die_at_ <= after_time);
|
||||||
|
|
||||||
|
|
@ -2215,13 +2220,15 @@ VOID TEST(AppTest2, RtcSourceOnConsumerDestroyStreamAlive)
|
||||||
|
|
||||||
// Verify initial state
|
// Verify initial state
|
||||||
EXPECT_TRUE(source->is_created_);
|
EXPECT_TRUE(source->is_created_);
|
||||||
EXPECT_EQ(0, source->stream_die_at_);
|
// After fix #4449: stream_die_at_ is initialized to current time, not 0
|
||||||
|
srs_utime_t initial_die_at = source->stream_die_at_;
|
||||||
|
EXPECT_GT(initial_die_at, 0);
|
||||||
|
|
||||||
// Remove consumer when stream is created - should NOT set stream_die_at_
|
// Remove consumer when stream is created - should NOT update stream_die_at_
|
||||||
source->on_consumer_destroy(consumer);
|
source->on_consumer_destroy(consumer);
|
||||||
|
|
||||||
// Verify stream death time was NOT set
|
// Verify stream death time was NOT changed (still has initial value)
|
||||||
EXPECT_EQ(0, source->stream_die_at_);
|
EXPECT_EQ(initial_die_at, source->stream_die_at_);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
srs_freep(consumer);
|
srs_freep(consumer);
|
||||||
|
|
@ -4598,3 +4605,144 @@ VOID TEST(AppTest2, RtcSourceGetTrackDescMultipleMatchingVideoTracks)
|
||||||
EXPECT_EQ("video-h264-track-2", all_video_tracks[1]->id_);
|
EXPECT_EQ("video-h264-track-2", all_video_tracks[1]->id_);
|
||||||
EXPECT_EQ("video-h265-track", all_video_tracks[2]->id_);
|
EXPECT_EQ("video-h265-track", all_video_tracks[2]->id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reproduce issue 4449: Newly created source is immediately considered dead
|
||||||
|
// When a new source is created with stream_die_at_=0, if notify() timer fires
|
||||||
|
// before a publisher connects, the source gets deleted because stream_is_dead()
|
||||||
|
// returns true. This causes "new live source, dead=1" in logs.
|
||||||
|
VOID TEST(ReproduceIssue4449, RtmpLiveSourceNotifyDeletesNewlyCreatedSource)
|
||||||
|
{
|
||||||
|
srs_error_t err;
|
||||||
|
|
||||||
|
// Create a source manager
|
||||||
|
SrsUniquePtr<SrsLiveSourceManager> manager(new SrsLiveSourceManager());
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->initialize());
|
||||||
|
|
||||||
|
// Create a mock request
|
||||||
|
SrsUniquePtr<SrsRequest> req(new SrsRequest());
|
||||||
|
req->host_ = "localhost";
|
||||||
|
req->vhost_ = "test.vhost";
|
||||||
|
req->app_ = "live";
|
||||||
|
req->stream_ = "thegobot";
|
||||||
|
|
||||||
|
// Fetch or create source (this creates a new source)
|
||||||
|
SrsSharedPtr<SrsLiveSource> source;
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(req.get(), source));
|
||||||
|
|
||||||
|
// After fix: newly created source should NOT be dead
|
||||||
|
EXPECT_FALSE(source->stream_is_dead());
|
||||||
|
EXPECT_EQ(1, (int)manager->pool_.size());
|
||||||
|
|
||||||
|
// Simulate timer firing - call notify()
|
||||||
|
int pool_size_before = (int)manager->pool_.size();
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->notify(0, 0, 0));
|
||||||
|
int pool_size_after = (int)manager->pool_.size();
|
||||||
|
|
||||||
|
// After fix: the newly created source should NOT be deleted by notify
|
||||||
|
EXPECT_EQ(pool_size_before, pool_size_after);
|
||||||
|
EXPECT_EQ(1, pool_size_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test SRT source for the same issue
|
||||||
|
VOID TEST(ReproduceIssue4449, SrtSourceNotifyDeletesNewlyCreatedSource)
|
||||||
|
{
|
||||||
|
srs_error_t err;
|
||||||
|
|
||||||
|
// Create a SRT source manager
|
||||||
|
SrsUniquePtr<SrsSrtSourceManager> manager(new SrsSrtSourceManager());
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->initialize());
|
||||||
|
|
||||||
|
// Create a mock request
|
||||||
|
SrsUniquePtr<SrsRequest> req(new SrsRequest());
|
||||||
|
req->host_ = "localhost";
|
||||||
|
req->vhost_ = "test.vhost";
|
||||||
|
req->app_ = "live";
|
||||||
|
req->stream_ = "thegobot";
|
||||||
|
|
||||||
|
// Fetch or create source (this creates a new source)
|
||||||
|
SrsSharedPtr<SrsSrtSource> source;
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(req.get(), source));
|
||||||
|
|
||||||
|
// After fix: newly created source should NOT be dead
|
||||||
|
EXPECT_FALSE(source->stream_is_dead());
|
||||||
|
EXPECT_EQ(1, (int)manager->pool_.size());
|
||||||
|
|
||||||
|
// Simulate timer firing - call notify()
|
||||||
|
int pool_size_before = (int)manager->pool_.size();
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->notify(0, 0, 0));
|
||||||
|
int pool_size_after = (int)manager->pool_.size();
|
||||||
|
|
||||||
|
// After fix: the newly created source should NOT be deleted by notify
|
||||||
|
EXPECT_EQ(pool_size_before, pool_size_after);
|
||||||
|
EXPECT_EQ(1, pool_size_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test RTC source for the same issue
|
||||||
|
VOID TEST(ReproduceIssue4449, RtcSourceNotifyDeletesNewlyCreatedSource)
|
||||||
|
{
|
||||||
|
srs_error_t err;
|
||||||
|
|
||||||
|
// Create a RTC source manager
|
||||||
|
SrsUniquePtr<SrsRtcSourceManager> manager(new SrsRtcSourceManager());
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->initialize());
|
||||||
|
|
||||||
|
// Create a mock request
|
||||||
|
SrsUniquePtr<SrsRequest> req(new SrsRequest());
|
||||||
|
req->host_ = "localhost";
|
||||||
|
req->vhost_ = "test.vhost";
|
||||||
|
req->app_ = "live";
|
||||||
|
req->stream_ = "thegobot";
|
||||||
|
|
||||||
|
// Fetch or create source (this creates a new source)
|
||||||
|
SrsSharedPtr<SrsRtcSource> source;
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(req.get(), source));
|
||||||
|
|
||||||
|
// After fix: newly created source should NOT be dead
|
||||||
|
EXPECT_FALSE(source->stream_is_dead());
|
||||||
|
EXPECT_EQ(1, (int)manager->pool_.size());
|
||||||
|
|
||||||
|
// Simulate timer firing - call notify()
|
||||||
|
int pool_size_before = (int)manager->pool_.size();
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->notify(0, 0, 0));
|
||||||
|
int pool_size_after = (int)manager->pool_.size();
|
||||||
|
|
||||||
|
// After fix: the newly created source should NOT be deleted by notify
|
||||||
|
EXPECT_EQ(pool_size_before, pool_size_after);
|
||||||
|
EXPECT_EQ(1, pool_size_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SRS_RTSP
|
||||||
|
// Test RTSP source for the same issue
|
||||||
|
VOID TEST(ReproduceIssue4449, RtspSourceNotifyDeletesNewlyCreatedSource)
|
||||||
|
{
|
||||||
|
srs_error_t err;
|
||||||
|
|
||||||
|
// Create a RTSP source manager
|
||||||
|
SrsUniquePtr<SrsRtspSourceManager> manager(new SrsRtspSourceManager());
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->initialize());
|
||||||
|
|
||||||
|
// Create a mock request
|
||||||
|
SrsUniquePtr<SrsRequest> req(new SrsRequest());
|
||||||
|
req->host_ = "localhost";
|
||||||
|
req->vhost_ = "test.vhost";
|
||||||
|
req->app_ = "live";
|
||||||
|
req->stream_ = "thegobot";
|
||||||
|
|
||||||
|
// Fetch or create source (this creates a new source)
|
||||||
|
SrsSharedPtr<SrsRtspSource> source;
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(req.get(), source));
|
||||||
|
|
||||||
|
// After fix: newly created source should NOT be dead
|
||||||
|
EXPECT_FALSE(source->stream_is_dead());
|
||||||
|
EXPECT_EQ(1, (int)manager->pool_.size());
|
||||||
|
|
||||||
|
// Simulate timer firing - call notify()
|
||||||
|
int pool_size_before = (int)manager->pool_.size();
|
||||||
|
HELPER_EXPECT_SUCCESS(manager->notify(0, 0, 0));
|
||||||
|
int pool_size_after = (int)manager->pool_.size();
|
||||||
|
|
||||||
|
// After fix: the newly created source should NOT be deleted by notify
|
||||||
|
EXPECT_EQ(pool_size_before, pool_size_after);
|
||||||
|
EXPECT_EQ(1, pool_size_after);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user