mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 13:51:47 +00:00
69 lines
2.5 KiB
Zig
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");
|