| ... | ... | @@ -4,6 +4,7 @@ const assert = std.debug.assert; |
| 4 | 4 | const event = this; |
| 5 | 5 | const mem = std.mem; |
| 6 | 6 | const posix = std.os.posix; |
| 7 | const windows = std.os.windows; |
| 7 | 8 | const AtomicRmwOp = builtin.AtomicRmwOp; |
| 8 | 9 | const AtomicOrder = builtin.AtomicOrder; |
| 9 | 10 | |
| ... | ... | @@ -113,10 +114,10 @@ pub const Loop = struct { |
| 113 | 114 | allocator: *mem.Allocator, |
| 114 | 115 | next_tick_queue: std.atomic.QueueMpsc(promise), |
| 115 | 116 | os_data: OsData, |
| 117 | final_resume_node: ResumeNode, |
| 116 | 118 | dispatch_lock: u8, // TODO make this a bool |
| 117 | 119 | pending_event_count: usize, |
| 118 | 120 | extra_threads: []*std.os.Thread, |
| 119 | | final_resume_node: ResumeNode, |
| 120 | 121 | |
| 121 | 122 | // pre-allocated eventfds. all permanently active. |
| 122 | 123 | // this is how we send promises to be resumed on other threads. |
| ... | ... | @@ -144,6 +145,7 @@ pub const Loop = struct { |
| 144 | 145 | }, |
| 145 | 146 | builtin.Os.windows => struct { |
| 146 | 147 | base: ResumeNode, |
| 148 | completion_key: usize, |
| 147 | 149 | }, |
| 148 | 150 | else => @compileError("unsupported OS"), |
| 149 | 151 | }; |
| ... | ... | @@ -181,12 +183,12 @@ pub const Loop = struct { |
| 181 | 183 | .next_tick_queue = std.atomic.QueueMpsc(promise).init(), |
| 182 | 184 | .dispatch_lock = 1, // start locked so threads go directly into epoll wait |
| 183 | 185 | .extra_threads = undefined, |
| 186 | .available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(), |
| 187 | .eventfd_resume_nodes = undefined, |
| 184 | 188 | .final_resume_node = ResumeNode{ |
| 185 | 189 | .id = ResumeNode.Id.Stop, |
| 186 | 190 | .handle = undefined, |
| 187 | 191 | }, |
| 188 | | .available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(), |
| 189 | | .eventfd_resume_nodes = undefined, |
| 190 | 192 | }; |
| 191 | 193 | const extra_thread_count = thread_count - 1; |
| 192 | 194 | self.eventfd_resume_nodes = try self.allocator.alloc( |
| ... | ... | @@ -209,7 +211,8 @@ pub const Loop = struct { |
| 209 | 211 | } |
| 210 | 212 | |
| 211 | 213 | const InitOsDataError = std.os.LinuxEpollCreateError || mem.Allocator.Error || std.os.LinuxEventFdError || |
| 212 | | std.os.SpawnThreadError || std.os.LinuxEpollCtlError || std.os.BsdKEventError; |
| 214 | std.os.SpawnThreadError || std.os.LinuxEpollCtlError || std.os.BsdKEventError || |
| 215 | std.os.WindowsCreateIoCompletionPortError; |
| 213 | 216 | |
| 214 | 217 | const wakeup_bytes = []u8{0x1} ** 8; |
| 215 | 218 | |
| ... | ... | @@ -335,6 +338,51 @@ pub const Loop = struct { |
| 335 | 338 | self.extra_threads[extra_thread_index] = try std.os.spawnThread(self, workerRun); |
| 336 | 339 | } |
| 337 | 340 | }, |
| 341 | builtin.Os.windows => { |
| 342 | self.os_data.extra_thread_count = extra_thread_count; |
| 343 | |
| 344 | self.os_data.io_port = try std.os.windowsCreateIoCompletionPort( |
| 345 | windows.INVALID_HANDLE_VALUE, |
| 346 | null, |
| 347 | undefined, |
| 348 | undefined, |
| 349 | ); |
| 350 | errdefer std.os.close(self.os_data.io_port); |
| 351 | |
| 352 | for (self.eventfd_resume_nodes) |*eventfd_node, i| { |
| 353 | eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{ |
| 354 | .data = ResumeNode.EventFd{ |
| 355 | .base = ResumeNode{ |
| 356 | .id = ResumeNode.Id.EventFd, |
| 357 | .handle = undefined, |
| 358 | }, |
| 359 | // this one is for sending events |
| 360 | .completion_key = @ptrToInt(&eventfd_node.data.base), |
| 361 | }, |
| 362 | .next = undefined, |
| 363 | }; |
| 364 | self.available_eventfd_resume_nodes.push(eventfd_node); |
| 365 | } |
| 366 | |
| 367 | var extra_thread_index: usize = 0; |
| 368 | errdefer { |
| 369 | var i: usize = 0; |
| 370 | while (i < extra_thread_index) : (i += 1) { |
| 371 | while (true) { |
| 372 | const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); |
| 373 | std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue; |
| 374 | break; |
| 375 | } |
| 376 | } |
| 377 | while (extra_thread_index != 0) { |
| 378 | extra_thread_index -= 1; |
| 379 | self.extra_threads[extra_thread_index].wait(); |
| 380 | } |
| 381 | } |
| 382 | while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) { |
| 383 | self.extra_threads[extra_thread_index] = try std.os.spawnThread(self, workerRun); |
| 384 | } |
| 385 | }, |
| 338 | 386 | else => {}, |
| 339 | 387 | } |
| 340 | 388 | } |
| ... | ... | @@ -349,6 +397,10 @@ pub const Loop = struct { |
| 349 | 397 | }, |
| 350 | 398 | builtin.Os.macosx => { |
| 351 | 399 | self.allocator.free(self.os_data.kevents); |
| 400 | std.os.close(self.os_data.kqfd); |
| 401 | }, |
| 402 | builtin.Os.windows => { |
| 403 | std.os.close(self.os_data.io_port); |
| 352 | 404 | }, |
| 353 | 405 | else => {}, |
| 354 | 406 | } |
| ... | ... | @@ -434,7 +486,7 @@ pub const Loop = struct { |
| 434 | 486 | builtin.Os.macosx => { |
| 435 | 487 | const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent); |
| 436 | 488 | const eventlist = ([*]posix.Kevent)(undefined)[0..0]; |
| 437 | | _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch |_| { |
| 489 | _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch { |
| 438 | 490 | // fine, we didn't need it anyway |
| 439 | 491 | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 440 | 492 | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| ... | ... | @@ -446,7 +498,21 @@ pub const Loop = struct { |
| 446 | 498 | builtin.Os.linux => { |
| 447 | 499 | // the pending count is already accounted for |
| 448 | 500 | const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT | std.os.linux.EPOLLET; |
| 449 | | self.modFd(eventfd_node.eventfd, eventfd_node.epoll_op, epoll_events, &eventfd_node.base) catch |_| { |
| 501 | self.modFd(eventfd_node.eventfd, eventfd_node.epoll_op, epoll_events, &eventfd_node.base) catch { |
| 502 | // fine, we didn't need it anyway |
| 503 | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 504 | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| 505 | resume handle; |
| 506 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 507 | continue :start_over; |
| 508 | }; |
| 509 | }, |
| 510 | builtin.Os.windows => { |
| 511 | // this value is never dereferenced but we need it to be non-null so that |
| 512 | // the consumer code can decide whether to read the completion key. |
| 513 | // it has to do this for normal I/O, so we match that behavior here. |
| 514 | const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); |
| 515 | std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, eventfd_node.completion_key, overlapped) catch { |
| 450 | 516 | // fine, we didn't need it anyway |
| 451 | 517 | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 452 | 518 | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| ... | ... | @@ -482,6 +548,17 @@ pub const Loop = struct { |
| 482 | 548 | _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable; |
| 483 | 549 | return; |
| 484 | 550 | }, |
| 551 | builtin.Os.windows => { |
| 552 | var i: usize = 0; |
| 553 | while (i < self.os_data.extra_thread_count) : (i += 1) { |
| 554 | while (true) { |
| 555 | const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); |
| 556 | std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue; |
| 557 | break; |
| 558 | } |
| 559 | } |
| 560 | return; |
| 561 | }, |
| 485 | 562 | else => @compileError("unsupported OS"), |
| 486 | 563 | } |
| 487 | 564 | } |
| ... | ... | @@ -536,6 +613,35 @@ pub const Loop = struct { |
| 536 | 613 | } |
| 537 | 614 | } |
| 538 | 615 | }, |
| 616 | builtin.Os.windows => { |
| 617 | var completion_key: usize = undefined; |
| 618 | while (true) { |
| 619 | var nbytes: windows.DWORD = undefined; |
| 620 | var overlapped: ?*windows.OVERLAPPED = undefined; |
| 621 | switch (std.os.windowsGetQueuedCompletionStatus(self.os_data.io_port, &nbytes, &completion_key, |
| 622 | &overlapped, windows.INFINITE)) { |
| 623 | std.os.WindowsWaitResult.Aborted => return, |
| 624 | std.os.WindowsWaitResult.Normal => {}, |
| 625 | } |
| 626 | if (overlapped != null) break; |
| 627 | } |
| 628 | const resume_node = @intToPtr(*ResumeNode, completion_key); |
| 629 | const handle = resume_node.handle; |
| 630 | const resume_node_id = resume_node.id; |
| 631 | switch (resume_node_id) { |
| 632 | ResumeNode.Id.Basic => {}, |
| 633 | ResumeNode.Id.Stop => return, |
| 634 | ResumeNode.Id.EventFd => { |
| 635 | const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node); |
| 636 | const stack_node = @fieldParentPtr(std.atomic.Stack(ResumeNode.EventFd).Node, "data", event_fd_node); |
| 637 | self.available_eventfd_resume_nodes.push(stack_node); |
| 638 | }, |
| 639 | } |
| 640 | resume handle; |
| 641 | if (resume_node_id == ResumeNode.Id.EventFd) { |
| 642 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 643 | } |
| 644 | }, |
| 539 | 645 | else => @compileError("unsupported OS"), |
| 540 | 646 | } |
| 541 | 647 | } |
| ... | ... | @@ -548,6 +654,10 @@ pub const Loop = struct { |
| 548 | 654 | final_eventfd_event: std.os.linux.epoll_event, |
| 549 | 655 | }, |
| 550 | 656 | builtin.Os.macosx => MacOsData, |
| 657 | builtin.Os.windows => struct { |
| 658 | io_port: windows.HANDLE, |
| 659 | extra_thread_count: usize, |
| 660 | }, |
| 551 | 661 | else => struct {}, |
| 552 | 662 | }; |
| 553 | 663 | |