Reduce memory usage in long-running processes (#14885)

This commit is contained in:
Jarred Sumner
2024-10-29 12:56:10 -07:00
committed by GitHub
parent d5f9978007
commit b5a73130ad
16 changed files with 120 additions and 60 deletions

View File

@@ -55,11 +55,11 @@ pub const KeepAlive = struct {
this.status = .inactive;
if (comptime @TypeOf(event_loop_ctx_) == JSC.EventLoopHandle) {
event_loop_ctx_.loop().subActive(1);
event_loop_ctx_.loop().unref();
return;
}
const event_loop_ctx = JSC.AbstractVM(event_loop_ctx_);
event_loop_ctx.platformEventLoop().subActive(1);
event_loop_ctx.platformEventLoop().unref();
}
/// From another thread, Prevent a poll from keeping the process alive.

View File

@@ -0,0 +1,18 @@
#include "root.h"
#include <JavaScriptCore/VM.h>
#include <JavaScriptCore/Heap.h>
extern "C" int Bun__JSC_onBeforeWait(JSC::VM* vm)
{
if (vm->heap.hasAccess()) {
vm->heap.releaseAccess();
return 1;
}
return 0;
}
extern "C" void Bun__JSC_onAfterWait(JSC::VM* vm)
{
vm->heap.acquireAccess();
}

View File

@@ -1,30 +0,0 @@
#include "root.h"
#include "JavaScriptCore/VM.h"
// On Linux, signals are used to suspend/resume threads in JavaScriptCore
// When `.acquireAccess` is called, the signal might be raised.
// This causes issues with LLDB which might catch the signal.
// So we want to avoid that, we really only want this code to be executed when the debugger is attached
// But it's pretty hard to tell if LLDB is attached or not, so we just disable this code on Linux when in debug mode
#ifndef ACQUIRE_RELEASE_HEAP_ACCESS
#if OS(DARWIN)
#define ACQUIRE_RELEASE_HEAP_ACCESS 1
#else
#ifndef BUN_DEBUG
#define ACQUIRE_RELEASE_HEAP_ACCESS 1
#endif
#endif
#endif
extern "C" void bun_on_tick_before(JSC::VM* vm)
{
#if ACQUIRE_RELEASE_HEAP_ACCESS
// vm->heap.releaseAccess();
#endif
}
extern "C" void bun_on_tick_after(JSC::VM* vm)
{
#if ACQUIRE_RELEASE_HEAP_ACCESS
// vm->heap.acquireAccess();
#endif
}

View File

@@ -6161,6 +6161,22 @@ pub const VM = extern struct {
LargeHeap = 1,
};
extern fn Bun__JSC_onBeforeWait(vm: *VM) i32;
extern fn Bun__JSC_onAfterWait(vm: *VM) void;
pub const ReleaseHeapAccess = struct {
vm: *VM,
needs_to_release: bool,
pub fn acquire(this: *const ReleaseHeapAccess) void {
if (this.needs_to_release) {
Bun__JSC_onAfterWait(this.vm);
}
}
};
pub fn releaseHeapAccess(vm: *VM) ReleaseHeapAccess {
return .{ .vm = vm, .needs_to_release = Bun__JSC_onBeforeWait(vm) != 0 };
}
pub fn create(heap_type: HeapType) *VM {
return cppFn("create", .{@intFromEnum(heap_type)});
}

View File

@@ -545,6 +545,7 @@ pub const GarbageCollectionController = struct {
const actual = uws.Loop.get();
this.gc_timer = uws.Timer.createFallthrough(actual, this);
this.gc_repeating_timer = uws.Timer.createFallthrough(actual, this);
actual.internal_loop_data.jsc_vm = vm.jsc;
if (comptime Environment.isDebug) {
if (bun.getenvZ("BUN_TRACK_LAST_FN_NAME") != null) {

View File

@@ -1646,6 +1646,12 @@ pub const ModuleLoader = struct {
var parse_result: ParseResult = switch (disable_transpilying or
(loader == .json and !path.isJSONCFile())) {
inline else => |return_file_only| brk: {
const heap_access = if (!disable_transpilying)
jsc_vm.jsc.releaseHeapAccess()
else
JSC.VM.ReleaseHeapAccess{ .vm = jsc_vm.jsc, .needs_to_release = false };
defer heap_access.acquire();
break :brk jsc_vm.bundler.parseMaybeReturnFileOnly(
parse_options,
null,

View File

@@ -114,35 +114,25 @@ pub fn CompressionStream(comptime T: type) type {
//
const vm = globalThis.bunVM();
var task = AsyncJob.new(.{
.binding = this,
});
this.task = .{ .callback = &AsyncJob.runTask };
this.poll_ref.ref(vm);
JSC.WorkPool.schedule(&task.task);
JSC.WorkPool.schedule(&this.task);
return .undefined;
}
const AsyncJob = struct {
task: JSC.WorkPoolTask = .{ .callback = &runTask },
binding: *T,
pub usingnamespace bun.New(@This());
pub fn runTask(this: *JSC.WorkPoolTask) void {
var job: *AsyncJob = @fieldParentPtr("task", this);
job.run();
job.destroy();
pub fn runTask(task: *JSC.WorkPoolTask) void {
const this: *T = @fieldParentPtr("task", task);
AsyncJob.run(this);
}
pub fn run(job: *AsyncJob) void {
const this = job.binding;
pub fn run(this: *T) void {
const globalThis: *JSC.JSGlobalObject = this.globalThis;
const vm = globalThis.bunVMConcurrently();
this.stream.doWork();
this.poll_ref.refConcurrently(vm);
vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(JSC.Task.init(this)));
}
};
@@ -294,6 +284,29 @@ pub fn CompressionStream(comptime T: type) type {
pub const NativeZlib = JSC.Codegen.JSNativeZlib.getConstructor;
const CountedKeepAlive = struct {
keep_alive: bun.Async.KeepAlive = .{},
ref_count: u32 = 0,
pub fn ref(this: *@This(), vm: *JSC.VirtualMachine) void {
if (this.ref_count == 0) {
this.keep_alive.ref(vm);
}
this.ref_count += 1;
}
pub fn unref(this: *@This(), vm: *JSC.VirtualMachine) void {
this.ref_count -= 1;
if (this.ref_count == 0) {
this.keep_alive.unref(vm);
}
}
pub fn deinit(this: *@This()) void {
this.keep_alive.disable();
}
};
pub const SNativeZlib = struct {
pub usingnamespace bun.NewRefCounted(@This(), deinit);
pub usingnamespace JSC.Codegen.JSNativeZlib;
@@ -306,11 +319,12 @@ pub const SNativeZlib = struct {
write_result: ?[*]u32 = null,
write_callback: JSC.Strong = .{},
onerror_value: JSC.Strong = .{},
poll_ref: bun.Async.KeepAlive = .{},
poll_ref: CountedKeepAlive = .{},
this_value: JSC.Strong = .{},
write_in_progress: bool = false,
pending_close: bool = false,
closed: bool = false,
task: JSC.WorkPoolTask = .{ .callback = undefined },
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) ?*@This() {
const arguments = callframe.argumentsUndef(4).ptr;
@@ -392,6 +406,7 @@ pub const SNativeZlib = struct {
pub fn deinit(this: *@This()) void {
this.write_callback.deinit();
this.onerror_value.deinit();
this.poll_ref.deinit();
this.destroy();
}
};
@@ -662,11 +677,14 @@ pub const SNativeBrotli = struct {
write_result: ?[*]u32 = null,
write_callback: JSC.Strong = .{},
onerror_value: JSC.Strong = .{},
poll_ref: bun.Async.KeepAlive = .{},
poll_ref: CountedKeepAlive = .{},
this_value: JSC.Strong = .{},
write_in_progress: bool = false,
pending_close: bool = false,
closed: bool = false,
task: JSC.WorkPoolTask = .{
.callback = undefined,
},
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) ?*@This() {
const arguments = callframe.argumentsUndef(1).ptr;
@@ -752,6 +770,7 @@ pub const SNativeBrotli = struct {
pub fn deinit(this: *@This()) void {
this.write_callback.deinit();
this.onerror_value.deinit();
this.poll_ref.deinit();
this.destroy();
}
};

View File

@@ -459,7 +459,9 @@ pub const WebWorker = struct {
var exit_code: i32 = 0;
var globalObject: ?*JSC.JSGlobalObject = null;
var vm_to_deinit: ?*JSC.VirtualMachine = null;
var loop: ?*bun.uws.Loop = null;
if (this.vm) |vm| {
loop = vm.uwsLoop();
this.vm = null;
vm.is_shutting_down = true;
vm.onExit();
@@ -470,6 +472,9 @@ pub const WebWorker = struct {
var arena = this.arena;
WebWorker__dispatchExit(globalObject, cpp_worker, exit_code);
if (loop) |loop_| {
loop_.internal_loop_data.jsc_vm = null;
}
bun.uws.onThreadExit();
this.deinit();

View File

@@ -3146,8 +3146,8 @@ pub fn NewRefCounted(comptime T: type, comptime deinit_fn: ?fn (self: *T) void)
const ptr = bun.new(T, t);
if (Environment.enable_logs) {
if (ptr.ref_count != 1) {
Output.panic("Expected ref_count to be 1, got {d}", .{ptr.ref_count});
if (ptr.ref_count == 0) {
Output.panic("Expected ref_count to be > 0, got {d}", .{ptr.ref_count});
}
}

View File

@@ -55,6 +55,7 @@ pub const InternalLoopData = extern struct {
parent_ptr: ?*anyopaque,
parent_tag: c_char,
iteration_nr: usize,
jsc_vm: ?*JSC.VM,
pub fn recvSlice(this: *InternalLoopData) []u8 {
return this.recv_buf[0..LIBUS_RECV_BUFFER_LENGTH];