@@ -106,7 +106,9 @@ template <typename R, typename... Args>
106106class SyncCallback <R(Args...)> {
107107 public:
108108 SyncCallback (jsi::Runtime &rt, jsi::Function function, std::shared_ptr<CallInvoker> jsInvoker)
109- : wrapper_(CallbackWrapper::createWeak(std::move(function), rt, std::move(jsInvoker)))
109+ : wrapper_(CallbackWrapper::createWeak(std::move(function), rt, std::move(jsInvoker))),
110+ collection_ (&LongLivedObjectCollection::get (rt)),
111+ wrapperPtr_(wrapper_.lock().get())
110112 {
111113 }
112114
@@ -116,18 +118,30 @@ class SyncCallback<R(Args...)> {
116118 SyncCallback &operator =(const SyncCallback &) = delete;
117119
118120 // Allow move
119- SyncCallback (SyncCallback &&other) noexcept : wrapper_(std::move(other.wrapper_)) {}
121+ SyncCallback (SyncCallback &&other) noexcept
122+ : wrapper_(std::move(other.wrapper_)),
123+ collection_(std::exchange(other.collection_, nullptr )),
124+ wrapperPtr_(std::exchange(other.wrapperPtr_, nullptr ))
125+ {
126+ }
120127
121128 SyncCallback &operator =(SyncCallback &&other) noexcept
122129 {
123130 wrapper_ = std::move (other.wrapper_ );
131+ collection_ = std::exchange (other.collection_ , nullptr );
132+ wrapperPtr_ = std::exchange (other.wrapperPtr_ , nullptr );
124133 return *this ;
125134 }
126135
127136 ~SyncCallback ()
128137 {
129- if (auto wrapper = wrapper_.lock ()) {
130- wrapper->destroy ();
138+ // May run on any thread (e.g. the fbjni destructor thread). Locking wrapper_
139+ // here could leave this thread with the last reference outside the
140+ // collection's lock, racing with clear() during runtime teardown. Remove by
141+ // address instead, so any release happens under that lock. expired() skips
142+ // wrappers that clear() already released, whose address may be reused.
143+ if (collection_ != nullptr && !wrapper_.expired ()) {
144+ collection_->remove (wrapperPtr_);
131145 }
132146 }
133147
@@ -179,6 +193,11 @@ class SyncCallback<R(Args...)> {
179193
180194 // Held weakly so lifetime is managed by LongLivedObjectCollection.
181195 std::weak_ptr<CallbackWrapper> wrapper_;
196+ // Used by the destructor to release the wrapper without locking wrapper_.
197+ // The collection outlives the runtime: LongLivedObjectCollection::get() never
198+ // erases its entries.
199+ LongLivedObjectCollection *collection_;
200+ const LongLivedObject *wrapperPtr_;
182201};
183202
184203template <typename ... Args>
0 commit comments