diff --git a/README.md b/README.md index a76ef6e..851b537 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,13 @@ comptime { } ``` +Module initialization is transactional: + +- If `init` fails, the environment refcount is restored and `cleanup` is not called. +- If `init` succeeds but module registration fails, the environment refcount is restored and + `cleanup` is called. +- After successful registration, `cleanup` runs when the environment exits. + This enables safe shared-state initialization for worker thread scenarios. --- diff --git a/src/js/export_module.zig b/src/js/export_module.zig index 1b9a3ae..40eb0c7 100644 --- a/src/js/export_module.zig +++ b/src/js/export_module.zig @@ -97,15 +97,27 @@ pub fn exportModule(comptime Module: type, comptime options: anytype) void { }; var prev_refcount: u32 = 0; + var init_succeeded = false; if (has_lifecycle) { prev_refcount = State.env_refcount.fetchAdd(1, .monotonic); - errdefer if (!cleanup_hook_registered) { - _ = State.env_refcount.fetchSub(1, .acq_rel); - }; - - if (has_init) { - try options.init(prev_refcount); + } + errdefer if (!cleanup_hook_registered) { + if (has_lifecycle) { + const prev_refcount_rollback = State.env_refcount.fetchSub(1, .acq_rel); + std.debug.assert(prev_refcount_rollback > 0); + const new_refcount = prev_refcount_rollback - 1; + + if (has_cleanup) { + if (init_succeeded) { + options.cleanup(new_refcount); + } + } } + }; + + if (has_init) { + try options.init(prev_refcount); + init_succeeded = true; } _ = try registerDecls(Module, env, module, 0);