Skip to content

Commit 3648886

Browse files
javachefacebook-github-bot
authored andcommitted
Use AsyncCallback in RCTTurboModule (re-land) (#41049)
Summary: Pull Request resolved: #41049 Similarly to D50319914, simplify the careful logic we have with CallbackWrapper and RCTBlockGuard and instead rely on bridging's `AsyncCallback` so safely handle jsi::Function for us. The underlying issue causing memory corruption has been addressed in D50286876. Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D50319913 fbshipit-source-id: e422518b9a647b7daa0b75eae529a8b04ce1c22b
1 parent 8ac47fa commit 3648886

File tree

1 file changed

+93
-165
lines changed
  • packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon

1 file changed

+93
-165
lines changed

packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm

Lines changed: 93 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,6 @@
66
*/
77

88
#import "RCTTurboModule.h"
9-
#import "RCTBlockGuard.h"
10-
11-
#import <cxxreact/SystraceSection.h>
12-
#import <glog/logging.h>
13-
#import <objc/message.h>
14-
#import <objc/runtime.h>
15-
#import <atomic>
16-
#import <iostream>
17-
#import <sstream>
18-
#import <vector>
199

2010
#import <React/RCTBridgeModule.h>
2111
#import <React/RCTConvert.h>
@@ -27,7 +17,17 @@
2717
#import <ReactCommon/LongLivedObject.h>
2818
#import <ReactCommon/TurboModule.h>
2919
#import <ReactCommon/TurboModulePerfLogger.h>
30-
#import <ReactCommon/TurboModuleUtils.h>
20+
#import <cxxreact/SystraceSection.h>
21+
#import <react/bridging/Bridging.h>
22+
23+
#include <glog/logging.h>
24+
25+
#import <objc/message.h>
26+
#import <objc/runtime.h>
27+
#import <atomic>
28+
#import <iostream>
29+
#import <sstream>
30+
#import <vector>
3131

3232
using namespace facebook;
3333
using namespace facebook::react;
@@ -143,7 +143,23 @@ static int32_t getUniqueId()
143143
}
144144

145145
static RCTResponseSenderBlock
146-
convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr<CallInvoker> jsInvoker);
146+
convertJSIFunctionToCallback(jsi::Runtime &rt, jsi::Function &&function, std::shared_ptr<CallInvoker> jsInvoker)
147+
{
148+
__block std::optional<AsyncCallback<>> callback({rt, std::move(function), std::move(jsInvoker)});
149+
return ^(NSArray *args) {
150+
if (!callback) {
151+
LOG(FATAL) << "Callback arg cannot be called more than once";
152+
return;
153+
}
154+
155+
callback->call([args](jsi::Runtime &rt, jsi::Function &jsFunction) {
156+
auto jsArgs = convertNSArrayToStdVector(rt, args);
157+
jsFunction.call(rt, (const jsi::Value *)jsArgs.data(), jsArgs.size());
158+
});
159+
callback = std::nullopt;
160+
};
161+
}
162+
147163
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker)
148164
{
149165
if (value.isUndefined() || value.isNull()) {
@@ -164,56 +180,14 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s
164180
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
165181
}
166182
if (o.isFunction(runtime)) {
167-
return convertJSIFunctionToCallback(runtime, std::move(o.getFunction(runtime)), jsInvoker);
183+
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
168184
}
169185
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
170186
}
171187

172188
throw std::runtime_error("Unsupported jsi::jsi::Value kind");
173189
}
174190

