Skip to content

Commit 317f5e9

Browse files
committed
feat: add jsEnter state to quickjs/primjs jsr
1 parent 19db909 commit 317f5e9

3 files changed

Lines changed: 53 additions & 32 deletions

File tree

test-app/runtime/src/main/cpp/napi/primjs/jsr.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ class JSR {
1414
public:
1515
JSR();
1616
std::recursive_mutex js_mutex;
17+
// Depth of nested JS scopes entered from the host (see NapiScope). We drain
18+
// the pending-job (microtask) queue once this returns to 0, i.e. when the
19+
// native call stack has fully unwound back out of JS.
20+
int jsEnterState = 0;
1721
void lock() {
1822
js_mutex.lock();
1923
}
@@ -31,6 +35,10 @@ class NapiScope {
3135
{
3236
js_lock_env(env_);
3337
// TODO: UPDATE STACK TOP HERE
38+
jsr_ = JSR::env_to_jsr_cache.Get(env_);
39+
if (jsr_) {
40+
jsr_->jsEnterState++;
41+
}
3442
if (open_handle) {
3543
napi_open_handle_scope(env_, &napiHandleScope_);
3644
} else {
@@ -39,6 +47,18 @@ class NapiScope {
3947
}
4048

4149
~NapiScope() {
50+
// Drain the microtask queue only when the outermost JS scope unwinds so
51+
// that promise continuations (async/await) run — mirroring how a JS
52+
// engine empties its job queue once control returns to the host.
53+
// Draining at a nested depth would run continuations while JS is still
54+
// on the stack. A throwing job must never escape a destructor.
55+
if (jsr_ && --jsr_->jsEnterState <= 0) {
56+
jsr_->jsEnterState = 0;
57+
try {
58+
js_execute_pending_jobs(env_);
59+
} catch (...) {
60+
}
61+
}
4262
if (napiHandleScope_) {
4363
napi_close_handle_scope(env_, napiHandleScope_);
4464
}
@@ -48,6 +68,7 @@ class NapiScope {
4868
private:
4969
napi_env env_;
5070
napi_handle_scope napiHandleScope_;
71+
JSR* jsr_ = nullptr;
5172
};
5273

5374
#define JSEnterScope

test-app/runtime/src/main/cpp/napi/quickjs/jsr.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ class JSR {
1515
public:
1616
JSR();
1717
std::recursive_mutex js_mutex;
18+
// Depth of nested JS scopes entered from the host (see NapiScope). We drain
19+
// the pending-job (microtask) queue once this returns to 0, i.e. when the
20+
// native call stack has fully unwound back out of JS.
21+
int jsEnterState = 0;
1822
void lock() {
1923
js_mutex.lock();
2024
}
@@ -32,6 +36,10 @@ class NapiScope {
3236
{
3337
js_lock_env(env_);
3438
qjs_update_stack_top(env);
39+
jsr_ = JSR::env_to_jsr_cache.Get(env_);
40+
if (jsr_) {
41+
jsr_->jsEnterState++;
42+
}
3543
if (open_handle) {
3644
napi_open_handle_scope(env_, &napiHandleScope_);
3745
} else {
@@ -40,6 +48,18 @@ class NapiScope {
4048
}
4149

4250
~NapiScope() {
51+
// Drain the microtask queue only when the outermost JS scope unwinds so
52+
// that promise continuations (async/await) run — mirroring how a JS
53+
// engine empties its job queue once control returns to the host.
54+
// Draining at a nested depth would run continuations while JS is still
55+
// on the stack. A throwing job must never escape a destructor.
56+
if (jsr_ && --jsr_->jsEnterState <= 0) {
57+
jsr_->jsEnterState = 0;
58+
try {
59+
js_execute_pending_jobs(env_);
60+
} catch (...) {
61+
}
62+
}
4363
if (napiHandleScope_) {
4464
napi_close_handle_scope(env_, napiHandleScope_);
4565
}
@@ -49,6 +69,7 @@ class NapiScope {
4969
private:
5070
napi_env env_;
5171
napi_handle_scope napiHandleScope_;
72+
JSR* jsr_ = nullptr;
5273
};
5374

5475
#define JSEnterScope

test-app/runtime/src/main/cpp/napi/quickjs/quickjs-api.c

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,6 @@ typedef struct napi_env__ {
265265
JsAtoms atoms;
266266
ExternalInfo *gcBefore;
267267
ExternalInfo *gcAfter;
268-
int js_enter_state;
269268
int64_t usedMemory;
270269
} napi_env__;
271270

@@ -296,21 +295,14 @@ typedef struct ExternalBufferInfo {
296295
} ExternalBufferInfo;
297296

298297
/**
299-
* -------------------------------------
300-
* MICROTASK HANDLING
301-
* -------------------------------------
298+
* MICROTASK HANDLING
299+
*
300+
* The microtask queue is drained by NapiScope (see quickjs/jsr.h) once the
301+
* native call stack fully unwinds back out of JS — the same scope-depth model
302+
* used by the Hermes and PrimJS engines. qjs_execute_pending_jobs below is the
303+
* pump it calls; there is no longer any per-napi-call js_enter/js_exit here.
302304
*/
303305

304-
static inline void js_enter(napi_env env) {
305-
env->js_enter_state++;
306-
}
307-
308-
static inline void js_exit(napi_env env) {
309-
if (--env->js_enter_state <= 0) {
310-
qjs_execute_pending_jobs(env);
311-
}
312-
}
313-
314306
/**
315307
* --------------------------------------
316308
* NAPI DATA FINALIZERS
@@ -3133,7 +3125,6 @@ napi_status napi_call_function(napi_env env, napi_value thisValue, napi_value fu
31333125
jsThis = JS_GetGlobalObject(env->context);
31343126
}
31353127

3136-
js_enter(env);
31373128
JSValue *args = NULL;
31383129
JSValue returnValue;
31393130

@@ -3159,8 +3150,6 @@ napi_status napi_call_function(napi_env env, napi_value thisValue, napi_value fu
31593150
NULL);
31603151
}
31613152

3162-
js_exit(env);
3163-
31643153
if (useGlobal) JS_FreeValue(env->context, jsThis);
31653154

31663155
if (JS_IsException(returnValue)) {
@@ -3339,7 +3328,6 @@ napi_new_instance(napi_env env, napi_value constructor, size_t argc, const napi_
33393328
CHECK_ARG(constructor)
33403329
CHECK_ARG(result)
33413330

3342-
js_enter(env);
33433331
JSValue *args = NULL;
33443332
JSValue returnValue;
33453333

@@ -3364,8 +3352,6 @@ napi_new_instance(napi_env env, napi_value constructor, size_t argc, const napi_
33643352
args);
33653353
}
33663354

3367-
js_exit(env);
3368-
33693355

33703356
if (JS_IsException(returnValue)) {
33713357
JS_Throw(env->context, returnValue);
@@ -3412,10 +3398,11 @@ CallConstructor(JSContext *context, JSValueConst newTarget, int argc, JSValueCon
34123398
JSValue returnValue = JS_UNDEFINED;
34133399

34143400
if (result) {
3415-
returnValue = *((JSValue *) result);
3416-
JS_DupValue(env->context, returnValue);
3417-
JS_FreeValue(env->context, thisValue);
3401+
returnValue = JS_DupValue(env->context, *((JSValue *) result));
34183402
}
3403+
// Always release the trampoline-created `this`; a null result (callback
3404+
// returned undefined or bailed) previously leaked it.
3405+
JS_FreeValue(env->context, thisValue);
34193406

34203407
assert(LIST_FIRST(&env->handleScopeList) == &handleScope &&
34213408
"napi_close_handle_scope() or napi_close_escapable_handle_scope() should follow FILO rule.");
@@ -3517,7 +3504,7 @@ napi_wrap(napi_env env, napi_value jsObject, void *nativeObject, napi_finalize f
35173504

35183505
externalInfo->data = nativeObject;
35193506
externalInfo->finalizeHint = finalize_hint;
3520-
externalInfo->finalizeCallback = NULL;
3507+
externalInfo->finalizeCallback = finalize_cb;
35213508

35223509
JSValue external = JS_NewObjectClass(env->context, (int) env->runtime->externalClassId);
35233510

@@ -3751,10 +3738,8 @@ napi_status napi_resolve_deferred(napi_env env, napi_deferred deferred, napi_val
37513738
if (resolution != NULL) {
37523739
value = *((JSValue *) resolution);
37533740
}
3754-
js_enter(env);
37553741
JSValue jsResult = JS_Call(env->context, *((JSValue *) deferred->resolve), JS_UNDEFINED, 1,
37563742
&value);
3757-
js_exit(env);
37583743
JS_FreeValue(env->context, jsResult);
37593744

37603745
return napi_clear_last_error(env);
@@ -3768,10 +3753,8 @@ napi_status napi_reject_deferred(napi_env env, napi_deferred deferred, napi_valu
37683753
if (rejection != NULL) {
37693754
value = *((JSValue *) rejection);
37703755
}
3771-
js_enter(env);
37723756
JSValue jsResult = JS_Call(env->context, *((JSValue *) deferred->reject), JS_UNDEFINED, 1,
37733757
&value);
3774-
js_exit(env);
37753758
JS_FreeValue(env->context, jsResult);
37763759

37773760
return napi_clear_last_error(env);
@@ -4196,8 +4179,6 @@ napi_status qjs_create_napi_env(napi_env *env, napi_runtime runtime) {
41964179

41974180
(*env)->context = context;
41984181

4199-
(*env)->js_enter_state = 0;
4200-
42014182
JS_SetRuntimeOpaque(runtime->runtime, *env);
42024183

42034184
// Create runtime atoms
@@ -4389,10 +4370,8 @@ napi_status qjs_execute_script(napi_env env,
43894370

43904371
JSValue eval_result;
43914372
const char *cScript = JS_ToCString(env->context, *((JSValue *) script));
4392-
js_enter(env);
43934373
eval_result = JS_Eval(env->context, cScript, strlen(cScript), file, JS_EVAL_TYPE_GLOBAL);
43944374
JS_FreeCString(env->context, cScript);
4395-
js_exit(env);
43964375
if (JS_IsException(eval_result)) {
43974376
const char *exceptionMessage = JS_ToCString(env->context, eval_result);
43984377
napi_set_last_error(env, napi_cannot_run_js, exceptionMessage, 0, NULL);

0 commit comments

Comments
 (0)