Skip to content

Commit ede0d07

Browse files
committed
fix: make live stdin reader wakeable during shutdown joins
publish_live parked its stdin reader thread in istream::read on std::cin, which blocks until a full 16 KiB chunk or EOF. Every error-path join then depended on the feeder closing stdin: a relay drop with a live-but-idle feeder hung the publisher - the same unwakeable-blocking-call shape as the DASH accept() hang fixed in 65c5a4f. Route all stdin consumption in publish_live (both the ftyp+moov discovery phase and the reader thread) through one helper that, for real stdin on POSIX, reads fd 0 directly behind a bounded 100ms poll and re-checks a stop flag that joins now set first. Reading the fd raw in only one phase would lose bytes read ahead into cin's stdio buffer, so both phases share the helper and the istream is never used for cin. Non-cin istreams (unit tests) and Windows keep the original blocking read.
1 parent 65c5a4f commit ede0d07

1 file changed

Lines changed: 79 additions & 13 deletions

File tree

src/transport/moqt_session.cpp

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
#include "openmoq/publisher/mp4_box.h"
66

77
#include <algorithm>
8+
#include <array>
89
#include <atomic>
10+
#include <cerrno>
911
#include <chrono>
1012
#include <cstdint>
1113
#include <cstdlib>
@@ -24,6 +26,11 @@
2426
#include <tuple>
2527
#include <vector>
2628

