Files
bun.sh/src/bun.js/event_loop/DeferredTaskQueue.zig
pfg 83760fc446 Sort imports in all files (#21119)
Co-authored-by: taylor.fish <contact@taylor.fish>
2025-07-21 13:26:47 -07:00

69 lines
2.5 KiB
Zig

//! 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 bun = @import("bun");
const std = @import("std");