Skip to content

Commit 549d369

Browse files
Panic event + unit test
1 parent 245d55c commit 549d369

4 files changed

Lines changed: 60 additions & 6 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ When making larger scale changes, check with the developer before committing to
4040

4141
The SDK has three categories of threads:
4242

43-
**FFI callback thread** — The Rust FFI layer calls `LivekitFfiCallback` from a Rust-managed thread (typically a Tokio runtime thread). This single entry point deserializes the `FfiEvent` and calls `FfiClient::pushEvent`, which:
43+
**FFI callback thread** — The Rust FFI layer calls `ffiEventCallback` from a Rust-managed thread (typically a Tokio runtime thread). This single entry point deserializes the `FfiEvent` and calls `FfiClient::pushEvent`, which:
4444
1. Completes any pending async `std::promise` matched by `async_id`.
4545
2. Invokes all registered `FfiClient` listeners (including `Room::onEvent`).
4646

src/ffi_client.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include "ffi_client.h"
1818

1919
#include <cassert>
20+
#include <csignal>
21+
#include <iostream>
2022

2123
#include "data_track.pb.h"
2224
#include "e2ee.pb.h"
@@ -167,7 +169,7 @@ bool FfiClient::initialize(bool capture_logs) {
167169
return false;
168170
}
169171
initialized_.store(true, std::memory_order_release);
170-
livekit_ffi_initialize(&LivekitFfiCallback, capture_logs, LIVEKIT_BUILD_FLAVOR, LIVEKIT_BUILD_VERSION);
172+
livekit_ffi_initialize(&ffiEventCallback, capture_logs, LIVEKIT_BUILD_FLAVOR, LIVEKIT_BUILD_VERSION);
171173
return true;
172174
}
173175

@@ -250,11 +252,19 @@ void FfiClient::pushEvent(const proto::FfiEvent& event) const {
250252
}
251253
}
252254

253-
void LivekitFfiCallback(const uint8_t* buf, size_t len) {
255+
extern "C" LIVEKIT_INTERNAL_API void ffiEventCallback(const uint8_t* buf, size_t len) {
254256
proto::FfiEvent event;
255257
event.ParseFromArray(buf,
256258
static_cast<int>(len)); // TODO: this fixes for now, what if len exceeds int?
257259

260+
// We are in a unrecoverable state, terminate the process
261+
// This is what Python does, may not make sense for C++
262+
if (event.has_panic()) {
263+
std::cerr << "FFI Panic: " << event.panic().message() << '\n';
264+
std::raise(SIGTERM);
265+
return;
266+
}
267+
258268
FfiClient::instance().pushEvent(event);
259269
}
260270

src/ffi_client.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
#include <cstdint>
2121
#include <functional>
2222
#include <future>
23-
#include <iostream>
2423
#include <memory>
2524
#include <mutex>
2625
#include <optional>
@@ -59,7 +58,7 @@ extern "C" void livekit_ffi_initialize(FfiCallbackFn cb, bool capture_logs, cons
5958

6059
extern "C" void livekit_ffi_dispose();
6160

62-
extern "C" void LivekitFfiCallback(const uint8_t* buf, size_t len);
61+
extern "C" LIVEKIT_INTERNAL_API void ffiEventCallback(const uint8_t* buf, size_t len);
6362

6463
// The FfiClient is used to communicate with the FFI interface of the Rust SDK
6564
// We use the generated protocol messages to facilitate the communication.
@@ -195,7 +194,7 @@ class LIVEKIT_INTERNAL_API FfiClient {
195194
std::atomic<AsyncId> next_async_id_{1};
196195

197196
void pushEvent(const proto::FfiEvent& event) const;
198-
friend void LivekitFfiCallback(const uint8_t* buf, size_t len);
197+
friend void ffiEventCallback(const uint8_t* buf, size_t len);
199198
std::atomic<bool> initialized_{false};
200199
};
201200
} // namespace livekit

src/tests/unit/test_ffi_client.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,29 @@
1717
#include <gtest/gtest.h>
1818
#include <livekit/livekit.h>
1919

20+
#include <csignal>
2021
#include <stdexcept>
22+
#include <string>
2123
#include <unordered_set>
2224

2325
#include "ffi.pb.h"
2426
#include "ffi_client.h"
2527

2628
namespace livekit::test {
2729

30+
namespace {
31+
32+
volatile bool g_sigterm_received = false;
33+
34+
// Has to be registered globally per csignal API
35+
void handleSignal(int signal) {
36+
if (signal == SIGTERM) {
37+
g_sigterm_received = true;
38+
}
39+
}
40+
41+
} // namespace
42+
2843
class FfiClientTest : public ::testing::Test {
2944
protected:
3045
void SetUp() override {
@@ -140,6 +155,36 @@ TEST_F(FfiClientTest, ListenerRegistrationSurvivesShutdownReinitCycle) {
140155
EXPECT_NO_THROW(FfiClient::instance().removeListener(id));
141156
}
142157

158+
TEST_F(FfiClientTest, PanicEvent) {
159+
// Wire up a signal handler to ensure the panic event raises SIGTERM
160+
// (and that users can handle it)
161+
g_sigterm_received = false;
162+
auto previous_handler = std::signal(SIGTERM, handleSignal);
163+
ASSERT_NE(previous_handler, SIG_ERR);
164+
165+
// Wire up a listener to ensure the panic event doesn't make it through
166+
// (matches Python SDK)
167+
bool listener_called = false;
168+
const auto id =
169+
FfiClient::instance().addListener([&listener_called](const proto::FfiEvent&) { listener_called = true; });
170+
171+
proto::FfiEvent event;
172+
event.mutable_panic()->set_message("rust panic");
173+
std::string bytes;
174+
ASSERT_TRUE(event.SerializeToString(&bytes));
175+
176+
testing::internal::CaptureStderr();
177+
ffiEventCallback(reinterpret_cast<const std::uint8_t*>(bytes.data()), bytes.size());
178+
const std::string stderr_output = testing::internal::GetCapturedStderr();
179+
180+
ASSERT_NE(std::signal(SIGTERM, previous_handler), SIG_ERR);
181+
FfiClient::instance().removeListener(id);
182+
183+
EXPECT_TRUE(g_sigterm_received);
184+
EXPECT_FALSE(listener_called);
185+
EXPECT_NE(stderr_output.find("FFI Panic: rust panic"), std::string::npos);
186+
}
187+
143188
// ---------------------------------------------------------------------------
144189
// These tests ensure FfiClient methods throw in various error conditions
145190
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)