29+
#if !defined(_WIN32)
30+
#include <poll.h>
31+
#include <unistd.h>
32+
#endif
33+
2734
namespace openmoq::publisher::transport {
2835

2936
namespace {
@@ -3279,6 +3286,52 @@ TransportStatus MoqtSession::publish_live(const LiveIngestOptions& ingest,
32793286
encode_publish_namespace_done_message(namespace_message), false);
32803287
}
32813288

3289+
namespace {
3290+
3291+
// Reads live input for publish_live, appending to the streaming reader and
3292+
// returning the byte count (0 on end of stream or stop request). For real
3293+
// stdin on POSIX, reads the fd directly behind a bounded poll: istream::read
3294+
// would park until a full chunk arrives, which made shutdown joins hang on a
3295+
// feeder that is alive but idle, and mixing istream reads with raw fd reads
3296+
// would lose bytes buffered inside cin/stdio — so every stdin byte in this
3297+
// flow must come through here. A null stop means wait indefinitely for data,
3298+
// matching the blocking semantics of the pre-thread discovery phase.
3299+
std::size_t read_live_input(std::istream& input,
3300+
openmoq::publisher::StreamingMp4Reader& reader,
3301+
const std::atomic<bool>* stop) {
3302+
#if !defined(_WIN32)
3303+
if (&input == &std::cin) {
3304+
constexpr int kStdinPollTimeoutMsec = 100;
3305+
std::array<std::uint8_t, 16384> buffer;
3306+
while (stop == nullptr || !stop->load(std::memory_order_acquire)) {
3307+
pollfd poll_fd{};
3308+
poll_fd.fd = STDIN_FILENO;
3309+
poll_fd.events = POLLIN;
3310+
const int ready = ::poll(&poll_fd, 1, kStdinPollTimeoutMsec);
3311+
if (ready <= 0) {
3312+
continue;
3313+
}
3314+
const ssize_t count = ::read(STDIN_FILENO, buffer.data(), buffer.size());
3315+
if (count < 0) {
3316+
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
3317+
continue;
3318+
}
3319+
return 0;
3320+
}
3321+
if (count == 0) {
3322+
return 0;
3323+
}
3324+
reader.append(buffer.data(), static_cast<std::size_t>(count));
3325+
return static_cast<std::size_t>(count);
3326+
}
3327+
return 0;
3328+
}
3329+
#endif
3330+
return reader.read_from(input);
3331+
}
3332+
3333+
} // namespace
3334+
32823335
TransportStatus MoqtSession::publish_live(std::istream& input,
32833336
openmoq::publisher::DraftVersion draft_version,
32843337
bool /*split_cmaf_chunks*/) {
@@ -3303,7 +3356,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
33033356
std::cerr << "[moqt-session] live: waiting for ftyp+moov from stdin...\n";
33043357

33053358
while (ftyp_bytes.empty() || moov_bytes.empty()) {
3306-
const std::size_t bytes_read = reader.read_from(input);
3359+
const std::size_t bytes_read = read_live_input(input, reader, nullptr);
33073360
if (bytes_read == 0 && ftyp_bytes.empty()) {
33083361
return TransportStatus::failure("stdin EOF before ftyp box");
33093362
}
@@ -3455,14 +3508,20 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
34553508
};
34563509
auto queue = std::make_shared<LiveMediaQueue>();
34573510

3458-
std::thread stdin_thread([&reader, &input, &tracks, queue]() {
3511+
std::atomic<bool> stdin_stop{false};
3512+
std::thread stdin_thread([&reader, &input, &tracks, queue, &stdin_stop]() {
34593513
std::vector<std::uint8_t> pending_moof;
34603514
std::size_t shared_group_id = 0;
34613515
std::map<std::string, std::size_t> object_id_in_group; // per track, resets on new group
34623516
bool first_keyframe_seen = false;
34633517

34643518
while (true) {
3465-
const std::size_t bytes_read = reader.read_from(input);
3519+
if (stdin_stop.load(std::memory_order_acquire)) {
3520+
std::lock_guard<std::mutex> lock(queue->mutex);
3521+
queue->eof = true;
3522+
break;
3523+
}
3524+
const std::size_t bytes_read = read_live_input(input, reader, &stdin_stop);
34663525

34673526
while (auto box = reader.next_box()) {
34683527
if (box->type == "moof") {
@@ -3530,6 +3589,13 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
35303589
}
35313590
});
35323591

3592+
const auto join_stdin_thread = [&stdin_thread, &stdin_stop]() {
3593+
stdin_stop.store(true, std::memory_order_release);
3594+
if (stdin_thread.joinable()) {
3595+
stdin_thread.join();
3596+
}
3597+
};
3598+
35333599
// Main loop: drain queue and publish fragments
35343600
std::map<std::string, SubgroupSenderState> sender_by_track;
35353601
std::map<std::string, std::uint64_t> last_group_id_by_track;
@@ -3950,7 +4016,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
39504016
{
39514017
status = drain_queue();
39524018
if (!status.ok) {
3953-
stdin_thread.join();
4019+
join_stdin_thread();
39544020
return status;
39554021
}
39564022
}
@@ -3963,7 +4029,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
39634029

39644030
auto [pre_status, pre_subs] = process_control_messages();
39654031
if (!pre_status.ok) {
3966-
stdin_thread.join();
4032+
join_stdin_thread();
39674033
return pre_status;
39684034
}
39694035

@@ -3977,7 +4043,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
39774043
}
39784044
auto [ctrl_status, new_subs] = process_control_messages();
39794045
if (!ctrl_status.ok) {
3980-
stdin_thread.join();
4046+
join_stdin_thread();
39814047
return ctrl_status;
39824048
}
39834049

@@ -4011,7 +4077,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40114077
// Drain remaining
40124078
status = drain_queue();
40134079
if (!status.ok) {
4014-
stdin_thread.join();
4080+
join_stdin_thread();
40154081
return status;
40164082
}
40174083
break;
@@ -4023,14 +4089,14 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40234089
if (!pending_control_bytes_.empty()) {
40244090
auto [pre_status, pre_subs] = process_control_messages();
40254091
if (!pre_status.ok) {
4026-
stdin_thread.join();
4092+
join_stdin_thread();
40274093
return pre_status;
40284094
}
40294095
}
40304096

40314097
auto [request_status, request_subs] = process_control_messages();
40324098
if (!request_status.ok) {
4033-
stdin_thread.join();
4099+
join_stdin_thread();
40344100
return request_status;
40354101
}
40364102

@@ -4065,14 +4131,14 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40654131
break;
40664132
}
40674133
} else {
4068-
stdin_thread.join();
4134+
join_stdin_thread();
40694135
return read_status;
40704136
}
40714137

40724138
// Process control messages
40734139
auto [ctrl_status, new_subs] = process_control_messages();
40744140
if (!ctrl_status.ok) {
4075-
stdin_thread.join();
4141+
join_stdin_thread();
40764142
return ctrl_status;
40774143
}
40784144

@@ -4084,7 +4150,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40844150
if (!subscribed_tracks.empty()) {
40854151
status = drain_queue();
40864152
if (!status.ok) {
4087-
stdin_thread.join();
4153+
join_stdin_thread();
40884154
return status;
40894155
}
40904156
}
@@ -4098,7 +4164,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40984164
}
40994165
}
41004166

4101-
stdin_thread.join();
4167+
join_stdin_thread();
41024168

41034169
for (auto& [track_name, sender] : sender_by_track) {
41044170
status = sender.finish_group(transport_);

0 commit comments

Comments
 (0)