Skip to content

Commit 5d1d3bf

Browse files
committed
fix(🐛): prevent EXC_BAD_ACCESS in Promise destructor after runtime teardown
Replace the memory-leak workaround (heap-leaking JSI Function pointers) with proper lifecycle management via RuntimeLifecycleMonitor. The Promise now registers as a listener and releases its JSI objects in onRuntimeDestroyed while the runtime is still valid. - Register/unregister with RuntimeLifecycleMonitor in constructor/destructor - Guard resolve/reject with mutex and runtimeDestroyed_ flag - Release JSI Functions in onRuntimeDestroyed before the runtime is torn down - Call removeListener outside the lock to keep locking hierarchy simple - Add debug-only logging for dropped resolve/reject calls during teardown
1 parent c010bb9 commit 5d1d3bf

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

packages/skia/cpp/jsi/JsiPromises.cpp

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,65 @@
11
#include "JsiPromises.h"
22

3+
#ifndef NDEBUG
4+
#include "utils/RNSkLog.h"
5+
#endif
6+
37
namespace RNJsi {
48

59
JsiPromises::Promise::Promise(jsi::Runtime &rt, jsi::Function resolve,
610
jsi::Function reject)
7-
: runtime_(rt), resolve_(std::move(resolve)), reject_(std::move(reject)) {}
11+
: runtime_(rt), resolve_(std::move(resolve)), reject_(std::move(reject)) {
12+
RuntimeLifecycleMonitor::addListener(rt, this);
13+
}
14+
15+
JsiPromises::Promise::~Promise() {
16+
bool shouldRemove = false;
17+
{
18+
std::lock_guard<std::mutex> lock(mutex_);
19+
shouldRemove = !runtimeDestroyed_;
20+
}
21+
// Call removeListener outside the lock to avoid holding mutex_ across
22+
// an external call. This keeps the locking hierarchy simple and avoids
23+
// potential issues if RuntimeLifecycleMonitor's internals change.
24+
if (shouldRemove) {
25+
RuntimeLifecycleMonitor::removeListener(runtime_, this);
26+
}
27+
}
28+
29+
void JsiPromises::Promise::onRuntimeDestroyed(jsi::Runtime *) {
30+
std::lock_guard<std::mutex> lock(mutex_);
31+
// Release JSI Function objects now while the runtime is still alive
32+
// enough to handle invalidation. After a move, the source jsi::Function
33+
// is in a valid but empty state, making its eventual destruction in
34+
// ~Promise() a safe no-op.
35+
runtimeDestroyed_ = true;
36+
{
37+
jsi::Function r(std::move(resolve_));
38+
jsi::Function j(std::move(reject_));
39+
}
40+
}
841

942
void JsiPromises::Promise::resolve(const jsi::Value &result) {
43+
std::lock_guard<std::mutex> lock(mutex_);
44+
if (runtimeDestroyed_) {
45+
#ifndef NDEBUG
46+
RNSkia::RNSkLogger::logToConsole(
47+
"Promise::resolve() dropped — runtime already torn down");
48+
#endif
49+
return;
50+
}
1051
resolve_.call(runtime_, result);
1152
}
1253

1354
void JsiPromises::Promise::reject(const std::string &message) {
55+
std::lock_guard<std::mutex> lock(mutex_);
56+
if (runtimeDestroyed_) {
57+
#ifndef NDEBUG
58+
RNSkia::RNSkLogger::logToConsole(
59+
"Promise::reject() dropped — runtime already torn down");
60+
#endif
61+
return;
62+
}
1463
jsi::Object error(runtime_);
1564
error.setProperty(runtime_, "message",
1665
jsi::String::createFromUtf8(runtime_, message));

packages/skia/cpp/jsi/JsiPromises.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#pragma once
22

33
#include <memory>
4+
#include <mutex>
45
#include <string>
56
#include <utility>
67

78
#include <jsi/jsi.h>
89

10+
#include "RuntimeLifecycleMonitor.h"
911
#include "third_party/base64.h"
1012

1113
namespace RNJsi {
@@ -29,15 +31,20 @@ class LongLivedObject {
2931

3032
class JsiPromises {
3133
public:
32-
struct Promise : public LongLivedObject {
34+
struct Promise : public LongLivedObject, public RuntimeLifecycleListener {
3335
Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject);
36+
~Promise();
37+
38+
void onRuntimeDestroyed(jsi::Runtime *) override;
3439

3540
void resolve(const jsi::Value &result);
3641
void reject(const std::string &error);
3742

3843
jsi::Runtime &runtime_;
44+
std::mutex mutex_;
3945
jsi::Function resolve_;
4046
jsi::Function reject_;
47+
bool runtimeDestroyed_{false};
4148
};
4249

4350
using PromiseSetupFunctionType =

0 commit comments

Comments
 (0)