mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
134 lines
4.4 KiB
Zig
134 lines
4.4 KiB
Zig
const std = @import("std");
|
|
const bun = @import("root").bun;
|
|
const Output = @import("./output.zig");
|
|
|
|
const strings = bun.strings;
|
|
const meta = bun.meta;
|
|
|
|
/// Reference-counted heap-allocated instance value.
|
|
///
|
|
/// `ref_count` is expected to be defined on `T` with a default value set to `1`
|
|
pub fn NewRefCounted(comptime T: type, comptime deinit_fn: ?fn (self: *T) void, debug_name: ?[:0]const u8) type {
|
|
if (!@hasField(T, "ref_count")) {
|
|
@compileError("Expected a field named \"ref_count\" with a default value of 1 on " ++ @typeName(T));
|
|
}
|
|
|
|
for (std.meta.fields(T)) |field| {
|
|
if (strings.eqlComptime(field.name, "ref_count")) {
|
|
if (field.default_value_ptr == null) {
|
|
@compileError("Expected a field named \"ref_count\" with a default value of 1 on " ++ @typeName(T));
|
|
}
|
|
}
|
|
}
|
|
|
|
const output_name = debug_name orelse meta.typeBaseName(@typeName(T));
|
|
const log = Output.scoped(output_name, true);
|
|
|
|
return struct {
|
|
pub fn destroy(self: *T) void {
|
|
if (bun.Environment.allow_assert) {
|
|
bun.assert(self.ref_count == 0);
|
|
}
|
|
|
|
bun.destroy(self);
|
|
}
|
|
|
|
pub fn ref(self: *T) void {
|
|
if (bun.Environment.isDebug) log("0x{x} ref {d} + 1 = {d}", .{ @intFromPtr(self), self.ref_count, self.ref_count + 1 });
|
|
|
|
self.ref_count += 1;
|
|
}
|
|
|
|
pub fn deref(self: *T) void {
|
|
const ref_count = self.ref_count;
|
|
if (bun.Environment.isDebug) {
|
|
if (ref_count == 0 or ref_count == std.math.maxInt(@TypeOf(ref_count))) {
|
|
@panic("Use after-free detected on " ++ output_name);
|
|
}
|
|
}
|
|
|
|
if (bun.Environment.isDebug) log("0x{x} deref {d} - 1 = {d}", .{ @intFromPtr(self), ref_count, ref_count - 1 });
|
|
|
|
self.ref_count = ref_count - 1;
|
|
|
|
if (ref_count == 1) {
|
|
if (comptime deinit_fn) |deinit| {
|
|
deinit(self);
|
|
} else {
|
|
self.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub inline fn new(t: T) *T {
|
|
const ptr = bun.new(T, t);
|
|
|
|
if (bun.Environment.enable_logs) {
|
|
if (ptr.ref_count == 0) {
|
|
Output.panic("Expected ref_count to be > 0, got {d}", .{ptr.ref_count});
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn NewThreadSafeRefCounted(comptime T: type, comptime deinit_fn: ?fn (self: *T) void, debug_name: ?[:0]const u8) type {
|
|
if (!@hasField(T, "ref_count")) {
|
|
@compileError("Expected a field named \"ref_count\" with a default value of 1 on " ++ @typeName(T));
|
|
}
|
|
|
|
for (std.meta.fields(T)) |field| {
|
|
if (strings.eqlComptime(field.name, "ref_count")) {
|
|
if (field.default_value_ptr == null) {
|
|
@compileError("Expected a field named \"ref_count\" with a default value of 1 on " ++ @typeName(T));
|
|
}
|
|
}
|
|
}
|
|
|
|
const output_name = debug_name orelse meta.typeBaseName(@typeName(T));
|
|
const log = Output.scoped(output_name, true);
|
|
|
|
return struct {
|
|
pub fn destroy(self: *T) void {
|
|
if (bun.Environment.allow_assert) {
|
|
bun.assert(self.ref_count.load(.seq_cst) == 0);
|
|
}
|
|
|
|
bun.destroy(self);
|
|
}
|
|
|
|
pub fn ref(self: *T) void {
|
|
const ref_count = self.ref_count.fetchAdd(1, .seq_cst);
|
|
if (bun.Environment.isDebug) log("0x{x} ref {d} + 1 = {d}", .{ @intFromPtr(self), ref_count, ref_count - 1 });
|
|
bun.debugAssert(ref_count > 0);
|
|
}
|
|
|
|
pub fn deref(self: *T) void {
|
|
const ref_count = self.ref_count.fetchSub(1, .seq_cst);
|
|
if (bun.Environment.isDebug) log("0x{x} deref {d} - 1 = {d}", .{ @intFromPtr(self), ref_count, ref_count -| 1 });
|
|
|
|
if (ref_count == 1) {
|
|
if (comptime deinit_fn) |deinit| {
|
|
deinit(self);
|
|
} else {
|
|
self.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub inline fn new(t: T) *T {
|
|
const ptr = bun.new(T, t);
|
|
|
|
if (bun.Environment.enable_logs) {
|
|
if (ptr.ref_count.load(.seq_cst) != 1) {
|
|
Output.panic("Expected ref_count to be 1, got {d}", .{ptr.ref_count.load(.seq_cst)});
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
};
|
|
}
|