From f61becb656f84340c940e35985feb39970a6ff5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Mon, 27 Jul 2026 12:31:27 +0200 Subject: [PATCH 1/2] refactor: extract tokenizer try-lock helper Deduplicate the try-lock + liveness-check boilerplate repeated across the five TokenizerHostObject ops into a class-scoped tryLockUnique member, so callsites read self->tryLockUnique(rt, ctx) without lambdas or template parameters. Scoped to the tokenizer rather than shared across host objects to keep ergonomics simple and avoid coupling unrelated types. --- .../cpp/extensions/nlp/tokenizer.cpp | 57 ++++++------------- .../cpp/extensions/nlp/tokenizer.h | 8 +++ 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp index e17109e86f..01ece72339 100644 --- a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp +++ b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp @@ -67,6 +67,18 @@ TokenizerHostObject::TokenizerHostObject(std::string tokenizerPath) } } +std::unique_lock TokenizerHostObject::tryLockUnique(jsi::Runtime &rt, + std::string_view context) { + std::unique_lock lock(mutex_, std::try_to_lock); + if (!lock.owns_lock()) { + throw jsi::JSError(rt, std::format("{} is currently in use", context)); + } + if (!tokenizer_) { + throw jsi::JSError(rt, std::format("{} has been disposed", context)); + } + return lock; +} + jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { auto nameStr = name.utf8(rt); @@ -81,14 +93,7 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam throw jsi::JSError(rt, "encode: Usage: encode(text)"); } - std::unique_lock lock(self->mutex_, std::try_to_lock); - if (!lock.owns_lock()) { - throw jsi::JSError(rt, "encode: Tokenizer is currently in use"); - } - - if (!self->tokenizer_) { - throw jsi::JSError(rt, "encode: Tokenizer has been disposed"); - } + auto lock = self->tryLockUnique(rt, "encode: Tokenizer"); auto text = conversions::asType(rt, "encode: text", args[0]); auto tokens = unwrap(rt, "encode: Failed to encode input", @@ -112,14 +117,7 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam skipSpecialTokens = conversions::asType(rt, "decode: skipSpecialTokens", args[1]); } - std::unique_lock lock(self->mutex_, std::try_to_lock); - if (!lock.owns_lock()) { - throw jsi::JSError(rt, "decode: Tokenizer is currently in use"); - } - - if (!self->tokenizer_) { - throw jsi::JSError(rt, "decode: Tokenizer has been disposed"); - } + auto lock = self->tryLockUnique(rt, "decode: Tokenizer"); auto tokens = conversions::asVector(rt, "decode: tokens", args[0]); @@ -142,14 +140,7 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam throw jsi::JSError(rt, "getVocabSize: Usage: getVocabSize()"); } - std::unique_lock lock(self->mutex_, std::try_to_lock); - if (!lock.owns_lock()) { - throw jsi::JSError(rt, "getVocabSize: Tokenizer is currently in use"); - } - - if (!self->tokenizer_) { - throw jsi::JSError(rt, "getVocabSize: Tokenizer has been disposed"); - } + auto lock = self->tryLockUnique(rt, "getVocabSize: Tokenizer"); return static_cast(self->tokenizer_->vocab_size()); }; @@ -163,14 +154,7 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam throw jsi::JSError(rt, "idToToken: Usage: idToToken(id)"); } - std::unique_lock lock(self->mutex_, std::try_to_lock); - if (!lock.owns_lock()) { - throw jsi::JSError(rt, "idToToken: Tokenizer is currently in use"); - } - - if (!self->tokenizer_) { - throw jsi::JSError(rt, "idToToken: Tokenizer has been disposed"); - } + auto lock = self->tryLockUnique(rt, "idToToken: Tokenizer"); auto tokenId = conversions::asType(rt, "idToToken: id", args[0]); auto token = unwrap(rt, "idToToken: Failed to convert id to token", @@ -188,14 +172,7 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam throw jsi::JSError(rt, "tokenToId: Usage: tokenToId(token)"); } - std::unique_lock lock(self->mutex_, std::try_to_lock); - if (!lock.owns_lock()) { - throw jsi::JSError(rt, "tokenToId: Tokenizer is currently in use"); - } - - if (!self->tokenizer_) { - throw jsi::JSError(rt, "tokenToId: Tokenizer has been disposed"); - } + auto lock = self->tryLockUnique(rt, "tokenToId: Tokenizer"); auto token = conversions::asType(rt, "tokenToId: token", args[0]); auto tokenId = unwrap(rt, "tokenToId: Failed to convert token to id", diff --git a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h index aac5aa2c0c..2bd1ddc2aa 100644 --- a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h +++ b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,13 @@ class TokenizerHostObject : public facebook::jsi::HostObject, std::vector getPropertyNames(facebook::jsi::Runtime &rt) override; private: + // Non-blocking try-lock on mutex_ that also checks the tokenizer is still + // alive, throwing a facebook::jsi::JSError prefixed with `context` on + // contention ("... is currently in use") or after dispose ("... has been + // disposed"). + [[nodiscard]] std::unique_lock tryLockUnique(facebook::jsi::Runtime &rt, + std::string_view context); + std::string tokenizerPath_; std::unique_ptr tokenizer_; std::mutex mutex_; From 201056dc74e94067a612ffeb89b6a5d7813e64d5 Mon Sep 17 00:00:00 2001 From: Mateusz Sluszniak <56299341+msluszniak@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:16:40 +0200 Subject: [PATCH 2/2] Apply suggestion from @msluszniak --- .../react-native-executorch/cpp/extensions/nlp/tokenizer.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h index 2bd1ddc2aa..77a4b9d638 100644 --- a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h +++ b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.h @@ -21,10 +21,6 @@ class TokenizerHostObject : public facebook::jsi::HostObject, std::vector getPropertyNames(facebook::jsi::Runtime &rt) override; private: - // Non-blocking try-lock on mutex_ that also checks the tokenizer is still - // alive, throwing a facebook::jsi::JSError prefixed with `context` on - // contention ("... is currently in use") or after dispose ("... has been - // disposed"). [[nodiscard]] std::unique_lock tryLockUnique(facebook::jsi::Runtime &rt, std::string_view context);