fix(rtmp): avoid segfault on connection-refused teardown
Track libavformat RTMP session initialization state so teardown only writes a trailer after the muxer header succeeds. This avoids calling av_write_trailer() on partially initialized sessions when avio_open2() fails with Connection refused. Add a fault-suite regression for libavformat RTMP connection refusal and update the summary helper to compute all_pass from the actual manifest size instead of a hardcoded seven-row expectation. Verified by rebuilding cvmmap_streamer and rtmp_output_tester, reproducing the refused-connection path without a crash, and running ./scripts/fault_suite.sh successfully (8/8).
This commit is contained in:
+13
-1
@@ -238,6 +238,18 @@ EOF
|
|||||||
--frame-interval-ms 1 \
|
--frame-interval-ms 1 \
|
||||||
--linger-ms 0
|
--linger-ms 0
|
||||||
|
|
||||||
|
run_expected_failure 8 "libavformat_connection_refused" "libavformat connection refusal surfaces without crashing" \
|
||||||
|
"failed to open RTMP output 'rtmp://127.0.0.1:1/live/test': Connection refused" \
|
||||||
|
"${RTMP_OUTPUT_TESTER}" \
|
||||||
|
--rtmp-url rtmp://127.0.0.1:1/live/test \
|
||||||
|
--transport libavformat \
|
||||||
|
--codec h264 \
|
||||||
|
--frames 1 \
|
||||||
|
--width 320 \
|
||||||
|
--height 240 \
|
||||||
|
--frame-interval-ms 1 \
|
||||||
|
--linger-ms 0
|
||||||
|
|
||||||
local finished_at_utc
|
local finished_at_utc
|
||||||
finished_at_utc="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
finished_at_utc="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||||
|
|
||||||
@@ -294,7 +306,7 @@ PY
|
|||||||
echo "counts_pass=${pass_count}"
|
echo "counts_pass=${pass_count}"
|
||||||
echo "counts_fail=${fail_count}"
|
echo "counts_fail=${fail_count}"
|
||||||
echo "all_pass=${all_pass}"
|
echo "all_pass=${all_pass}"
|
||||||
echo "scenarios=removed_encoder_backend,removed_rtmp_transport,removed_rtmp_mode_cli,removed_rtmp_mode_toml,missing_rtmp_url,invalid_rtp_endpoint,ffmpeg_process_bad_binary"
|
echo "scenarios=removed_encoder_backend,removed_rtmp_transport,removed_rtmp_mode_cli,removed_rtmp_mode_toml,missing_rtmp_url,invalid_rtp_endpoint,ffmpeg_process_bad_binary,libavformat_connection_refused"
|
||||||
} > "${EVIDENCE_TEXT}"
|
} > "${EVIDENCE_TEXT}"
|
||||||
|
|
||||||
if [[ "${all_pass}" == "true" ]]; then
|
if [[ "${all_pass}" == "true" ]]; then
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def build_summary(args: CliArgs) -> dict[str, object]:
|
|||||||
pass_count = sum(1 for row in rows if row["status"] == "PASS")
|
pass_count = sum(1 for row in rows if row["status"] == "PASS")
|
||||||
fail_count = sum(1 for row in rows if row["status"] == "FAIL")
|
fail_count = sum(1 for row in rows if row["status"] == "FAIL")
|
||||||
skip_count = sum(1 for row in rows if row["status"] == "SKIP")
|
skip_count = sum(1 for row in rows if row["status"] == "SKIP")
|
||||||
all_pass = len(rows) == 7 and pass_count == 7 and fail_count == 0 and skip_count == 0
|
all_pass = len(rows) > 0 and pass_count == len(rows) and fail_count == 0 and skip_count == 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"run_id": args.run_id,
|
"run_id": args.run_id,
|
||||||
|
|||||||
@@ -230,6 +230,8 @@ public:
|
|||||||
std::string url{};
|
std::string url{};
|
||||||
AVFormatContext *format_context{nullptr};
|
AVFormatContext *format_context{nullptr};
|
||||||
AVStream *video_stream{nullptr};
|
AVStream *video_stream{nullptr};
|
||||||
|
bool io_opened{false};
|
||||||
|
bool header_written{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
LibavformatRtmpOutput() = default;
|
LibavformatRtmpOutput() = default;
|
||||||
@@ -368,6 +370,7 @@ private:
|
|||||||
ERR_NETWORK,
|
ERR_NETWORK,
|
||||||
"failed to open RTMP output '" + url + "': " + av_error_string(open_result));
|
"failed to open RTMP output '" + url + "': " + av_error_string(open_result));
|
||||||
}
|
}
|
||||||
|
session.io_opened = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RTMP sockets are non-seekable, so the FLV muxer must not try to backfill
|
// RTMP sockets are non-seekable, so the FLV muxer must not try to backfill
|
||||||
@@ -388,6 +391,7 @@ private:
|
|||||||
ERR_PROTOCOL,
|
ERR_PROTOCOL,
|
||||||
"failed to write RTMP header for '" + url + "': " + av_error_string(header_result));
|
"failed to write RTMP header for '" + url + "': " + av_error_string(header_result));
|
||||||
}
|
}
|
||||||
|
session.header_written = true;
|
||||||
|
|
||||||
spdlog::info(
|
spdlog::info(
|
||||||
"RTMP_OUTPUT_READY backend=libavformat codec={} url={}",
|
"RTMP_OUTPUT_READY backend=libavformat codec={} url={}",
|
||||||
@@ -401,9 +405,15 @@ private:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
av_write_trailer(session.format_context);
|
if (session.header_written) {
|
||||||
if (!(session.format_context->oformat->flags & AVFMT_NOFILE) && session.format_context->pb != nullptr) {
|
av_write_trailer(session.format_context);
|
||||||
|
session.header_written = false;
|
||||||
|
}
|
||||||
|
if (session.io_opened &&
|
||||||
|
!(session.format_context->oformat->flags & AVFMT_NOFILE) &&
|
||||||
|
session.format_context->pb != nullptr) {
|
||||||
avio_closep(&session.format_context->pb);
|
avio_closep(&session.format_context->pb);
|
||||||
|
session.io_opened = false;
|
||||||
}
|
}
|
||||||
avformat_free_context(session.format_context);
|
avformat_free_context(session.format_context);
|
||||||
session.format_context = nullptr;
|
session.format_context = nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user