| ... | @@ -12,7 +12,6 @@ pub const Loop = struct { | ... | @@ -12,7 +12,6 @@ pub const Loop = struct { |
| 12 | next_tick_queue: std.atomic.Queue(promise), | 12 | next_tick_queue: std.atomic.Queue(promise), |
| 13 | os_data: OsData, | 13 | os_data: OsData, |
| 14 | final_resume_node: ResumeNode, | 14 | final_resume_node: ResumeNode, |
| 15 | dispatch_lock: u8, // TODO make this a bool | | |
| 16 | pending_event_count: usize, | 15 | pending_event_count: usize, |
| 17 | extra_threads: []*std.os.Thread, | 16 | extra_threads: []*std.os.Thread, |
| 18 | | 17 | |
| ... | @@ -74,11 +73,10 @@ pub const Loop = struct { | ... | @@ -74,11 +73,10 @@ pub const Loop = struct { |
| 74 | /// max(thread_count - 1, 0) | 73 | /// max(thread_count - 1, 0) |
| 75 | fn initInternal(self: *Loop, allocator: *mem.Allocator, thread_count: usize) !void { | 74 | fn initInternal(self: *Loop, allocator: *mem.Allocator, thread_count: usize) !void { |
| 76 | self.* = Loop{ | 75 | self.* = Loop{ |
| 77 | .pending_event_count = 0, | 76 | .pending_event_count = 1, |
| 78 | .allocator = allocator, | 77 | .allocator = allocator, |
| 79 | .os_data = undefined, | 78 | .os_data = undefined, |
| 80 | .next_tick_queue = std.atomic.Queue(promise).init(), | 79 | .next_tick_queue = std.atomic.Queue(promise).init(), |
| 81 | .dispatch_lock = 1, // start locked so threads go directly into epoll wait | | |
| 82 | .extra_threads = undefined, | 80 | .extra_threads = undefined, |
| 83 | .available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(), | 81 | .available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(), |
| 84 | .eventfd_resume_nodes = undefined, | 82 | .eventfd_resume_nodes = undefined, |
| ... | @@ -306,7 +304,7 @@ pub const Loop = struct { | ... | @@ -306,7 +304,7 @@ pub const Loop = struct { |
| 306 | pub fn addFd(self: *Loop, fd: i32, resume_node: *ResumeNode) !void { | 304 | pub fn addFd(self: *Loop, fd: i32, resume_node: *ResumeNode) !void { |
| 307 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | 305 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); |
| 308 | errdefer { | 306 | errdefer { |
| 309 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); | 307 | self.finishOneEvent(); |
| 310 | } | 308 | } |
| 311 | try self.modFd( | 309 | try self.modFd( |
| 312 | fd, | 310 | fd, |
| ... | @@ -326,7 +324,7 @@ pub const Loop = struct { | ... | @@ -326,7 +324,7 @@ pub const Loop = struct { |
| 326 | | 324 | |
| 327 | pub fn removeFd(self: *Loop, fd: i32) void { | 325 | pub fn removeFd(self: *Loop, fd: i32) void { |
| 328 | self.removeFdNoCounter(fd); | 326 | self.removeFdNoCounter(fd); |
| 329 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); | 327 | self.finishOneEvent(); |
| 330 | } | 328 | } |
| 331 | | 329 | |
| 332 | fn removeFdNoCounter(self: *Loop, fd: i32) void { | 330 | fn removeFdNoCounter(self: *Loop, fd: i32) void { |
| ... | @@ -345,14 +343,70 @@ pub const Loop = struct { | ... | @@ -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 | /// Bring your own linked list node. This means it can't fail. | 400 | /// Bring your own linked list node. This means it can't fail. |
| 349 | pub fn onNextTick(self: *Loop, node: *NextTickNode) void { | 401 | pub fn onNextTick(self: *Loop, node: *NextTickNode) void { |
| 350 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | 402 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); |
| 351 | self.next_tick_queue.put(node); | 403 | self.next_tick_queue.put(node); |
| | 404 | self.dispatch(); |
| 352 | } | 405 | } |
| 353 | | 406 | |
| 354 | pub fn run(self: *Loop) void { | 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 | self.workerRun(); | 410 | self.workerRun(); |
| 357 | for (self.extra_threads) |extra_thread| { | 411 | for (self.extra_threads) |extra_thread| { |
| 358 | extra_thread.wait(); | 412 | extra_thread.wait(); |
| ... | @@ -396,106 +450,45 @@ pub const Loop = struct { | ... | @@ -396,106 +450,45 @@ pub const Loop = struct { |
| 396 | } | 450 | } |
| 397 | } | 451 | } |
| 398 | | 452 | |
| 399 | fn workerRun(self: *Loop) void { | 453 | fn finishOneEvent(self: *Loop) void { |
| 400 | start_over: while (true) { | 454 | if (@atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) == 1) { |
| 401 | if (@atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) { | 455 | // cause all the threads to stop |
| 402 | while (self.next_tick_queue.get()) |next_tick_node| { | 456 | switch (builtin.os) { |
| 403 | const handle = next_tick_node.data; | 457 | builtin.Os.linux => { |
| 404 | if (self.next_tick_queue.isEmpty()) { | 458 | // writing 8 bytes to an eventfd cannot fail |
| 405 | // last node, just resume it | 459 | std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable; |
| 406 | _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | 460 | return; |
| 407 | resume handle; | 461 | }, |
| 408 | _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); | 462 | builtin.Os.macosx => { |
| 409 | continue :start_over; | 463 | const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent); |
| 410 | } | 464 | const eventlist = ([*]posix.Kevent)(undefined)[0..0]; |
| 411 | | 465 | // cannot fail because we already added it and this just enables it |
| 412 | // non-last node, stick it in the epoll/kqueue set so that | 466 | _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable; |
| 413 | // other threads can get to it | 467 | return; |
| 414 | if (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| { | 468 | }, |
| 415 | const eventfd_node = &resume_stack_node.data; | 469 | builtin.Os.windows => { |
| 416 | eventfd_node.base.handle = handle; | 470 | var i: usize = 0; |
| 417 | switch (builtin.os) { | 471 | while (i < self.os_data.extra_thread_count) : (i += 1) { |
| 418 | builtin.Os.macosx => { | 472 | while (true) { |
| 419 | const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent); | 473 | const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1); |
| 420 | const eventlist = ([*]posix.Kevent)(undefined)[0..0]; | 474 | std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue; |
| 421 | _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch { | 475 | break; |
| 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"), | | |
| 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 | } | 478 | return; |
| 466 | | 479 | }, |
| 467 | const pending_event_count = @atomicLoad(usize, &self.pending_event_count, AtomicOrder.SeqCst); | 480 | else => @compileError("unsupported OS"), |
| 468 | if (pending_event_count == 0) { | 481 | } |
| 469 | // cause all the threads to stop | 482 | } |
| 470 | switch (builtin.os) { | 483 | } |
| 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 | } | | |
| 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 | switch (builtin.os) { | 494 | switch (builtin.os) { |
| ... | @@ -519,7 +512,7 @@ pub const Loop = struct { | ... | @@ -519,7 +512,7 @@ pub const Loop = struct { |
| 519 | } | 512 | } |
| 520 | resume handle; | 513 | resume handle; |
| 521 | if (resume_node_id == ResumeNode.Id.EventFd) { | 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,7 +534,7 @@ pub const Loop = struct { |
| 541 | } | 534 | } |
| 542 | resume handle; | 535 | resume handle; |
| 543 | if (resume_node_id == ResumeNode.Id.EventFd) { | 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,7 +563,7 @@ pub const Loop = struct { |
| 570 | } | 563 | } |
| 571 | resume handle; | 564 | resume handle; |
| 572 | if (resume_node_id == ResumeNode.Id.EventFd) { | 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 | else => @compileError("unsupported OS"), | 569 | else => @compileError("unsupported OS"), |