From fac0a6d7fca5e20e9d880cc39e6cae9c96cea73b Mon Sep 17 00:00:00 2001 From: Radek Czemerys Date: Mon, 6 Apr 2026 20:31:18 +0800 Subject: [PATCH 1/2] fix: add mutex to VoiceActivityDetection to prevent race between generate() and unload() VoiceActivityDetection inherits from BaseModel but lacks the thread-safety that VisionModel already provides. When generate() runs on a worker thread (via ModelHostObject::promiseHostFunction) and unload() is called from the JS thread, BaseModel::unload() destroys module_ mid-inference, causing SIGILL/SIGSEGV crashes. This applies the same pattern used by VisionModel: - Add inference_mutex_ member - Lock in generate() to protect forward() calls - Override unload() to acquire the lock before BaseModel::unload() Fixes https://github.com/software-mansion/react-native-executorch/issues/1055 --- .../VoiceActivityDetection.cpp | 6 ++++++ .../VoiceActivityDetection.h | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.cpp b/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.cpp index a1252edfee..49971cba8c 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.cpp @@ -54,8 +54,14 @@ VoiceActivityDetection::preprocess(std::span waveform) const { return frameBuffer; } +void VoiceActivityDetection::unload() noexcept { + std::scoped_lock lock(inference_mutex_); + BaseModel::unload(); +} + std::vector VoiceActivityDetection::generate(std::span waveform) const { + std::scoped_lock lock(inference_mutex_); auto windowedInput = preprocess(waveform); auto [chunksNumber, remainder] = std::div( diff --git a/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h b/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h index e692889305..d2f2b1f9e6 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h +++ b/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "rnexecutorch/metaprogramming/ConstructorHelpers.h" @@ -23,7 +24,19 @@ class VoiceActivityDetection : public BaseModel { [[nodiscard("Registered non-void function")]] std::vector generate(std::span waveform) const; + /** + * @brief Thread-safe unload that waits for any in-flight inference to + * complete. + * + * Mirrors VisionModel::unload(). Without this, BaseModel::unload() can + * destroy module_ while generate() is still calling forward() on a worker + * thread, causing SIGILL / SIGSEGV crashes. + */ + void unload() noexcept; + private: + mutable std::mutex inference_mutex_; + std::vector> preprocess(std::span waveform) const; std::vector postprocess(const std::vector &scores, From 91581fba871ae3ba3faa33cb64ac5a30f9baf699 Mon Sep 17 00:00:00 2001 From: Bartosz Hanc Date: Tue, 7 Apr 2026 19:19:19 +0200 Subject: [PATCH 2/2] Update packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h --- .../voice_activity_detection/VoiceActivityDetection.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h b/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h index d2f2b1f9e6..c756bb6d3c 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h +++ b/packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/VoiceActivityDetection.h @@ -24,14 +24,6 @@ class VoiceActivityDetection : public BaseModel { [[nodiscard("Registered non-void function")]] std::vector generate(std::span waveform) const; - /** - * @brief Thread-safe unload that waits for any in-flight inference to - * complete. - * - * Mirrors VisionModel::unload(). Without this, BaseModel::unload() can - * destroy module_ while generate() is still calling forward() on a worker - * thread, causing SIGILL / SIGSEGV crashes. - */ void unload() noexcept; private: