Skip to content

Commit d57bfc5

Browse files
committed
Close and flush the previous player on reconnect in egress
The listener factory overwrote impl->player without closing the old session, so a stalled player replaced by a reconnect left its fd open forever: the writer thread stayed blocked in the old player's send(), writer.join() in stop() never returned, and the fd that would unblock it was only closed after that join. Shut down the outgoing session's fd under player_mutex before installing the new one so a blocked writer unblocks. Also add PlayerQueue::clear() and call it in the same critical section, after closing the old player and before installing the new one, so coded frames queued during the previous player's tenure are dropped instead of sitting ahead of the new player's init frames. clear() does not count toward dropped() -- it is a deliberate flush, not the loss dropped() tracks. Add a unit test covering that.
1 parent 1ed617c commit d57bfc5

4 files changed

Lines changed: 37 additions & 0 deletions

File tree

gateway/include/roqr/gateway/player_queue.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ class PlayerQueue {
3636
size_t size() const;
3737
uint64_t dropped() const;
3838

39+
// Drop all queued entries without counting them toward dropped() — used
40+
// when installing a new player so stale prior-era frames don't linger
41+
// ahead of the new player's init frames; this is a deliberate flush, not
42+
// the loss dropped() tracks.
43+
void clear();
44+
3945
private:
4046
struct Entry {
4147
roqr::rtmp::RtmpMessage msg;

gateway/src/egress.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,14 @@ bool EgressGateway::start(const EgressOptions& options) {
158158
[impl](roqr::rtmp::ServerSession& s) {
159159
{
160160
std::lock_guard lock(impl->player_mutex);
161+
// A previous player may still be alive (e.g. a stalled one
162+
// that never disconnected): shut its fd down so any writer
163+
// blocked in its send() unblocks, then drop its queued
164+
// frames so the new player isn't fed stale, prior-era media
165+
// ahead of its own init frames.
166+
if (impl->player != nullptr && impl->player != &s)
167+
impl->player->close();
168+
impl->queue.clear();
161169
impl->player = &s;
162170
impl->player_ready = false;
163171
}

gateway/src/player_queue.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,9 @@ uint64_t PlayerQueue::dropped() const {
5656
return dropped_;
5757
}
5858

59+
void PlayerQueue::clear() {
60+
std::lock_guard lock(mutex_);
61+
queue_.clear();
62+
}
63+
5964
} // namespace roqr::gateway

tests/gateway/player_queue_test.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,21 @@ TEST_CASE("close unblocks a waiting consumer and drains remaining") {
7676
q2.close();
7777
consumer.join();
7878
}
79+
80+
TEST_CASE("clear drops all entries without counting them as dropped") {
81+
PlayerQueue q(8);
82+
REQUIRE(q.push(seq_header(), PlayerQueue::Kind::Init));
83+
REQUIRE(q.push(vid(1), PlayerQueue::Kind::Coded));
84+
REQUIRE(q.push(vid(2), PlayerQueue::Kind::Coded));
85+
CHECK(q.size() == 3);
86+
CHECK(q.dropped() == 0);
87+
88+
q.clear();
89+
90+
CHECK(q.size() == 0);
91+
CHECK(q.dropped() == 0); // clear() is not a drop-tracked eviction
92+
93+
// Queue is still usable after clear().
94+
REQUIRE(q.push(vid(3), PlayerQueue::Kind::Coded));
95+
CHECK(q.pop()->timestamp == 3);
96+
}

0 commit comments

Comments
 (0)