| ... | ... | @@ -12,7 +12,6 @@ pub const Loop = struct { |
| 12 | 12 | next_tick_queue: std.atomic.Queue(promise), |
| 13 | 13 | os_data: OsData, |
| 14 | 14 | final_resume_node: ResumeNode, |
| 15 | | dispatch_lock: u8, // TODO make this a bool |
| 16 | 15 | pending_event_count: usize, |
| 17 | 16 | extra_threads: []*std.os.Thread, |
| 18 | 17 | |
| ... | ... | @@ -74,11 +73,10 @@ pub const Loop = struct { |
| 74 | 73 | /// max(thread_count - 1, 0) |
| 75 | 74 | fn initInternal(self: *Loop, allocator: *mem.Allocator, thread_count: usize) !void { |
| 76 | 75 | self.* = Loop{ |
| 77 | | .pending_event_count = 0, |
| 76 | .pending_event_count = 1, |
| 78 | 77 | .allocator = allocator, |
| 79 | 78 | .os_data = undefined, |
| 80 | 79 | .next_tick_queue = std.atomic.Queue(promise).init(), |
| 81 | | .dispatch_lock = 1, // start locked so threads go directly into epoll wait |
| 82 | 80 | .extra_threads = undefined, |
| 83 | 81 | .available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(), |
| 84 | 82 | .eventfd_resume_nodes = undefined, |
| ... | ... | @@ -306,7 +304,7 @@ pub const Loop = struct { |
| 306 | 304 | pub fn addFd(self: *Loop, fd: i32, resume_node: *ResumeNode) !void { |
| 307 | 305 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); |
| 308 | 306 | errdefer { |
| 309 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 307 | self.finishOneEvent(); |
| 310 | 308 | } |
| 311 | 309 | try self.modFd( |
| 312 | 310 | fd, |
| ... | ... | @@ -326,7 +324,7 @@ pub const Loop = struct { |
| 326 | 324 | |
| 327 | 325 | pub fn removeFd(self: *Loop, fd: i32) void { |
| 328 | 326 | self.removeFdNoCounter(fd); |
| 329 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 327 | self.finishOneEvent(); |
| 330 | 328 | } |
| 331 | 329 | |
| 332 | 330 | fn removeFdNoCounter(self: *Loop, fd: i32) void { |
| ... | ... | @@ -345,14 +343,70 @@ pub const Loop = struct { |
| 345 | 343 | } |
| 346 | 344 | } |
| 347 | 345 | |
| 346 | fn dispatch(self: *Loop) void { |
| 347 | while (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| { |
| 348 | const next_tick_node = self.next_tick_queue.get() orelse { |
| 349 | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| 350 | return; |
| 351 | }; |
| 352 | const eventfd_node = &resume_stack_node.data; |
| 353 | eventfd_node.base.handle = next_tick_node.data; |
| 354 | switch (builtin.os) { |
| 355 | builtin.Os.macosx => { |
| 356 | const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent); |
| 357 | const eventlist = ([*]posix.Kevent)(undefined)[0..0]; |
| 358 | _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch { |
| 359 | self.next_tick_queue.unget(next_tick_node); |
| 360 | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| 361 | return; |
| 362 | }; |
| 363 | }, |
| 364 | builtin.Os.linux => { |
| 365 | // the pending count is already accounted for |
| 366 | const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT | |
| 367 | std.os.linux.EPOLLET; |
| 368 | self.modFd( |
| 369 | eventfd_node.eventfd, |
| 370 | eventfd_node.epoll_op, |
| 371 | epoll_events, |
| 372 | &eventfd_node.base, |
| 373 | ) catch { |
| 374 | self.next_tick_queue.unget(next_tick_node); |
| 375 | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| 376 | return; |
| 377 | }; |
| 378 | }, |
| 379 | builtin.Os.windows => { |
| 380 | // this value is never dereferenced but we need it to be non-null so that |
| 381 | // the consumer code can decide whether to read the completion key. |
| 382 | // it has to do this for normal I/O, so we match that behavior here. |
| 383 | const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); |
| 384 | std.os.windowsPostQueuedCompletionStatus( |
| 385 | self.os_data.io_port, |
| 386 | undefined, |
| 387 | eventfd_node.completion_key, |
| 388 | overlapped, |
| 389 | ) catch { |
| 390 | self.next_tick_queue.unget(next_tick_node); |
| 391 | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| 392 | return; |
| 393 | }; |
| 394 | }, |
| 395 | else => @compileError("unsupported OS"), |
| 396 | } |
| 397 | } |
| 398 | } |
| 399 | |
| 348 | 400 | /// Bring your own linked list node. This means it can't fail. |
| 349 | 401 | pub fn onNextTick(self: *Loop, node: *NextTickNode) void { |
| 350 | 402 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); |
| 351 | 403 | self.next_tick_queue.put(node); |
| 404 | self.dispatch(); |
| 352 | 405 | } |
| 353 | 406 | |
| 354 | 407 | pub fn run(self: *Loop) void { |
| 355 | | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 408 | self.finishOneEvent(); // the reference we start with |
| 409 | |
| 356 | 410 | self.workerRun(); |
| 357 | 411 | for (self.extra_threads) |extra_thread| { |
| 358 | 412 | extra_thread.wait(); |
| ... | ... | @@ -396,106 +450,45 @@ pub const Loop = struct { |
| 396 | 450 | } |
| 397 | 451 | } |
| 398 | 452 | |
| 399 | | fn workerRun(self: *Loop) void { |
| 400 | | start_over: while (true) { |
| 401 | | if (@atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) { |
| 402 | | while (self.next_tick_queue.get()) |next_tick_node| { |
| 403 | | const handle = next_tick_node.data; |
| 404 | | if (self.next_tick_queue.isEmpty()) { |
| 405 | | // last node, just resume it |
| 406 | | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 407 | | resume handle; |
| 408 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 409 | | continue :start_over; |
| 410 | | } |
| 411 | | |
| 412 | | // non-last node, stick it in the epoll/kqueue set so that |
| 413 | | // other threads can get to it |
| 414 | | if (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| { |
| 415 | | const eventfd_node = &resume_stack_node.data; |
| 416 | | eventfd_node.base.handle = handle; |
| 417 | | switch (builtin.os) { |
| 418 | | builtin.Os.macosx => { |
| 419 | | const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent); |
| 420 | | const eventlist = ([*]posix.Kevent)(undefined)[0..0]; |
| 421 | | _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch { |
| 422 | | // fine, we didn't need it anyway |
| 423 | | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 424 | | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| 425 | | resume handle; |
| 426 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 427 | | continue :start_over; |
| 428 | | }; |
| 429 | | }, |
| 430 | | builtin.Os.linux => { |
| 431 | | // the pending count is already accounted for |
| 432 | | const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT | std.os.linux.EPOLLET; |
| 433 | | self.modFd(eventfd_node.eventfd, eventfd_node.epoll_op, epoll_events, &eventfd_node.base) catch { |
| 434 | | // fine, we didn't need it anyway |
| 435 | | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 436 | | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| 437 | | resume handle; |
| 438 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 439 | | continue :start_over; |
| 440 | | }; |
| 441 | | }, |
| 442 | | builtin.Os.windows => { |
| 443 | | // this value is never dereferenced but we need it to be non-null so that |
| 444 | | // the consumer code can decide whether to read the completion key. |
| 445 | | // it has to do this for normal I/O, so we match that behavior here. |
| 446 | | const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); |
| 447 | | std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, eventfd_node.completion_key, overlapped) catch { |
| 448 | | // fine, we didn't need it anyway |
| 449 | | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 450 | | self.available_eventfd_resume_nodes.push(resume_stack_node); |
| 451 | | resume handle; |
| 452 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 453 | | continue :start_over; |
| 454 | | }; |
| 455 | | }, |
| 456 | | else => @compileError("unsupported OS"), |
| 453 | fn finishOneEvent(self: *Loop) void { |
| 454 | if (@atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) == 1) { |
| 455 | // cause all the threads to stop |
| 456 | switch (builtin.os) { |
| 457 | builtin.Os.linux => { |
| 458 | // writing 8 bytes to an eventfd cannot fail |
| 459 | std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable; |
| 460 | return; |
| 461 | }, |
| 462 | builtin.Os.macosx => { |
| 463 | const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent); |
| 464 | const eventlist = ([*]posix.Kevent)(undefined)[0..0]; |
| 465 | // cannot fail because we already added it and this just enables it |
| 466 | _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable; |
| 467 | return; |
| 468 | }, |
| 469 | builtin.Os.windows => { |
| 470 | var i: usize = 0; |
| 471 | while (i < self.os_data.extra_thread_count) : (i += 1) { |
| 472 | while (true) { |
| 473 | const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); |
| 474 | std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue; |
| 475 | break; |
| 457 | 476 | } |
| 458 | | } else { |
| 459 | | // threads are too busy, can't add another eventfd to wake one up |
| 460 | | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 461 | | resume handle; |
| 462 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 463 | | continue :start_over; |
| 464 | 477 | } |
| 465 | | } |
| 466 | | |
| 467 | | const pending_event_count = @atomicLoad(usize, &self.pending_event_count, AtomicOrder.SeqCst); |
| 468 | | if (pending_event_count == 0) { |
| 469 | | // cause all the threads to stop |
| 470 | | switch (builtin.os) { |
| 471 | | builtin.Os.linux => { |
| 472 | | // writing 8 bytes to an eventfd cannot fail |
| 473 | | std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable; |
| 474 | | return; |
| 475 | | }, |
| 476 | | builtin.Os.macosx => { |
| 477 | | const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent); |
| 478 | | const eventlist = ([*]posix.Kevent)(undefined)[0..0]; |
| 479 | | // cannot fail because we already added it and this just enables it |
| 480 | | _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable; |
| 481 | | return; |
| 482 | | }, |
| 483 | | builtin.Os.windows => { |
| 484 | | var i: usize = 0; |
| 485 | | while (i < self.os_data.extra_thread_count) : (i += 1) { |
| 486 | | while (true) { |
| 487 | | const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); |
| 488 | | std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue; |
| 489 | | break; |
| 490 | | } |
| 491 | | } |
| 492 | | return; |
| 493 | | }, |
| 494 | | else => @compileError("unsupported OS"), |
| 495 | | } |
| 496 | | } |
| 478 | return; |
| 479 | }, |
| 480 | else => @compileError("unsupported OS"), |
| 481 | } |
| 482 | } |
| 483 | } |
| 497 | 484 | |
| 498 | | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 485 | fn workerRun(self: *Loop) void { |
| 486 | while (true) { |
| 487 | while (true) { |
| 488 | const next_tick_node = self.next_tick_queue.get() orelse break; |
| 489 | self.dispatch(); |
| 490 | resume next_tick_node.data; |
| 491 | self.finishOneEvent(); |
| 499 | 492 | } |
| 500 | 493 | |
| 501 | 494 | switch (builtin.os) { |
| ... | ... | @@ -519,7 +512,7 @@ pub const Loop = struct { |
| 519 | 512 | } |
| 520 | 513 | resume handle; |
| 521 | 514 | if (resume_node_id == ResumeNode.Id.EventFd) { |
| 522 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 515 | self.finishOneEvent(); |
| 523 | 516 | } |
| 524 | 517 | } |
| 525 | 518 | }, |
| ... | ... | @@ -541,7 +534,7 @@ pub const Loop = struct { |
| 541 | 534 | } |
| 542 | 535 | resume handle; |
| 543 | 536 | if (resume_node_id == ResumeNode.Id.EventFd) { |
| 544 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 537 | self.finishOneEvent(); |
| 545 | 538 | } |
| 546 | 539 | } |
| 547 | 540 | }, |
| ... | ... | @@ -570,7 +563,7 @@ pub const Loop = struct { |
| 570 | 563 | } |
| 571 | 564 | resume handle; |
| 572 | 565 | if (resume_node_id == ResumeNode.Id.EventFd) { |
| 573 | | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); |
| 566 | self.finishOneEvent(); |
| 574 | 567 | } |
| 575 | 568 | }, |
| 576 | 569 | else => @compileError("unsupported OS"), |