Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fa33007

Browse files
BugFix: Bot-343, DataTracks return from subscribe() and read() from rust sdk (livekit#119)
1 parent af0820c commit fa33007

10 files changed

Lines changed: 267 additions & 12 deletions

client-sdk-rust

include/livekit/data_track_stream.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <mutex>
2424
#include <optional>
2525

26+
#include "livekit/data_track_error.h"
2627
#include "livekit/data_track_frame.h"
2728
#include "livekit/ffi_handle.h"
2829

@@ -79,6 +80,15 @@ class DataTrackStream {
7980
*/
8081
bool read(DataTrackFrame& out);
8182

83+
/**
84+
* Returns the terminal subscription error reported by the FFI stream.
85+
*
86+
* This is set when read() returns false because subscription establishment
87+
* failed before any frames were emitted. It remains empty for normal EOS or
88+
* when close() ends the stream locally.
89+
*/
90+
std::optional<SubscribeDataTrackError> terminalError() const;
91+
8292
/**
8393
* End the stream early.
8494
*
@@ -89,6 +99,9 @@ class DataTrackStream {
8999

90100
private:
91101
friend class RemoteDataTrack;
102+
#ifdef LIVEKIT_TEST_ACCESS
103+
friend class DataTrackStreamTest;
104+
#endif
92105

93106
DataTrackStream() = default;
94107
/// Internal init helper, called by RemoteDataTrack.
@@ -101,7 +114,7 @@ class DataTrackStream {
101114
void pushFrame(DataTrackFrame&& frame);
102115

103116
/// Push an end-of-stream signal (EOS).
104-
void pushEos();
117+
void pushEos(std::optional<SubscribeDataTrackError> error = std::nullopt);
105118

106119
/** Protects all mutable state below. */
107120
mutable std::mutex mutex_;
@@ -122,6 +135,9 @@ class DataTrackStream {
122135
/** True after close() has been called by the consumer. */
123136
bool closed_{false};
124137

138+
/** Typed terminal error reported with EOS, if subscription setup failed. */
139+
std::optional<SubscribeDataTrackError> terminal_error_;
140+
125141
/** RAII handle for the Rust-owned subscription resource. */
126142
FfiHandle subscription_handle_;
127143

src/data_track_error.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "livekit/data_track_error.h"
1818

1919
#include "data_track.pb.h"
20+
#include "lk_log.h"
2021

2122
namespace livekit {
2223

@@ -95,6 +96,8 @@ LocalDataTrackTryPushError LocalDataTrackTryPushError::fromProto(const proto::Lo
9596
}
9697

9798
SubscribeDataTrackError SubscribeDataTrackError::fromProto(const proto::SubscribeDataTrackError& error) {
99+
LK_LOG_WARN("Subscribe data track error from FFI: code={} message={}", static_cast<std::uint32_t>(error.code()),
100+
error.message());
98101
return SubscribeDataTrackError{fromProtoCode(error.code()), error.message()};
99102
}
100103

src/data_track_stream.cpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ bool DataTrackStream::read(DataTrackFrame& out) {
6565
return true;
6666
}
6767

68+
std::optional<SubscribeDataTrackError> DataTrackStream::terminalError() const {
69+
const std::scoped_lock<std::mutex> lock(mutex_);
70+
return terminal_error_;
71+
}
72+
6873
void DataTrackStream::close() {
6974
std::int32_t listener_id = -1;
7075
{
@@ -73,9 +78,14 @@ void DataTrackStream::close() {
7378
return;
7479
}
7580
closed_ = true;
81+
// Preserve errors reported by EOS for post-stream inspection, but do not
82+
// treat a local early close as a terminal subscription error.
83+
if (!eof_) {
84+
terminal_error_.reset();
85+
}
7686
subscription_handle_.reset();
7787
listener_id = listener_id_;
78-
listener_id_ = 0;
88+
listener_id_ = -1;
7989
}
8090

8191
if (listener_id != -1) {
@@ -103,7 +113,12 @@ void DataTrackStream::onFfiEvent(const FfiEvent& event) {
103113
DataTrackFrame frame = DataTrackFrame::fromOwnedInfo(fr);
104114
pushFrame(std::move(frame));
105115
} else if (dts.has_eos()) {
106-
pushEos();
116+
std::optional<SubscribeDataTrackError> error;
117+
const auto& eos = dts.eos();
118+
if (eos.has_error()) {
119+
error = SubscribeDataTrackError::fromProto(eos.error());
120+
}
121+
pushEos(std::move(error));
107122
}
108123
}
109124

@@ -123,13 +138,14 @@ void DataTrackStream::pushFrame(DataTrackFrame&& frame) {
123138
cv_.notify_one();
124139
}
125140

126-
void DataTrackStream::pushEos() {
141+
void DataTrackStream::pushEos(std::optional<SubscribeDataTrackError> error) {
127142
{
128143
const std::scoped_lock<std::mutex> lock(mutex_);
129144
if (eof_) {
130145
return;
131146
}
132147
eof_ = true;
148+
terminal_error_ = std::move(error);
133149
}
134150
cv_.notify_all();
135151
}

src/ffi_client.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ inline void logAndThrow(const std::string& error_msg) {
4646
throw std::runtime_error(error_msg);
4747
}
4848

49+
Result<proto::OwnedDataTrackStream, SubscribeDataTrackError> subscribeDataTrackFailure(SubscribeDataTrackErrorCode code,
50+
const std::string& message) {
51+
LK_LOG_WARN("Subscribe data track failed: code={} message={}", static_cast<std::uint32_t>(code), message);
52+
return Result<proto::OwnedDataTrackStream, SubscribeDataTrackError>::failure(SubscribeDataTrackError{code, message});
53+
}
54+
4955
std::optional<FfiClient::AsyncId> ExtractAsyncId(const proto::FfiEvent& event) {
5056
using E = proto::FfiEvent;
5157
switch (event.message_case()) {
@@ -651,18 +657,17 @@ Result<proto::OwnedDataTrackStream, SubscribeDataTrackError> FfiClient::subscrib
651657
try {
652658
const proto::FfiResponse resp = sendRequest(req);
653659
if (!resp.has_subscribe_data_track()) {
654-
return Result<proto::OwnedDataTrackStream, SubscribeDataTrackError>::failure(SubscribeDataTrackError{
655-
SubscribeDataTrackErrorCode::PROTOCOL_ERROR, "FfiResponse missing subscribe_data_track"});
660+
return subscribeDataTrackFailure(SubscribeDataTrackErrorCode::PROTOCOL_ERROR,
661+
"FfiResponse missing subscribe_data_track");
656662
}
657663
if (!resp.subscribe_data_track().has_stream()) {
658-
return Result<proto::OwnedDataTrackStream, SubscribeDataTrackError>::failure(SubscribeDataTrackError{
659-
SubscribeDataTrackErrorCode::PROTOCOL_ERROR, "FfiResponse subscribe_data_track missing stream"});
664+
return subscribeDataTrackFailure(SubscribeDataTrackErrorCode::PROTOCOL_ERROR,
665+
"FfiResponse subscribe_data_track missing stream");
660666
}
661667
proto::OwnedDataTrackStream sub = resp.subscribe_data_track().stream();
662668
return Result<proto::OwnedDataTrackStream, SubscribeDataTrackError>::success(std::move(sub));
663669
} catch (const std::exception& e) { // NOLINT(bugprone-empty-catch)
664-
return Result<proto::OwnedDataTrackStream, SubscribeDataTrackError>::failure(
665-
SubscribeDataTrackError{SubscribeDataTrackErrorCode::INTERNAL, e.what()});
670+
return subscribeDataTrackFailure(SubscribeDataTrackErrorCode::INTERNAL, e.what());
666671
}
667672
}
668673

src/remote_data_track.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "data_track.pb.h"
2222
#include "ffi.pb.h"
2323
#include "ffi_client.h"
24+
#include "lk_log.h"
2425

2526
namespace livekit {
2627

@@ -48,6 +49,9 @@ bool RemoteDataTrack::isPublished() const {
4849
Result<std::shared_ptr<DataTrackStream>, SubscribeDataTrackError> RemoteDataTrack::subscribe(
4950
const DataTrackStream::Options& options) {
5051
if (!handle_.valid()) {
52+
LK_LOG_WARN("Subscribe data track failed: code={} message={}",
53+
static_cast<std::uint32_t>(SubscribeDataTrackErrorCode::INVALID_HANDLE),
54+
"RemoteDataTrack::subscribe: invalid FFI handle");
5155
return Result<std::shared_ptr<DataTrackStream>, SubscribeDataTrackError>::failure(
5256
SubscribeDataTrackError{SubscribeDataTrackErrorCode::INVALID_HANDLE,
5357
"RemoteDataTrack::subscribe: invalid FFI "

src/subscription_thread_dispatcher.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,13 @@ std::thread SubscriptionThreadDispatcher::startDataReaderLocked(DataFrameCallbac
696696
LK_LOG_ERROR("Data frame callback exception: {}", e.what());
697697
}
698698
}
699+
const auto error = stream->terminalError();
700+
if (error.has_value()) {
701+
LK_LOG_ERROR(
702+
"Data reader stream ended with subscription error for \"{}\" from "
703+
"\"{}\": code={} message={}",
704+
track_name, identity, static_cast<std::uint32_t>(error->code), error->message);
705+
}
699706
LK_LOG_INFO("Data reader thread exiting for \"{}\" track=\"{}\"", identity, track_name);
700707
});
701708
// NOLINTEND(bugprone-lambda-function-name)

src/tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ if(UNIT_TEST_SOURCES)
6262
PRIVATE
6363
livekit
6464
spdlog::spdlog
65-
$<$<PLATFORM_ID:Windows>:${LIVEKIT_PROTOBUF_TARGET}>
65+
${LIVEKIT_PROTOBUF_TARGET}
6666
GTest::gtest_main
6767
)
6868

src/tests/integration/test_data_track.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,60 @@ TEST_F(DataTrackE2ETest, UnpublishUpdatesPublishedStateEndToEnd) {
340340
<< "Remote track did not report unpublished state";
341341
}
342342

343+
TEST_F(DataTrackE2ETest, SubscribeAfterUnpublishReportsTerminalError) {
344+
const auto track_name = makeTrackName("subscribe_after_unpublish");
345+
346+
DataTrackPublishedDelegate subscriber_delegate;
347+
std::vector<TestRoomConnectionOptions> room_configs(2);
348+
room_configs[1].delegate = &subscriber_delegate;
349+
350+
auto rooms = testRooms(room_configs);
351+
auto& publisher_room = rooms[0];
352+
353+
auto local_track = requirePublishedTrack(publisher_room->localParticipant(), track_name);
354+
ASSERT_TRUE(local_track->isPublished());
355+
356+
auto remote_track = subscriber_delegate.waitForTrack(kTrackWaitTimeout);
357+
ASSERT_NE(remote_track, nullptr) << "Timed out waiting for remote data track";
358+
ASSERT_TRUE(remote_track->isPublished());
359+
360+
local_track->unpublishDataTrack();
361+
ASSERT_FALSE(local_track->isPublished());
362+
ASSERT_TRUE(waitForCondition([&]() { return !remote_track->isPublished(); }, 2s))
363+
<< "Remote track did not report unpublished state";
364+
365+
auto subscribe_result = remote_track->subscribe();
366+
if (!subscribe_result) {
367+
FAIL() << "Expected subscribe to return a stream before terminal EOS: "
368+
<< describeDataTrackError(subscribe_result.error());
369+
}
370+
auto subscription = subscribe_result.value();
371+
372+
std::promise<bool> read_promise;
373+
auto read_future = read_promise.get_future();
374+
std::thread reader([subscription, promise = std::move(read_promise)]() mutable {
375+
DataTrackFrame frame;
376+
promise.set_value(subscription->read(frame));
377+
});
378+
379+
const auto read_status = read_future.wait_for(5s);
380+
if (read_status != std::future_status::ready) {
381+
subscription->close();
382+
}
383+
reader.join();
384+
385+
// TODO(BOT-347): this sometimes fails with a timeout.
386+
ASSERT_EQ(read_status, std::future_status::ready) << "Timed out waiting for terminal data-track EOS";
387+
EXPECT_FALSE(read_future.get()) << "Unpublished track subscription unexpectedly delivered a frame";
388+
389+
const auto terminal_error = subscription->terminalError();
390+
ASSERT_TRUE(terminal_error.has_value()) << "Expected terminal subscribe error on EOS";
391+
// EXPECT_EQ(terminal_error->code, SubscribeDataTrackErrorCode::UNPUBLISHED);
392+
// should this actually be internal?
393+
EXPECT_EQ(terminal_error->code, SubscribeDataTrackErrorCode::INTERNAL);
394+
EXPECT_FALSE(terminal_error->message.empty());
395+
}
396+
343397
TEST_F(DataTrackE2ETest, PublishManyTracks) {
344398
auto rooms = testRooms(1);
345399
auto& room = rooms[0];

0 commit comments

Comments
 (0)