Split EventLoop into many more files (#20134)

Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
Jarred Sumner
2025-06-01 20:43:30 -07:00
committed by GitHub
parent b2a728e45d
commit 71c14fac7b
19 changed files with 2813 additions and 2600 deletions

View File

@@ -0,0 +1,66 @@
//! Sometimes, you have work that will be scheduled, cancelled, and rescheduled multiple times
//! The order of that work may not particularly matter.
//!
//! An example of this is when writing to a file or network socket.
//!
//! You want to balance:
//! 1) Writing as much as possible to the file/socket in as few system calls as possible
//! 2) Writing to the file/socket as soon as possible
//!
//! That is a scheduling problem. How do you decide when to write to the file/socket? Developers
//! don't want to remember to call `flush` every time they write to a file/socket, but we don't
//! want them to have to think about buffering or not buffering either.
//!
//! Our answer to this is the DeferredTaskQueue.
//!
//! When you call write() when sending a streaming HTTP response, we don't actually write it immediately
//! by default. Instead, we wait until the end of the microtask queue to write it, unless either:
//!
//! - The buffer is full
//! - The developer calls `flush` manually
//!
//! But that means every time you call .write(), we have to check not only if the buffer is full, but also if
//! it previously had scheduled a write to the file/socket. So we use an ArrayHashMap to keep track of the
//! list of pointers which have a deferred task scheduled.
//!
//! The DeferredTaskQueue is drained after the microtask queue, but before other tasks are executed. This avoids re-entrancy
//! issues with the event loop.
const DeferredTaskQueue = @This();
pub const DeferredRepeatingTask = *const (fn (*anyopaque) bool);
map: std.AutoArrayHashMapUnmanaged(?*anyopaque, DeferredRepeatingTask) = .{},
pub fn postTask(this: *DeferredTaskQueue, ctx: ?*anyopaque, task: DeferredRepeatingTask) bool {
const existing = this.map.getOrPutValue(bun.default_allocator, ctx, task) catch bun.outOfMemory();
return existing.found_existing;
}
pub fn unregisterTask(this: *DeferredTaskQueue, ctx: ?*anyopaque) bool {
return this.map.swapRemove(ctx);
}
pub fn run(this: *DeferredTaskQueue) void {
var i: usize = 0;
var last = this.map.count();
while (i < last) {
const key = this.map.keys()[i] orelse {
this.map.swapRemoveAt(i);
last = this.map.count();
continue;
};
if (!this.map.values()[i](key)) {
this.map.swapRemoveAt(i);
last = this.map.count();
} else {
i += 1;
}
}
}
pub fn deinit(this: *DeferredTaskQueue) void {
this.map.deinit(bun.default_allocator);
}
const std = @import("std");
const bun = @import("bun");