authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-03-27 19:32:26-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-20 10:38:38-07:00
log0c1f5dbd64877d9e002648605ba2eec8ddbfcf16
tree5254a62e18fb6d74ff9e4a9cb509217f6e0c4372
parentc7b406f2adbe06710b8d1aca9599fb607ff26f96

EventLoop: implement main idle fiber


1 files changed, 95 insertions(+), 50 deletions(-)

lib/std/Io/EventLoop.zig+95-50
...@@ -11,19 +11,21 @@ cond: std.Thread.Condition,...@@ -11,19 +11,21 @@ cond: std.Thread.Condition,
11queue: std.DoublyLinkedList(void),11queue: std.DoublyLinkedList(void),
12free: std.DoublyLinkedList(void),12free: std.DoublyLinkedList(void),
13main_fiber_buffer: [@sizeOf(Fiber) + max_result_len]u8 align(@alignOf(Fiber)),13main_fiber_buffer: [@sizeOf(Fiber) + max_result_len]u8 align(@alignOf(Fiber)),
14exiting: bool,14exit_awaiter: ?*Fiber,
15idle_count: usize,15idle_count: usize,
16threads: std.ArrayListUnmanaged(Thread),16threads: std.ArrayListUnmanaged(Thread),
1717
18threadlocal var current_thread: *Thread = undefined;18threadlocal var current_idle_context: *Context = undefined;
19threadlocal var current_fiber: *Fiber = undefined;19threadlocal var current_fiber_context: *Context = undefined;
2020
21const max_result_len = 64;21const max_result_len = 64;
22const min_stack_size = 4 * 1024 * 1024;22const min_stack_size = 4 * 1024 * 1024;
23const idle_stack_size = 32 * 1024;
24const stack_align = 16;
2325
24const Thread = struct {26const Thread = struct {
25 thread: std.Thread,27 thread: std.Thread,
26 idle_fiber: Fiber,28 idle_context: Context,
27};29};
2830
29const Fiber = struct {31const Fiber = struct {
...@@ -54,6 +56,11 @@ const Fiber = struct {...@@ -54,6 +56,11 @@ const Fiber = struct {
54};56};
5557
56pub fn init(el: *EventLoop, gpa: Allocator) error{OutOfMemory}!void {58pub fn init(el: *EventLoop, gpa: Allocator) error{OutOfMemory}!void {
59 const threads_bytes = ((std.Thread.getCpuCount() catch 1) -| 1) * @sizeOf(Thread);
60 const idle_context_offset = std.mem.alignForward(usize, threads_bytes, @alignOf(Context));
61 const idle_stack_end_offset = std.mem.alignForward(usize, idle_context_offset + idle_stack_size, std.heap.page_size_max);
62 const allocated_slice = try gpa.alignedAlloc(u8, @max(@alignOf(Thread), @alignOf(Context), stack_align), idle_stack_end_offset);
63 errdefer gpa.free(allocated_slice);
57 el.* = .{64 el.* = .{
58 .gpa = gpa,65 .gpa = gpa,
59 .mutex = .{},66 .mutex = .{},
...@@ -61,28 +68,37 @@ pub fn init(el: *EventLoop, gpa: Allocator) error{OutOfMemory}!void {...@@ -61,28 +68,37 @@ pub fn init(el: *EventLoop, gpa: Allocator) error{OutOfMemory}!void {
61 .queue = .{},68 .queue = .{},
62 .free = .{},69 .free = .{},
63 .main_fiber_buffer = undefined,70 .main_fiber_buffer = undefined,
64 .exiting = false,71 .exit_awaiter = null,
65 .idle_count = 0,72 .idle_count = 0,
66 .threads = try .initCapacity(gpa, @max(std.Thread.getCpuCount() catch 1, 1)),73 .threads = .initBuffer(@ptrCast(allocated_slice[0..threads_bytes])),
67 };74 };
68 current_thread = el.threads.addOneAssumeCapacity();75 const main_idle_context: *Context = @alignCast(std.mem.bytesAsValue(Context, allocated_slice[idle_context_offset..][0..@sizeOf(Context)]));
69 current_fiber = @ptrCast(&el.main_fiber_buffer);76 const idle_stack_end: [*]align(stack_align) usize = @alignCast(@ptrCast(allocated_slice[idle_stack_end_offset..].ptr));
77 (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)};
78 main_idle_context.* = .{
79 .rsp = @intFromPtr(idle_stack_end - 1),
80 .rbp = 0,
81 .rip = @intFromPtr(&mainIdleEntry),
82 };
83 std.log.debug("created main idle {*}", .{main_idle_context});
84 current_idle_context = main_idle_context;
85 const current_fiber: *Fiber = @ptrCast(&el.main_fiber_buffer);
86 std.log.debug("created main fiber {*}", .{current_fiber});
87 current_fiber_context = &current_fiber.context;
70}88}
7189
72pub fn deinit(el: *EventLoop) void {90pub fn deinit(el: *EventLoop) void {
73 {91 assert(el.queue.len == 0); // pending async
74 el.mutex.lock();92 el.yield(null, &el.exit_awaiter);
75 defer el.mutex.unlock();
76 assert(el.queue.len == 0); // pending async
77 el.exiting = true;
78 }
79 el.cond.broadcast();
80 while (el.free.pop()) |free_node| {93 while (el.free.pop()) |free_node| {
81 const free_fiber: *Fiber = @fieldParentPtr("queue_node", free_node);94 const free_fiber: *Fiber = @fieldParentPtr("queue_node", free_node);
82 el.gpa.free(free_fiber.allocatedSlice());95 el.gpa.free(free_fiber.allocatedSlice());
83 }96 }
84 for (el.threads.items[1..]) |*thread| thread.thread.join();97 const idle_context_offset = std.mem.alignForward(usize, el.threads.items.len * @sizeOf(Thread), @alignOf(Context));
85 el.threads.deinit(el.gpa);98 const idle_stack_end = std.mem.alignForward(usize, idle_context_offset + idle_stack_size, std.heap.page_size_max);
99 const allocated_ptr: [*]align(@max(@alignOf(Thread), @alignOf(Context), stack_align)) u8 = @alignCast(@ptrCast(el.threads.items.ptr));
100 for (el.threads.items) |*thread| thread.thread.join();
101 el.gpa.free(allocated_ptr[0..idle_stack_end]);
86}102}
87103
88fn allocateFiber(el: *EventLoop, result_len: usize) error{OutOfMemory}!*Fiber {104fn allocateFiber(el: *EventLoop, result_len: usize) error{OutOfMemory}!*Fiber {
...@@ -103,40 +119,44 @@ fn allocateFiber(el: *EventLoop, result_len: usize) error{OutOfMemory}!*Fiber {...@@ -103,40 +119,44 @@ fn allocateFiber(el: *EventLoop, result_len: usize) error{OutOfMemory}!*Fiber {
103}119}
104120
105fn yield(el: *EventLoop, optional_fiber: ?*Fiber, register_awaiter: ?*?*Fiber) void {121fn yield(el: *EventLoop, optional_fiber: ?*Fiber, register_awaiter: ?*?*Fiber) void {
106 const ready_fiber: *Fiber = optional_fiber orelse if (ready_node: {122 const ready_context: *Context = ready_context: {
107 el.mutex.lock();123 const ready_fiber: *Fiber = optional_fiber orelse if (ready_node: {
108 defer el.mutex.unlock();124 el.mutex.lock();
109 break :ready_node el.queue.pop();125 defer el.mutex.unlock();
110 }) |ready_node|126 break :ready_node el.queue.pop();
111 @fieldParentPtr("queue_node", ready_node)127 }) |ready_node|
112 else128 @fieldParentPtr("queue_node", ready_node)
113 &current_thread.idle_fiber;129 else
130 break :ready_context current_idle_context;
131 break :ready_context &ready_fiber.context;
132 };
114 const message: SwitchMessage = .{133 const message: SwitchMessage = .{
115 .prev_context = &current_fiber.context,134 .prev_context = current_fiber_context,
116 .ready_context = &ready_fiber.context,135 .ready_context = ready_context,
117 .register_awaiter = register_awaiter,136 .register_awaiter = register_awaiter,
118 };137 };
119 std.log.debug("switching from {*} to {*}", .{138 std.log.debug("switching from {*} to {*}", .{ message.prev_context, message.ready_context });
120 @as(*Fiber, @fieldParentPtr("context", message.prev_context)),
121 @as(*Fiber, @fieldParentPtr("context", message.ready_context)),
122 });
123 contextSwitch(&message).handle(el);139 contextSwitch(&message).handle(el);
124}140}
125141
126fn schedule(el: *EventLoop, fiber: *Fiber) void {142fn schedule(el: *EventLoop, fiber: *Fiber) void {
127 signal: {143 el.mutex.lock();
128 el.mutex.lock();144 el.queue.append(&fiber.queue_node);
129 defer el.mutex.unlock();145 if (el.idle_count > 0) {
130 el.queue.append(&fiber.queue_node);146 el.mutex.unlock();
131 if (el.idle_count > 0) break :signal;147 el.cond.signal();
132 if (el.threads.items.len == el.threads.capacity) return;148 return;
133 const thread = el.threads.addOneAssumeCapacity();
134 thread.thread = std.Thread.spawn(.{
135 .stack_size = min_stack_size,
136 .allocator = el.gpa,
137 }, threadEntry, .{ el, thread }) catch return;
138 }149 }
139 el.cond.signal();150 defer el.mutex.unlock();
151 if (el.threads.items.len == el.threads.capacity) return;
152 const thread = el.threads.addOneAssumeCapacity();
153 thread.thread = std.Thread.spawn(.{
154 .stack_size = idle_stack_size,
155 .allocator = el.gpa,
156 }, threadEntry, .{ el, thread }) catch {
157 el.threads.items.len -= 1;
158 return;
159 };
140}160}
141161
142fn recycle(el: *EventLoop, fiber: *Fiber) void {162fn recycle(el: *EventLoop, fiber: *Fiber) void {
...@@ -148,14 +168,28 @@ fn recycle(el: *EventLoop, fiber: *Fiber) void {...@@ -148,14 +168,28 @@ fn recycle(el: *EventLoop, fiber: *Fiber) void {
148 el.free.append(&fiber.queue_node);168 el.free.append(&fiber.queue_node);
149}169}
150170
171fn mainIdle(el: *EventLoop, message: *const SwitchMessage) callconv(.c) noreturn {
172 message.handle(el);
173 el.yield(el.idle(), null);
174 unreachable; // switched to dead fiber
175}
176
151fn threadEntry(el: *EventLoop, thread: *Thread) void {177fn threadEntry(el: *EventLoop, thread: *Thread) void {
152 current_thread = thread;178 std.log.debug("created thread idle {*}", .{&thread.idle_context});
153 current_fiber = &thread.idle_fiber;179 current_idle_context = &thread.idle_context;
180 current_fiber_context = &thread.idle_context;
181 _ = el.idle();
182}
183
184fn idle(el: *EventLoop) *Fiber {
154 while (true) {185 while (true) {
155 el.yield(null, null);186 el.yield(null, null);
187 if (@atomicLoad(?*Fiber, &el.exit_awaiter, .acquire)) |exit_awaiter| {
188 el.cond.broadcast();
189 return exit_awaiter;
190 }
156 el.mutex.lock();191 el.mutex.lock();
157 defer el.mutex.unlock();192 defer el.mutex.unlock();
158 if (el.exiting) return;
159 el.idle_count += 1;193 el.idle_count += 1;
160 defer el.idle_count -= 1;194 defer el.idle_count -= 1;
161 el.cond.wait(&el.mutex);195 el.cond.wait(&el.mutex);
...@@ -169,7 +203,7 @@ const SwitchMessage = extern struct {...@@ -169,7 +203,7 @@ const SwitchMessage = extern struct {
169203
170 fn handle(message: *const SwitchMessage, el: *EventLoop) void {204 fn handle(message: *const SwitchMessage, el: *EventLoop) void {
171 const prev_fiber: *Fiber = @fieldParentPtr("context", message.prev_context);205 const prev_fiber: *Fiber = @fieldParentPtr("context", message.prev_context);
172 current_fiber = @fieldParentPtr("context", message.ready_context);206 current_fiber_context = message.ready_context;
173 if (message.register_awaiter) |awaiter| if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) el.schedule(prev_fiber);207 if (message.register_awaiter) |awaiter| if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) el.schedule(prev_fiber);
174 }208 }
175};209};
...@@ -208,6 +242,18 @@ inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage {...@@ -208,6 +242,18 @@ inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage {
208 };242 };
209}243}
210244
245fn mainIdleEntry() callconv(.naked) void {
246 switch (builtin.cpu.arch) {
247 .x86_64 => asm volatile (
248 \\ movq (%%rsp), %%rdi
249 \\ jmp %[mainIdle:P]
250 :
251 : [mainIdle] "X" (&mainIdle),
252 ),
253 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
254 }
255}
256
211fn fiberEntry() callconv(.naked) void {257fn fiberEntry() callconv(.naked) void {
212 switch (builtin.cpu.arch) {258 switch (builtin.cpu.arch) {
213 .x86_64 => asm volatile (259 .x86_64 => asm volatile (
...@@ -238,7 +284,7 @@ pub fn @"async"(...@@ -238,7 +284,7 @@ pub fn @"async"(
238 const closure: *AsyncClosure = @ptrFromInt(std.mem.alignBackward(284 const closure: *AsyncClosure = @ptrFromInt(std.mem.alignBackward(
239 usize,285 usize,
240 @intFromPtr(fiber.stackEndPointer() - @sizeOf(AsyncClosure)),286 @intFromPtr(fiber.stackEndPointer() - @sizeOf(AsyncClosure)),
241 @alignOf(AsyncClosure),287 @max(@alignOf(AsyncClosure), stack_align),
242 ));288 ));
243 closure.* = .{289 closure.* = .{
244 .event_loop = event_loop,290 .event_loop = event_loop,
...@@ -246,7 +292,7 @@ pub fn @"async"(...@@ -246,7 +292,7 @@ pub fn @"async"(
246 .fiber = fiber,292 .fiber = fiber,
247 .start = start,293 .start = start,
248 };294 };
249 const stack_end: [*]align(16) usize = @alignCast(@ptrCast(closure));295 const stack_end: [*]align(stack_align) usize = @alignCast(@ptrCast(closure));
250 fiber.context = .{296 fiber.context = .{
251 .rsp = @intFromPtr(stack_end - 1),297 .rsp = @intFromPtr(stack_end - 1),
252 .rbp = 0,298 .rbp = 0,
...@@ -258,7 +304,6 @@ pub fn @"async"(...@@ -258,7 +304,6 @@ pub fn @"async"(
258}304}
259305
260const AsyncClosure = struct {306const AsyncClosure = struct {
261 _: void align(16) = {},
262 event_loop: *EventLoop,307 event_loop: *EventLoop,
263 context: ?*anyopaque,308 context: ?*anyopaque,
264 fiber: *Fiber,309 fiber: *Fiber,