175-
static RCTResponseSenderBlock
176-
convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr<CallInvoker> jsInvoker)
177-
{
178-
auto weakWrapper = CallbackWrapper::createWeak(value.getFunction(runtime), runtime, jsInvoker);
179-
RCTBlockGuard *blockGuard = [[RCTBlockGuard alloc] initWithCleanup:^() {
180-
auto strongWrapper = weakWrapper.lock();
181-
if (strongWrapper) {
182-
strongWrapper->destroy();
183-
}
184-
}];
185-
186-
BOOL __block wrapperWasCalled = NO;
187-
RCTResponseSenderBlock callback = ^(NSArray *responses) {
188-
if (wrapperWasCalled) {
189-
LOG(FATAL) << "callback arg cannot be called more than once";
190-
}
191-
192-
auto strongWrapper = weakWrapper.lock();
193-
if (!strongWrapper) {
194-
return;
195-
}
196-
197-
strongWrapper->jsInvoker().invokeAsync([weakWrapper, responses, blockGuard]() {
198-
auto strongWrapper2 = weakWrapper.lock();
199-
if (!strongWrapper2) {
200-
return;
201-
}
202-
203-
std::vector<jsi::Value> args = convertNSArrayToStdVector(strongWrapper2->runtime(), responses);
204-
strongWrapper2->callback().call(strongWrapper2->runtime(), (const jsi::Value *)args.data(), args.size());
205-
strongWrapper2->destroy();
206-
207-
// Delete the CallbackWrapper when the block gets dealloced without being invoked.
208-
(void)blockGuard;
209-
});
210-
211-
wrapperWasCalled = YES;
212-
};
213-
214-
return [callback copy];
215-
}
216-
217191
static jsi::Value createJSRuntimeError(jsi::Runtime &runtime, const std::string &message)
218192
{
219193
return runtime.global().getPropertyAsFunction(runtime, "Error").call(runtime, message);
@@ -247,124 +221,78 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s
247221
}
248222

249223
jsi::Function Promise = runtime.global().getPropertyAsFunction(runtime, "Promise");
250-
std::string moduleName = name_;
251224

