Summary:
Because we use the `PromiseWrapper` struct, we need to explicitly manage its lifecycle to ensure that it doesn't clear before the promise methods are invoked by the ObjC Runtime. This `PromiseWrapper` struct is unnecessary. We could just not have it and create the CallbackWrappers for resolve and reject within the `createPromise` function. Therefore, I moved all the logic from `PromiseWrapper` to the `RCTTurboModule::createPromise` function.

In the next diff, I'm going to keep a track of all the CallbackWrappers we create in instances of RCTTurboModule, and `destroy()` them in the destructor of RCTTurboModule. This should make sure that all `jsi::Function`s are released before we delete the `jsi::Runtime`, which should prevent Marketplace from crashing when we hit CMD + R. See: https://fb.workplace.com/groups/rn.support/permalink/2761112713937326/.

Reviewed By: fkgozali

Differential Revision: D17208729

fbshipit-source-id: ce80c9c01088f0e3dc47c7c29397b7a197d699ce
This commit is contained in:
Ramanpreet Nara 2019-09-11 14:56:21 -07:00 коммит произвёл Facebook Github Bot
Родитель 1de8436c2c
Коммит fc9c53d621
2 изменённых файлов: 98 добавлений и 106 удалений

Просмотреть файл

@ -14,6 +14,7 @@
#import <React/RCTModuleMethod.h>
#import <ReactCommon/JSCallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import <ReactCommon/TurboModuleUtils.h>
#import <cxxreact/MessageQueueThread.h>
#import <string>
#import <unordered_map>
@ -69,6 +70,11 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule {
BOOL hasMethodArgConversionSelector(NSString *methodName, int argIndex);
SEL getMethodArgConversionSelector(NSString *methodName, int argIndex);
using PromiseInvocationBlock =
void (^)(jsi::Runtime &rt, RCTPromiseResolveBlock resolveWrapper, RCTPromiseRejectBlock rejectWrapper);
jsi::Value
createPromise(jsi::Runtime &runtime, std::shared_ptr<react::JSCallInvoker> jsInvoker, PromiseInvocationBlock invoke);
};
} // namespace react

Просмотреть файл

