Skip to content

Commit 760c848

Browse files
committed
feat: add promise rejection handling for v8
1 parent c75eb73 commit 760c848

File tree

2 files changed

+124
-17
lines changed

2 files changed

+124
-17
lines changed

test-app/app/src/main/assets/internal/ts_helpers.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,4 +390,77 @@
390390
},
391391
});
392392
}
393+
394+
const pendingUnhandledRejections = [];
395+
const hasBeenNotifiedProperty = new WeakMap();
396+
function emitPendingUnhandledRejections() {
397+
while (pendingUnhandledRejections.length > 0) {
398+
var promise = pendingUnhandledRejections.shift();
399+
var reason = pendingUnhandledRejections.shift();
400+
if (
401+
hasBeenNotifiedProperty.get(promise) === false &&
402+
globalThis.__onUncaughtError
403+
) {
404+
globalThis.__onUncaughtError(reason);
405+
}
406+
}
407+
}
408+
409+
function unhandledPromise(promise, reason) {
410+
pendingUnhandledRejections.push(promise, reason);
411+
__ns__setTimeout(() => {
412+
emitPendingUnhandledRejections();
413+
}, 1);
414+
}
415+
416+
function handledPromise(promise) {
417+
const hasBeenNotified = hasBeenNotifiedProperty.get(promise);
418+
if (hasBeenNotified != undefined) {
419+
hasBeenNotifiedProperty.delete(promise);
420+
}
421+
}
422+
423+
if (globalThis.__engine === "V8") {
424+
// Only report errors for promise rejections that go unhandled.
425+
globalThis.__v8UnhandledPromiseRejectionTracker = (
426+
event,
427+
promise,
428+
reason
429+
) => {
430+
if (event === globalThis.__promiseUnhandledEvent) {
431+
hasBeenNotifiedProperty.set(promise, false);
432+
const error = new Error(reason, {
433+
cause: reason,
434+
});
435+
error.name = "Unhandled promise rejection";
436+
unhandledPromise(promise, error);
437+
} else {
438+
handledPromise(promise);
439+
}
440+
};
441+
} else if (globalThis.__engine === "QuickJS") {
442+
globalThis.onUnhandledPromiseRejectionTracker = (
443+
promise,
444+
reason,
445+
isHandled
446+
) => {
447+
448+
if (!isHandled) {
449+
hasBeenNotifiedProperty.set(promise, false);
450+
const error = new Error(reason, {
451+
cause: reason,
452+
});
453+
error.name = "Unhandled promise rejection";
454+
// Preserve original stack trace.
455+
if (!promise.then["[[stack]]"]) {
456+
promise.then["[[stack]]"] = error.stack;
457+
} else {
458+
error.stack = promise.then["[[stack]]"];
459+
}
460+
unhandledPromise(promise, error);
461+
} else {
462+
handledPromise(promise);
463+
}
464+
};
465+
}
393466
})();

test-app/runtime/src/main/cpp/napi/v8/jsr.cpp

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
#include <utime.h>
88
#include "v8-fast-api-calls.h"
99
#include "NativeScriptAssert.h"
10+
#include "native_api_util.h"
1011

1112
using namespace v8;
1213
using namespace tns;
1314

