Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions src/js/class_runtime.zig
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ pub fn defaultFinalize(comptime T: type) napi.FinalizeCallback(T) {
pub fn registerClass(comptime T: type, env: napi.Env, ctor: napi.Value) !void {
const State = state(T);

State.mutex.lock();
defer State.mutex.unlock();
State.lock();
defer State.unlock();

if (State.find(env.env) != null) return;

Expand Down Expand Up @@ -113,8 +113,8 @@ pub fn materializeClassInstance(comptime T: type, env: napi.Env, instance: T, pr
fn getConstructor(comptime T: type, env: napi.Env) !napi.Value {
const State = state(T);

State.mutex.lock();
defer State.mutex.unlock();
State.lock();
defer State.unlock();

const entry = State.find(env.env) orelse return error.ClassNotRegistered;
return try entry.ctor_ref.getValue();
Expand Down Expand Up @@ -147,7 +147,17 @@ fn state(comptime T: type) type {
};

var head: ?*Entry = null;
var mutex: std.Thread.Mutex = .{};
var locked: std.atomic.Value(bool) = std.atomic.Value(bool).init(false);

fn lock() void {
while (locked.cmpxchgWeak(false, true, .acquire, .monotonic) != null) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For someone curiosity how this code works, here is some explanation.

CPUs provide a single hardware instruction that does two things atomically (cannot be interrupted):

▎ "Read the value. If it equals X, replace it with Y. Tell me if it worked."

This is cmpxchg — it's a single CPU instruction, not two separate operations. The CPU's cache coherency protocol guarantees that only one core can win the race.

The signature is:

locked.cmpxchgWeak(expected_value, new_value, success_order, fail_order)

So as per call we instruct

  1. success_case: If locked value is false, set it to true - means mark the resource locked.
  2. fail_case: If locked value is not false, keep trying in the loop.
  3. In success_case instruct atomic state to be acquire, so no CPU instruction is allowed to re-ordered in memory.
  4. In fail_case instruct atomic state to be monotonic, that suggest to perform any atomic operation afterwards.

std.atomic.spinLoopHint();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add noop here inside loop or add sleep. In both cases CPU resources are exhausted.

spinLoopHint() — Emits a PAUSE instruction (x86) or YIELD (ARM). This tells the CPU don't waste power or starve the sibling hyperthread. Without this, the tight loop burns CPU unnecessarily.

}
}

fn unlock() void {
locked.store(false, .release);
}

fn find(env_ptr: napi.c.napi_env) ?*Entry {
var current = head;
Expand All @@ -158,8 +168,8 @@ fn state(comptime T: type) type {
}

fn cleanupHook(entry: *Entry) void {
mutex.lock();
defer mutex.unlock();
lock();
defer unlock();

var cursor = &head;
while (cursor.*) |current| {
Expand Down
Loading