diff --git a/bridge/jsb_environment.cpp b/bridge/jsb_environment.cpp index 62497866..e121efab 100644 --- a/bridge/jsb_environment.cpp +++ b/bridge/jsb_environment.cpp @@ -940,8 +940,13 @@ namespace jsb { if (object_handle->ref_count_ == 0) { - // becomes a strong reference - jsb_check(!object_handle->ref_.IsEmpty()); + // A first-pass GC callback may already have reset `ref_` while second-pass free + // is still pending. Treat this as a stale/dead binding instead of crashing. + if (jsb_unlikely(object_handle->ref_.IsEmpty())) + { + JSB_LOG(Warning, "UNEXPECTED inc reference on dead value %d", (uintptr_t) p_pointer); + return false; + } object_handle->ref_.ClearWeak(); } ++object_handle->ref_count_; @@ -949,7 +954,12 @@ namespace jsb } // removing references - jsb_checkf(!object_handle->ref_.IsEmpty(), "removing references on dead values"); + if (jsb_unlikely(object_handle->ref_.IsEmpty())) + { + JSB_LOG(Warning, "UNEXPECTED dec reference on dead value %d", (uintptr_t) p_pointer); + return false; + } + jsb_check(object_handle->ref_count_ > 0); --object_handle->ref_count_; diff --git a/bridge/jsb_environment.h b/bridge/jsb_environment.h index 5245c946..cd414859 100644 --- a/bridge/jsb_environment.h +++ b/bridge/jsb_environment.h @@ -658,11 +658,40 @@ namespace jsb */ void call_script_prelude(ScriptClassID p_script_class_id, NativeObjectID p_object_id); - // callback from v8 gc (not 100% guaranteed called) + // NOTE: First-pass weak callbacks are very restricted in V8. Do not do heavy work there. jsb_force_inline static void object_gc_callback(const v8::WeakCallbackInfo& info) { - Environment* env = wrap(info.GetIsolate()); - env->add_async_call(AsyncCall::TYPE_GC_FREE, info.GetParameter()); + if (Environment* env = wrap(info.GetIsolate())) + { + // V8 requires clearing the weak handle in first-pass callbacks. + if (ObjectHandlePtr object_handle = env->object_db_.try_get_object(info.GetParameter())) + { + object_handle->ref_.Reset(); + } + + if (!env->is_disposing()) + { +#if JSB_WITH_V8 + info.SetSecondPassCallback(&object_gc_callback_second_pass); +#else + object_gc_callback_second_pass(info); +#endif + } + } + } + + static void object_gc_callback_second_pass(const v8::WeakCallbackInfo& info) + { + if (Environment* env = wrap(info.GetIsolate())) + { + // During shutdown we explicitly drain object_db_ in dispose(); skip GC callback work. + if (env->is_disposing()) + { + return; + } + + env->add_async_call(AsyncCall::TYPE_GC_FREE, info.GetParameter()); + } } // a forward method for non-v8 implementations