srs/trunk/src/utest/srs_utest_workflow_srt_conn.cpp
OSSRS-AI 18c30dc07b
AI: SRT: Fix player not exiting when publisher disconnects. v7.0.130 (#4591) (#4596)
When SRT publisher disconnects, player hangs indefinitely instead of
exiting after the configured peer_idle_timeout. This is because the
consumer wait() never checks if the publisher is still connected.

After fix, player waits for peer_idle_timeout (default 10s) then exits
gracefully when no packets arrive and publisher has disconnected.
2025-11-27 19:32:24 -05:00

269 lines
11 KiB
C++

/**
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Winlin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <srs_utest_workflow_srt_conn.hpp>
#include <srs_app_srt_conn.hpp>
#include <srs_protocol_conn.hpp>
#include <srs_protocol_io.hpp>
#include <srs_protocol_st.hpp>
#include <srs_utest_ai11.hpp>
#include <srs_utest_ai18.hpp>
#include <srs_utest_manual_mock.hpp>
#include <srs_utest_manual_service.hpp>
#include <sys/socket.h>
#include <unistd.h>
// This test is used to verify the basic workflow of the SRT connection.
// It's finished with the help of AI, but each step is manually designed
// and verified. So this is not dominated by AI, but by humanbeing.
VOID TEST(BasicWorkflowSrtConnTest, ManuallyVerifyForPublisher)
{
srs_error_t err;
// Mock all interface dependencies
SrsUniquePtr<MockAppConfig> mock_config(new MockAppConfig());
SrsUniquePtr<MockConnectionManager> mock_manager(new MockConnectionManager());
SrsUniquePtr<MockLiveSourceManager> mock_sources(new MockLiveSourceManager());
SrsUniquePtr<MockStreamPublishTokenManager> mock_tokens(new MockStreamPublishTokenManager());
SrsUniquePtr<MockAppStatistic> mock_stat(new MockAppStatistic());
SrsUniquePtr<MockHttpHooks> mock_hooks(new MockHttpHooks());
SrsUniquePtr<MockRtcSourceManager> mock_rtc_sources(new MockRtcSourceManager());
SrsUniquePtr<MockSrtSourceManager> mock_srt_sources(new MockSrtSourceManager());
MockSrtConnection *mock_srt_conn = new MockSrtConnection();
MockSecurity *mock_security = new MockSecurity();
MockSrtFormat *mock_format = new MockSrtFormat();
mock_config->default_vhost_ = new SrsConfDirective();
mock_config->default_vhost_->name_ = "vhost";
mock_config->default_vhost_->args_.push_back("__defaultVhost__");
// Enable SRT in mock config
mock_config->srt_enabled_ = true;
mock_config->srt_to_rtmp_ = false;
mock_config->rtc_from_rtmp_ = false;
// Configure SRT connection mock
mock_srt_conn->streamid_ = "#!::h=127.0.0.1,r=live/livestream,m=publish";
mock_srt_conn->srt_fd_ = 100;
// Create SrsMpegtsSrtConn - it takes ownership of srt_conn
SrsUniquePtr<SrsMpegtsSrtConn> conn(new SrsMpegtsSrtConn(mock_manager.get(), 100, "192.168.1.100", 9000));
conn->config_ = mock_config.get();
conn->stat_ = mock_stat.get();
conn->stream_publish_tokens_ = mock_tokens.get();
conn->srt_sources_ = mock_srt_sources.get();
conn->live_sources_ = mock_sources.get();
conn->rtc_sources_ = mock_rtc_sources.get();
conn->hooks_ = mock_hooks.get();
srs_freep(conn->srt_conn_);
conn->srt_conn_ = mock_srt_conn;
srs_freep(conn->security_);
conn->security_ = mock_security;
// Start the SRT connection.
if (true) {
HELPER_EXPECT_SUCCESS(conn->start());
// Wait for coroutine to start.
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Verify the req should be parsed.
ISrsRequest *req = conn->req_;
EXPECT_STREQ("192.168.1.100", req->ip_.c_str());
EXPECT_STREQ("__defaultVhost__", req->vhost_.c_str());
EXPECT_STREQ("live", req->app_.c_str());
EXPECT_STREQ("livestream", req->stream_.c_str());
}
// Create MPEG-TS packets to feed the SRT source.
MockSrtSource *mock_srt_source = dynamic_cast<MockSrtSource *>(mock_srt_sources->mock_source_.get());
// Inject mock format into SRT source
if (true) {
srs_freep(mock_srt_source->format_);
mock_srt_source->format_ = mock_format;
}
if (true) {
// Create a simple MPEG-TS packet (188 bytes)
// This is a minimal TS packet structure for testing
char ts_packet[188];
memset(ts_packet, 0, sizeof(ts_packet));
// TS packet header: sync byte (0x47) + flags
ts_packet[0] = 0x47; // Sync byte
ts_packet[1] = 0x40; // Payload unit start indicator
ts_packet[2] = 0x00; // PID (0x000 = PAT)
ts_packet[3] = 0x10; // Continuity counter
// Simulate receiving TS packet
mock_srt_conn->recv_msgs_.push_back(std::string(ts_packet, sizeof(ts_packet)));
mock_srt_conn->cond_->signal();
// Wait for processing
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Check message should be read by SRT recv thread.
EXPECT_EQ(1, mock_srt_conn->read_count_);
EXPECT_EQ(1, mock_srt_source->on_packet_count_);
}
// Simulate client quit event
if (true) {
mock_srt_conn->read_error_ = srs_error_new(ERROR_SOCKET_READ, "mock client quit");
// Wait for coroutine to stop.
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
}
// Stop the SRT connection.
conn->stop();
}
// This test is used to verify the basic workflow of the SRT connection.
// It's finished with the help of AI, but each step is manually designed
// and verified. So this is not dominated by AI, but by humanbeing.
VOID TEST(BasicWorkflowSrtConnTest, ManuallyVerifyForPlayer)
{
srs_error_t err;
// Mock all interface dependencies
SrsUniquePtr<MockAppConfig> mock_config(new MockAppConfig());
SrsUniquePtr<MockConnectionManager> mock_manager(new MockConnectionManager());
SrsUniquePtr<MockLiveSourceManager> mock_sources(new MockLiveSourceManager());
SrsUniquePtr<MockStreamPublishTokenManager> mock_tokens(new MockStreamPublishTokenManager());
SrsUniquePtr<MockAppStatistic> mock_stat(new MockAppStatistic());
SrsUniquePtr<MockHttpHooks> mock_hooks(new MockHttpHooks());
SrsUniquePtr<MockRtcSourceManager> mock_rtc_sources(new MockRtcSourceManager());
SrsUniquePtr<MockSrtSourceManager> mock_srt_sources(new MockSrtSourceManager());
MockSrtConnection *mock_srt_conn = new MockSrtConnection();
MockSecurity *mock_security = new MockSecurity();
MockSrtFormat *mock_format = new MockSrtFormat();
mock_config->default_vhost_ = new SrsConfDirective();
mock_config->default_vhost_->name_ = "vhost";
mock_config->default_vhost_->args_.push_back("__defaultVhost__");
// Enable SRT in mock config
mock_config->srt_enabled_ = true;
mock_config->srt_to_rtmp_ = false;
mock_config->rtc_from_rtmp_ = false;
// Configure SRT connection mock for play mode (m=request)
mock_srt_conn->streamid_ = "#!::h=127.0.0.1,r=live/livestream,m=request";
mock_srt_conn->srt_fd_ = 100;
// Simulate a publisher is connected (can_publish=false means publisher exists)
// @see https://github.com/ossrs/srs/issues/4591
mock_srt_sources->set_can_publish(false);
// Create SrsMpegtsSrtConn - it takes ownership of srt_conn
SrsUniquePtr<SrsMpegtsSrtConn> conn(new SrsMpegtsSrtConn(mock_manager.get(), 100, "192.168.1.100", 9000));
conn->config_ = mock_config.get();
conn->stat_ = mock_stat.get();
conn->stream_publish_tokens_ = mock_tokens.get();
conn->srt_sources_ = mock_srt_sources.get();
conn->live_sources_ = mock_sources.get();
conn->rtc_sources_ = mock_rtc_sources.get();
conn->hooks_ = mock_hooks.get();
srs_freep(conn->srt_conn_);
conn->srt_conn_ = mock_srt_conn;
srs_freep(conn->security_);
conn->security_ = mock_security;
// Start the SRT connection.
if (true) {
HELPER_EXPECT_SUCCESS(conn->start());
// Wait for coroutine to start.
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Verify the req should be parsed.
ISrsRequest *req = conn->req_;
EXPECT_STREQ("192.168.1.100", req->ip_.c_str());
EXPECT_STREQ("__defaultVhost__", req->vhost_.c_str());
EXPECT_STREQ("live", req->app_.c_str());
EXPECT_STREQ("livestream", req->stream_.c_str());
}
// Create MPEG-TS packets to feed the SRT source.
MockSrtSource *mock_srt_source = dynamic_cast<MockSrtSource *>(mock_srt_sources->mock_source_.get());
EXPECT_EQ(1, (int)mock_srt_source->consumers_.size());
// Inject mock format into SRT source
if (true) {
srs_freep(mock_srt_source->format_);
mock_srt_source->format_ = mock_format;
}
// Feed TS packets to the SRT source consumer.
// Note: The consumer waits for queue_.size() > mw_min_msgs_ (which is 1),
// so we need to enqueue 2 packets to trigger the signal, or wait for timeout.
if (true) {
// Create first MPEG-TS packet (188 bytes)
char ts_packet1[188];
memset(ts_packet1, 0, sizeof(ts_packet1));
ts_packet1[0] = 0x47; // Sync byte
ts_packet1[1] = 0x40; // Payload unit start indicator
ts_packet1[2] = 0x00; // PID (0x000 = PAT)
ts_packet1[3] = 0x10; // Continuity counter
SrsUniquePtr<SrsSrtPacket> packet1(new SrsSrtPacket());
packet1->wrap(ts_packet1, sizeof(ts_packet1));
HELPER_EXPECT_SUCCESS(mock_srt_source->on_srt_packet(packet1.get()));
EXPECT_EQ(1, mock_srt_source->on_packet_count_);
// Create second MPEG-TS packet to trigger consumer signal
char ts_packet2[188];
memset(ts_packet2, 0, sizeof(ts_packet2));
ts_packet2[0] = 0x47; // Sync byte
ts_packet2[1] = 0x40; // Payload unit start indicator
ts_packet2[2] = 0x00; // PID (0x000 = PAT)
ts_packet2[3] = 0x11; // Continuity counter (incremented)
SrsUniquePtr<SrsSrtPacket> packet2(new SrsSrtPacket());
packet2->wrap(ts_packet2, sizeof(ts_packet2));
HELPER_EXPECT_SUCCESS(mock_srt_source->on_srt_packet(packet2.get()));
EXPECT_EQ(2, mock_srt_source->on_packet_count_);
// Wait for consumer to process the messages.
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Verify that both packets are sent to the client.
EXPECT_EQ(376, mock_srt_conn->send_bytes_);
}
// Simulate client quit event, the receive thread will get this error.
if (true) {
mock_srt_conn->read_error_ = srs_error_new(ERROR_SOCKET_READ, "mock client quit");
mock_srt_conn->cond_->signal();
// Wait for coroutine to stop.
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
}
// Stop the SRT connection.
conn->stop();
}