-
Notifications
You must be signed in to change notification settings - Fork 8
Description
Problem
When a JsAsyncRuntime Dart object is garbage collected (e.g. during Flutter hot restart), the Rust Drop calls QuickJS's JS_FreeRuntime, which hits this assertion:
Assertion failed: (list_empty(&rt->gc_obj_list)), function JS_FreeRuntime, file quickjs.c, line 2308.
This is a SIGABRT — it kills the process. It happens because QuickJS still has objects on its GC list (modules, bridge closures, built-in objects from JsBuiltinOptions.essential()) that weren't freed before the runtime was dropped.
Reproduction
- Create a runtime, context, and engine with a bridge
- Evaluate some JS (especially modules via
evaluateModule) - Let the
JsAsyncRuntimeDart object get garbage collected (e.g. via Flutter hot restart, or by dropping all references)
final runtime = await JsAsyncRuntime.withOptions(
builtin: JsBuiltinOptions.essential(),
);
final context = await JsAsyncContext.from(runtime: runtime);
final engine = JsEngine(context: context);
await engine.init(bridge: (v) async => const JsResult.ok(JsValue.none()));
await engine.evaluateModule(
module: JsModule.code(module: '/test', code: 'export default 1;'),
);
// Later: runtime goes out of scope → Dart GC → Rust Drop → JS_FreeRuntime → SIGABRTCalling engine.dispose() first doesn't help — the assertion still fires when the runtime is eventually dropped.
Workaround
Bump the Arc ref count so the Rust Drop never runs:
import 'package:flutter_rust_bridge/src/misc/rust_opaque.dart';
void preventNativeDrop(Object obj) {
if (obj is RustOpaque) {
obj.frbInternalCstEncode(move: false); // increments Arc strong count
}
}
// After creation:
preventNativeDrop(runtime);This permanently leaks the QuickJS runtime (the Arc never reaches 0), but avoids the crash.
Suggested Fix
Before calling JS_FreeRuntime in the Rust Drop, run JS_RunGC and/or iterate rt->gc_obj_list to free remaining objects. Alternatively, skip the assertion and clean up gracefully — QuickJS's assert is unconditional (not behind #ifdef DEBUG).