@ -192,95 +192,13 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback(
};
}
// Helper for creating Promise object.
struct PromiseWrapper : public react::LongLivedObject {
static std::shared_ptr<PromiseWrapper> create(
jsi::Function resolve,
jsi::Function reject,
namespace facebook {
namespace react {
jsi::Value ObjCTurboModule::createPromise(
jsi::Runtime &runtime,
std::shared_ptr<react::JSCallInvoker> jsInvoker)
{
auto instance = std::make_shared<PromiseWrapper>(std::move(resolve), std::move(reject), runtime, jsInvoker);
// This instance needs to live longer than the caller's scope, since the resolve/reject functions may not
// be called immediately. Doing so keeps it alive at least until resolve/reject is called, or when the
// collection is cleared (e.g. when JS reloads).
react::LongLivedObjectCollection::get().add(instance);
return instance;
}
PromiseWrapper(
jsi::Function resolve,
jsi::Function reject,
jsi::Runtime &runtime,
std::shared_ptr<react::JSCallInvoker> jsInvoker)
: resolveWrapper(std::make_shared<react::CallbackWrapper>(std::move(resolve), runtime, jsInvoker)),
rejectWrapper(std::make_shared<react::CallbackWrapper>(std::move(reject), runtime, jsInvoker)),
runtime(runtime),
jsInvoker(jsInvoker)
{
}
RCTPromiseResolveBlock resolveBlock()
{
return ^(id result) {
if (resolveWrapper == nullptr) {
throw std::runtime_error("Promise resolve arg cannot be called more than once");
}
// Retain the resolveWrapper so that it stays alive inside the lambda.
std::shared_ptr<react::CallbackWrapper> retainedWrapper = resolveWrapper;
jsInvoker->invokeAsync([retainedWrapper, result]() {
jsi::Runtime &rt = retainedWrapper->runtime();
jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
retainedWrapper->callback().call(rt, arg);
});
// Prevent future invocation of the same resolve() function.
cleanup();
};
}
RCTPromiseRejectBlock rejectBlock()
{
return ^(NSString *code, NSString *message, NSError *error) {
// TODO: There is a chance `this` is no longer valid when this block executes.
if (rejectWrapper == nullptr) {
throw std::runtime_error("Promise reject arg cannot be called more than once");
}
// Retain the resolveWrapper so that it stays alive inside the lambda.
std::shared_ptr<react::CallbackWrapper> retainedWrapper = rejectWrapper;
NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
jsInvoker->invokeAsync([retainedWrapper, jsError]() {
jsi::Runtime &rt = retainedWrapper->runtime();
jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError);
retainedWrapper->callback().call(rt, arg);
});
// Prevent future invocation of the same resolve() function.
cleanup();
};
}
void cleanup()
{
resolveWrapper = nullptr;
rejectWrapper = nullptr;
allowRelease();
}
// CallbackWrapper is used here instead of just holding on the jsi jsi::Function in order to force release it after
// either the resolve() or the reject() is called. jsi jsi::Function does not support explicit releasing, so we need
// an extra mechanism to control that lifecycle.
std::shared_ptr<react::CallbackWrapper> resolveWrapper;
std::shared_ptr<react::CallbackWrapper> rejectWrapper;
jsi::Runtime &runtime;
std::shared_ptr<react::JSCallInvoker> jsInvoker;
};
using PromiseInvocationBlock = void (^)(jsi::Runtime &rt, std::shared_ptr<PromiseWrapper> wrapper);
static jsi::Value
createPromise(jsi::Runtime &runtime, std::shared_ptr<react::JSCallInvoker> jsInvoker, PromiseInvocationBlock invoke)
std::shared_ptr<react::JSCallInvoker> jsInvoker,
PromiseInvocationBlock invoke)
{
if (!invoke) {
return jsi::Value::undefined();
@ -297,24 +215,91 @@ createPromise(jsi::Runtime &runtime, std::shared_ptr<react::JSCallInvoker> jsInv
2,
[invokeCopy, jsInvoker](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
if (count != 2) {
throw std::invalid_argument("Promise fn arg count must be 2");
throw std::invalid_argument(
"Promise must pass constructor function two args. Passed " + std::to_string(count) + " args.");
}
if (!invokeCopy) {
return jsi::Value::undefined();
}
jsi::Function resolve = args[0].getObject(rt).getFunction(rt);
jsi::Function reject = args[1].getObject(rt).getFunction(rt);
auto wrapper = PromiseWrapper::create(std::move(resolve), std::move(reject), rt, jsInvoker);
invokeCopy(rt, wrapper);
std::shared_ptr<CallbackWrapper> resolveWrapper =
std::make_shared<react::CallbackWrapper>(args[0].getObject(rt).getFunction(rt), rt, jsInvoker);
std::shared_ptr<CallbackWrapper> rejectWrapper =
std::make_shared<react::CallbackWrapper>(args[1].getObject(rt).getFunction(rt), rt, jsInvoker);
BOOL __block resolveWasCalled = NO;
BOOL __block rejectWasCalled = NO;
RCTPromiseResolveBlock resolveBlock = ^(id result) {
if (rejectWasCalled) {
throw std::runtime_error("Tried to resolve a promise after it's already been rejected.");
}
if (resolveWasCalled) {
throw std::runtime_error("Tried to resolve a promise more than once.");
}
// In the case that ObjC runtime first invokes this block after
// the TurboModuleManager was invalidated, we should do nothing.
if (resolveWrapper->isDestroyed()) {
return;
}
resolveWrapper->jsInvoker().invokeAsync([resolveWrapper, rejectWrapper, result]() {
if (resolveWrapper->isDestroyed()) {
return;
}
jsi::Runtime &rt = resolveWrapper->runtime();
jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
resolveWrapper->callback().call(rt, arg);
resolveWrapper->destroy();
rejectWrapper->destroy();
});
resolveWasCalled = YES;
};
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
if (resolveWasCalled) {
throw std::runtime_error("Tried to reject a promise after it's already been resolved.");
}
if (rejectWasCalled) {
throw std::runtime_error("Tried to reject a promise more than once.");
}
// In the case that ObjC runtime first invokes this block after
// the TurboModuleManager was invalidated, we should do nothing.
if (rejectWrapper->isDestroyed()) {
return;
}
NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
rejectWrapper->jsInvoker().invokeAsync([rejectWrapper, resolveWrapper, jsError]() {
if (rejectWrapper->isDestroyed()) {
return;
}
jsi::Runtime &rt = rejectWrapper->runtime();
jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError);
rejectWrapper->callback().call(rt, arg);
rejectWrapper->destroy();
resolveWrapper->destroy();
});
rejectWasCalled = YES;
};
invokeCopy(rt, resolveBlock, rejectBlock);
return jsi::Value::undefined();
});
return Promise.callAsConstructor(runtime, fn);
}
namespace facebook {
namespace react {
namespace {
/**
@ -624,9 +609,10 @@ jsi::Value ObjCTurboModule::invokeObjCMethod(
if (valueKind == PromiseKind) {
// Promise return type is special cased today, i.e. it needs extra 2 function args for resolve() and reject(), to
// be passed to the actual ObjC++ class method.
return createPromise(runtime, jsInvoker_, ^(jsi::Runtime &rt, std::shared_ptr<PromiseWrapper> wrapper) {
RCTPromiseResolveBlock resolveBlock = wrapper->resolveBlock();
RCTPromiseRejectBlock rejectBlock = wrapper->rejectBlock();
return createPromise(
runtime,
jsInvoker_,
^(jsi::Runtime &rt, RCTPromiseResolveBlock resolveBlock, RCTPromiseRejectBlock rejectBlock) {
[inv setArgument:(void *)&resolveBlock atIndex:count + 2];
[inv setArgument:(void *)&rejectBlock atIndex:count + 3];
[retainedObjectsForInvocation addObject:resolveBlock];