252225
// Note: the passed invoke() block is not retained by default, so let's retain it here to help keep it longer.
253226
// Otherwise, there's a risk of it getting released before the promise function below executes.
254227
PromiseInvocationBlock invokeCopy = [invoke copy];
255-
jsi::Function fn = jsi::Function::createFromHostFunction(
228+
return Promise.callAsConstructor(
256229
runtime,
257-
jsi::PropNameID::forAscii(runtime, "fn"),
258-
2,
259-
[invokeCopy, jsInvoker = jsInvoker_, moduleName, methodName](
260-
jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
261-
std::string moduleMethod = moduleName + "." + methodName + "()";
262-
263-
if (count != 2) {
264-
throw std::invalid_argument(
265-
moduleMethod + ": Promise must pass constructor function two args. Passed " + std::to_string(count) +
266-
" args.");
267-
}
268-
if (!invokeCopy) {
269-
return jsi::Value::undefined();
270-
}
271-
272-
auto weakResolveWrapper = CallbackWrapper::createWeak(args[0].getObject(rt).getFunction(rt), rt, jsInvoker);
273-
auto weakRejectWrapper = CallbackWrapper::createWeak(args[1].getObject(rt).getFunction(rt), rt, jsInvoker);
274-
275-
__block BOOL resolveWasCalled = NO;
276-
__block BOOL rejectWasCalled = NO;
277-
278-
RCTBlockGuard *blockGuard = [[RCTBlockGuard alloc] initWithCleanup:^() {
279-
auto strongResolveWrapper = weakResolveWrapper.lock();
280-
if (strongResolveWrapper) {
281-
strongResolveWrapper->destroy();
282-
}
283-
284-
auto strongRejectWrapper = weakRejectWrapper.lock();
285-
if (strongRejectWrapper) {
286-
strongRejectWrapper->destroy();
287-
}
288-
}];
289-
290-
RCTPromiseResolveBlock resolveBlock = ^(id result) {
291-
if (rejectWasCalled) {
292-
RCTLogError(@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
293-
return;
294-
}
295-
296-
if (resolveWasCalled) {
297-
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
298-
return;
299-
}
300-
301-
auto strongResolveWrapper = weakResolveWrapper.lock();
302-
auto strongRejectWrapper = weakRejectWrapper.lock();
303-
if (!strongResolveWrapper || !strongRejectWrapper) {
304-
return;
305-
}
306-
307-
strongResolveWrapper->jsInvoker().invokeAsync([weakResolveWrapper, weakRejectWrapper, result, blockGuard]() {
308-
auto strongResolveWrapper2 = weakResolveWrapper.lock();
309-
auto strongRejectWrapper2 = weakRejectWrapper.lock();
310-
if (!strongResolveWrapper2 || !strongRejectWrapper2) {
311-
return;
230+
jsi::Function::createFromHostFunction(
231+
runtime,
232+
jsi::PropNameID::forAscii(runtime, "fn"),
233+
2,
234+
[invokeCopy, jsInvoker = jsInvoker_, moduleName = name_, methodName](
235+
jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
236+
std::string moduleMethod = moduleName + "." + methodName + "()";
237+
238+
if (count != 2) {
239+
throw std::invalid_argument(
240+
moduleMethod + ": Promise must pass constructor function two args. Passed " + std::to_string(count) +
241+
" args.");
312242
}
313-
314-
jsi::Runtime &rt = strongResolveWrapper2->runtime();
315-
jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
316-
strongResolveWrapper2->callback().call(rt, arg);
317-
318-
strongResolveWrapper2->destroy();
319-
strongRejectWrapper2->destroy();
320-
(void)blockGuard;
321-
});
322-
323-
resolveWasCalled = YES;
324-
};
325-
326-
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
327-
if (resolveWasCalled) {
328-
RCTLogError(@"%s: Tried to reject a promise after it's already been resolved.", moduleMethod.c_str());
329-
return;
330-
}
331-
332-
if (rejectWasCalled) {
333-
RCTLogError(@"%s: Tried to reject a promise more than once.", moduleMethod.c_str());
334-
return;
335-
}
336-
337-
auto strongResolveWrapper = weakResolveWrapper.lock();
338-
auto strongRejectWrapper = weakRejectWrapper.lock();
339-
if (!strongResolveWrapper || !strongRejectWrapper) {
340-
return;
341-
}
342-
343-
NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
344-
strongRejectWrapper->jsInvoker().invokeAsync([weakResolveWrapper, weakRejectWrapper, jsError, blockGuard]() {
345-
auto strongResolveWrapper2 = weakResolveWrapper.lock();
346-
auto strongRejectWrapper2 = weakRejectWrapper.lock();
347-
if (!strongResolveWrapper2 || !strongRejectWrapper2) {
348-
return;
243+
if (!invokeCopy) {
244+
return jsi::Value::undefined();
349245
}
350246

351-
jsi::Runtime &rt = strongRejectWrapper2->runtime();
352-
jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError);
353-
strongRejectWrapper2->callback().call(rt, arg);
354-
355-
strongResolveWrapper2->destroy();
356-
strongRejectWrapper2->destroy();
357-
(void)blockGuard;
358-
});
359-
360-
rejectWasCalled = YES;
361-
};
362-
363-
invokeCopy(resolveBlock, rejectBlock);
364-
return jsi::Value::undefined();
365-
});
366-
367-
return Promise.callAsConstructor(runtime, fn);
247+
__block BOOL resolveWasCalled = NO;
248+
__block std::optional<AsyncCallback<>> resolve(
249+
{rt, args[0].getObject(rt).getFunction(rt), std::move(jsInvoker)});
250+
__block std::optional<AsyncCallback<>> reject(
251+
{rt, args[1].getObject(rt).getFunction(rt), std::move(jsInvoker)});
252+
253+
RCTPromiseResolveBlock resolveBlock = ^(id result) {
254+
if (!resolve || !reject) {
255+
if (resolveWasCalled) {
256+
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
257+
} else {
258+
RCTLogError(
259+
@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
260+
}
261+
return;
262+
}
263+
264+
resolve->call([result](jsi::Runtime &rt, jsi::Function &jsFunction) {
265+
jsFunction.call(rt, convertObjCObjectToJSIValue(rt, result));
266+
});
267+
268+
resolveWasCalled = YES;
269+
resolve = std::nullopt;
270+
reject = std::nullopt;
271+
};
272+
273+
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
274+
if (!resolve || !reject) {
275+
if (resolveWasCalled) {
276+
RCTLogError(@"%s: Tried to reject a promise after it's already been resolved.", moduleMethod.c_str());
277+
} else {
278+
RCTLogError(@"%s: Tried to reject a promise more than once.", moduleMethod.c_str());
279+
}
280+
return;
281+
}
282+
283+
NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
284+
reject->call([jsError](jsi::Runtime &rt, jsi::Function &jsFunction) {
285+
jsFunction.call(rt, convertObjCObjectToJSIValue(rt, jsError));
286+
});
287+
288+
resolveWasCalled = NO;
289+
resolve = std::nullopt;
290+
reject = std::nullopt;
291+
};
292+
293+
invokeCopy(resolveBlock, rejectBlock);
294+
return jsi::Value::undefined();
295+
}));
368296
}
369297

370298
/**

0 commit comments

Comments
 (0)