1415
tns::SimpleAllocator g_allocator;
15-
JSR::JSR(): isolate(nullptr) {
16+
17+
JSR::JSR() : isolate(nullptr) {
1618
v8::Isolate::CreateParams create_params;
1719
create_params.array_buffer_allocator = &g_allocator;
1820

@@ -24,9 +26,10 @@ JSR::JSR(): isolate(nullptr) {
2426
}
2527
isolate = v8::Isolate::New(create_params);
2628
}
29+
2730
std::unique_ptr<v8::Platform> JSR::platform = nullptr;
2831
bool JSR::s_mainThreadInitialized = false;
29-
std::unordered_map<napi_env, JSR*> JSR::env_to_jsr_cache;
32+
std::unordered_map<napi_env, JSR *> JSR::env_to_jsr_cache;
3033

3134
napi_status js_create_runtime(napi_runtime *runtime) {
3235
if (!runtime) return napi_invalid_arg;
@@ -35,7 +38,7 @@ napi_status js_create_runtime(napi_runtime *runtime) {
3538
return napi_ok;
3639
}
3740

38-
napi_status js_set_runtime_flags(const char* flags) {
41+
napi_status js_set_runtime_flags(const char *flags) {
3942
V8::V8::SetFlagsFromString(flags);
4043
return napi_ok;
4144
}
@@ -62,9 +65,9 @@ napi_status js_unlock_env(napi_env env) {
6265
return napi_ok;
6366
}
6467

65-
napi_status js_create_napi_env(napi_env* env, napi_runtime runtime) {
68+
napi_status js_create_napi_env(napi_env *env, napi_runtime runtime) {
6669
if (env == nullptr) return napi_invalid_arg;
67-
JSR* jsr = (JSR*) runtime;
70+
JSR *jsr = (JSR *) runtime;
6871
// Must enter explictly
6972
#ifdef __V8_13__
7073
jsr->isolate->Enter();
@@ -74,6 +77,32 @@ napi_status js_create_napi_env(napi_env* env, napi_runtime runtime) {
7477
v8::Local<v8::Context> context = v8::Context::New(jsr->isolate);
7578
*env = new napi_env__(context, NAPI_VERSION_EXPERIMENTAL);
7679
JSR::env_to_jsr_cache.insert(std::make_pair(*env, jsr));
80+
81+
Local<Object> global = context->Global();
82+
global->Set(context,
83+
String::NewFromUtf8(jsr->isolate, "__promiseUnhandledEvent").ToLocalChecked(),
84+
Integer::New(jsr->isolate, PromiseRejectEvent::kPromiseRejectWithNoHandler));
85+
global->Set(context,
86+
String::NewFromUtf8(jsr->isolate, "__promiseHandledEvent").ToLocalChecked(),
87+
Integer::New(jsr->isolate,
88+
PromiseRejectEvent::kPromiseHandlerAddedAfterReject));
89+
90+
jsr->isolate->SetPromiseRejectCallback([](PromiseRejectMessage message) {
91+
Local<Promise> promise = message.GetPromise();
92+
Isolate *isolate = promise->GetIsolate();
93+
Local<Value> value = message.GetValue();
94+
Local<Integer> event = Integer::New(isolate, message.GetEvent());
95+
v8::HandleScope handle_scope(isolate);
96+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
97+
Local<Object> global = context->Global();
98+
Local<Value> callback = global->Get(context, String::NewFromUtf8(isolate,
99+
"__v8UnhandledPromiseRejectionTracker").ToLocalChecked()).ToLocalChecked();
100+
if (value.IsEmpty())
101+
value = Undefined(isolate);
102+
Local<Value> args[] = {event, promise, value};
103+
callback.As<Function>()->Call(context, global, 3, args);
104+
});
105+
77106
// Must exit explictly
78107
#ifdef __V8_13__
79108
jsr->isolate->Exit();
@@ -90,11 +119,11 @@ napi_status js_create_napi_env(napi_env* env, napi_runtime runtime) {
90119
napi_status js_free_napi_env(napi_env env) {
91120
if (env == nullptr) return napi_invalid_arg;
92121
env->DeleteMe();
93-
return napi_ok;
122+
return napi_ok;
94123
}
95124

96125
napi_status js_free_runtime(napi_runtime runtime) {
97-
JSR* jsr = (JSR*) runtime;
126+
JSR *jsr = (JSR *) runtime;
98127
jsr->isolate->Dispose();
99128
delete jsr;
100129
return napi_ok;
@@ -118,23 +147,26 @@ napi_status js_get_engine_ptr(napi_env env, int64_t *engine_ptr) {
118147
return napi_ok;
119148
}
120149

121-
napi_status js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64_t* externalMemory) {
150+
napi_status
151+
js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64_t *externalMemory) {
122152
*externalMemory = env->isolate->AdjustAmountOfExternalAllocatedMemory(changeInBytes);
123153
return napi_ok;
124154
}
125155

126156
napi_status js_cache_script(napi_env env, const char *source, const char *file) {
127-
v8::Local<v8::String> sourceString = v8::String::NewFromUtf8(env->isolate, source).ToLocalChecked();
157+
v8::Local<v8::String> sourceString = v8::String::NewFromUtf8(env->isolate,
158+
source).ToLocalChecked();
128159
v8::Local<v8::String> fileString = v8::String::NewFromUtf8(env->isolate, file).ToLocalChecked();
129160
#ifdef __V8_13__
130161
v8::ScriptOrigin origin(fileString);
131162
#else
132-
v8::ScriptOrigin origin(env->isolate,fileString);
163+
v8::ScriptOrigin origin(env->isolate, fileString);
133164
#endif
134-
v8::Local<v8::Script> script = v8::Script::Compile(env->context(),sourceString, &origin).ToLocalChecked();
165+
v8::Local<v8::Script> script = v8::Script::Compile(env->context(), sourceString,
166+
&origin).ToLocalChecked();
135167

136168
Local<UnboundScript> unboundScript = script->GetUnboundScript();
137-
ScriptCompiler::CachedData* cachedData = ScriptCompiler::CreateCodeCache(unboundScript);
169+
ScriptCompiler::CachedData *cachedData = ScriptCompiler::CreateCodeCache(unboundScript);
138170

139171
int length = cachedData->length;
140172
auto cachePath = std::string(file) + ".cache";
@@ -154,7 +186,8 @@ napi_status js_cache_script(napi_env env, const char *source, const char *file)
154186
return napi_ok;
155187
}
156188

157-
napi_status js_run_cached_script(napi_env env, const char * file, napi_value script, void* cache, napi_value *result) {
189+
napi_status js_run_cached_script(napi_env env, const char *file, napi_value script, void *cache,
190+
napi_value *result) {
158191
auto cachePath = std::string(file) + ".cache";
159192
struct stat s_result;
160193
if (stat(cachePath.c_str(), &s_result) == 0) {
@@ -175,19 +208,20 @@ napi_status js_run_cached_script(napi_env env, const char * file, napi_value scr
175208
return napi_cannot_run_js;
176209
}
177210

178-
auto * cacheData = new ScriptCompiler::CachedData(reinterpret_cast<uint8_t*>(data), length, ScriptCompiler::CachedData::BufferOwned);
211+
auto *cacheData = new ScriptCompiler::CachedData(reinterpret_cast<uint8_t *>(data), length,
212+
ScriptCompiler::CachedData::BufferOwned);
179213
std::string filePath = std::string("file://") + file;
180214

181215
auto fullRequiredModulePathWithSchema = v8::String::NewFromUtf8(env->isolate, filePath.c_str());
182216

183217
#ifdef __V8_13__
184218
v8::ScriptOrigin origin(fullRequiredModulePathWithSchema.ToLocalChecked());
185219
#else
186-
v8::ScriptOrigin origin(env->isolate,fullRequiredModulePathWithSchema.ToLocalChecked());
220+
v8::ScriptOrigin origin(env->isolate, fullRequiredModulePathWithSchema.ToLocalChecked());
187221
#endif
188222

189223
v8::Local<v8::String> scriptText;
190-
memcpy(static_cast<void*>(&scriptText), &script, sizeof(script));
224+
memcpy(static_cast<void *>(&scriptText), &script, sizeof(script));
191225

192226
TryCatch tc(env->isolate);
193227

@@ -206,7 +240,7 @@ napi_status js_run_cached_script(napi_env env, const char * file, napi_value scr
206240
return napi_ok;
207241
}
208242

209-
napi_status js_get_runtime_version(napi_env env, napi_value* version) {
243+
napi_status js_get_runtime_version(napi_env env, napi_value *version) {
210244

211245
napi_create_string_utf8(env, "V8", NAPI_AUTO_LENGTH, version);
212246

0 commit comments

Comments
 (0)