| ... | ... | @@ -13,7 +13,6 @@ const IpAddress = std.Io.net.IpAddress; |
| 13 | 13 | const Allocator = std.mem.Allocator; |
| 14 | 14 | const assert = std.debug.assert; |
| 15 | 15 | const posix = std.posix; |
| 16 | | const ResetEvent = std.Thread.ResetEvent; |
| 17 | 16 | |
| 18 | 17 | /// Thread-safe. |
| 19 | 18 | allocator: Allocator, |
| ... | ... | @@ -153,8 +152,10 @@ pub fn io(t: *Threaded) Io { |
| 153 | 152 | .cancel = cancel, |
| 154 | 153 | .cancelRequested = cancelRequested, |
| 155 | 154 | .select = select, |
| 155 | |
| 156 | 156 | .groupAsync = groupAsync, |
| 157 | 157 | .groupWait = groupWait, |
| 158 | .groupWaitUncancelable = groupWaitUncancelable, |
| 158 | 159 | .groupCancel = groupCancel, |
| 159 | 160 | |
| 160 | 161 | .mutexLock = mutexLock, |
| ... | ... | @@ -300,7 +301,7 @@ const AsyncClosure = struct { |
| 300 | 301 | } |
| 301 | 302 | |
| 302 | 303 | fn waitAndFree(ac: *AsyncClosure, gpa: Allocator, result: []u8) void { |
| 303 | | ac.reset_event.wait(); |
| 304 | ac.reset_event.waitUncancelable(); |
| 304 | 305 | @memcpy(result, ac.resultPointer()[0..result.len]); |
| 305 | 306 | free(ac, gpa, result.len); |
| 306 | 307 | } |
| ... | ... | @@ -472,7 +473,7 @@ const GroupClosure = struct { |
| 472 | 473 | assert(cancel_tid == Closure.canceling_tid); |
| 473 | 474 | // We already know the task is canceled before running the callback. Since all closures |
| 474 | 475 | // in a Group have void return type, we can return early. |
| 475 | | std.Thread.WaitGroup.finishStateless(group_state, reset_event); |
| 476 | syncFinish(group_state, reset_event); |
| 476 | 477 | return; |
| 477 | 478 | } |
| 478 | 479 | current_closure = closure; |
| ... | ... | @@ -485,7 +486,7 @@ const GroupClosure = struct { |
| 485 | 486 | assert(cancel_tid == Closure.canceling_tid); |
| 486 | 487 | } |
| 487 | 488 | |
| 488 | | std.Thread.WaitGroup.finishStateless(group_state, reset_event); |
| 489 | syncFinish(group_state, reset_event); |
| 489 | 490 | } |
| 490 | 491 | |
| 491 | 492 | fn free(gc: *GroupClosure, gpa: Allocator) void { |
| ... | ... | @@ -505,6 +506,32 @@ const GroupClosure = struct { |
| 505 | 506 | const base: [*]u8 = @ptrCast(gc); |
| 506 | 507 | return base + contextOffset(gc.context_alignment); |
| 507 | 508 | } |
| 509 | |
| 510 | const sync_is_waiting: usize = 1 << 0; |
| 511 | const sync_one_pending: usize = 1 << 1; |
| 512 | |
| 513 | fn syncStart(state: *std.atomic.Value(usize)) void { |
| 514 | const prev_state = state.fetchAdd(sync_one_pending, .monotonic); |
| 515 | assert((prev_state / sync_one_pending) < (std.math.maxInt(usize) / sync_one_pending)); |
| 516 | } |
| 517 | |
| 518 | fn syncFinish(state: *std.atomic.Value(usize), event: *ResetEvent) void { |
| 519 | const prev_state = state.fetchSub(sync_one_pending, .acq_rel); |
| 520 | assert((prev_state / sync_one_pending) > 0); |
| 521 | if (prev_state == (sync_one_pending | sync_is_waiting)) event.set(); |
| 522 | } |
| 523 | |
| 524 | fn syncWait(t: *Threaded, state: *std.atomic.Value(usize), event: *ResetEvent) Io.Cancelable!void { |
| 525 | const prev_state = state.fetchAdd(sync_is_waiting, .acquire); |
| 526 | assert(prev_state & sync_is_waiting == 0); |
| 527 | if ((prev_state / sync_one_pending) > 0) try event.wait(t); |
| 528 | } |
| 529 | |
| 530 | fn syncWaitUncancelable(state: *std.atomic.Value(usize), event: *ResetEvent) void { |
| 531 | const prev_state = state.fetchAdd(sync_is_waiting, .acquire); |
| 532 | assert(prev_state & sync_is_waiting == 0); |
| 533 | if ((prev_state / sync_one_pending) > 0) event.waitUncancelable(); |
| 534 | } |
| 508 | 535 | }; |
| 509 | 536 | |
| 510 | 537 | fn groupAsync( |
| ... | ... | @@ -566,22 +593,40 @@ fn groupAsync( |
| 566 | 593 | // This needs to be done before unlocking the mutex to avoid a race with |
| 567 | 594 | // the associated task finishing. |
| 568 | 595 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 569 | | std.Thread.WaitGroup.startStateless(group_state); |
| 596 | GroupClosure.syncStart(group_state); |
| 570 | 597 | |
| 571 | 598 | t.mutex.unlock(); |
| 572 | 599 | t.cond.signal(); |
| 573 | 600 | } |
| 574 | 601 | |
| 575 | | fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { |
| 602 | fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) Io.Cancelable!void { |
| 576 | 603 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 577 | 604 | const gpa = t.allocator; |
| 578 | 605 | |
| 579 | 606 | if (builtin.single_threaded) return; |
| 580 | 607 | |
| 581 | | // TODO these primitives are too high level, need to check cancel on EINTR |
| 582 | 608 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 583 | 609 | const reset_event: *ResetEvent = @ptrCast(&group.context); |
| 584 | | std.Thread.WaitGroup.waitStateless(group_state, reset_event); |
| 610 | try GroupClosure.syncWait(t, group_state, reset_event); |
| 611 | |
| 612 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); |
| 613 | while (true) { |
| 614 | const gc: *GroupClosure = @fieldParentPtr("node", node); |
| 615 | const node_next = node.next; |
| 616 | gc.free(gpa); |
| 617 | node = node_next orelse break; |
| 618 | } |
| 619 | } |
| 620 | |
| 621 | fn groupWaitUncancelable(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { |
| 622 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 623 | const gpa = t.allocator; |
| 624 | |
| 625 | if (builtin.single_threaded) return; |
| 626 | |
| 627 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 628 | const reset_event: *ResetEvent = @ptrCast(&group.context); |
| 629 | GroupClosure.syncWaitUncancelable(group_state, reset_event); |
| 585 | 630 | |
| 586 | 631 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); |
| 587 | 632 | while (true) { |
| ... | ... | @@ -609,7 +654,7 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void |
| 609 | 654 | |
| 610 | 655 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 611 | 656 | const reset_event: *ResetEvent = @ptrCast(&group.context); |
| 612 | | std.Thread.WaitGroup.waitStateless(group_state, reset_event); |
| 657 | GroupClosure.syncWaitUncancelable(group_state, reset_event); |
| 613 | 658 | |
| 614 | 659 | { |
| 615 | 660 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); |
| ... | ... | @@ -661,22 +706,20 @@ fn checkCancel(t: *Threaded) error{Canceled}!void { |
| 661 | 706 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { |
| 662 | 707 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 663 | 708 | if (prev_state == .contended) { |
| 664 | | try t.checkCancel(); |
| 665 | | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 709 | try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 666 | 710 | } |
| 667 | 711 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { |
| 668 | | try t.checkCancel(); |
| 669 | | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 712 | try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 670 | 713 | } |
| 671 | 714 | } |
| 672 | 715 | |
| 673 | 716 | fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { |
| 674 | 717 | _ = userdata; |
| 675 | 718 | if (prev_state == .contended) { |
| 676 | | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 719 | futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 677 | 720 | } |
| 678 | 721 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { |
| 679 | | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 722 | futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 680 | 723 | } |
| 681 | 724 | } |
| 682 | 725 | |
| ... | ... | @@ -708,7 +751,7 @@ fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: |
| 708 | 751 | defer mutex.lockUncancelable(t_io); |
| 709 | 752 | |
| 710 | 753 | while (true) { |
| 711 | | futexWait(cond_epoch, epoch); |
| 754 | futexWaitUncancelable(cond_epoch, epoch); |
| 712 | 755 | epoch = cond_epoch.load(.acquire); |
| 713 | 756 | state = cond_state.load(.monotonic); |
| 714 | 757 | while (state & signal_mask != 0) { |
| ... | ... | @@ -747,8 +790,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I |
| 747 | 790 | defer mutex.lockUncancelable(t.io()); |
| 748 | 791 | |
| 749 | 792 | while (true) { |
| 750 | | try t.checkCancel(); |
| 751 | | futexWait(cond_epoch, epoch); |
| 793 | try futexWait(t, cond_epoch, epoch); |
| 752 | 794 | |
| 753 | 795 | epoch = cond_epoch.load(.acquire); |
| 754 | 796 | state = cond_state.load(.monotonic); |
| ... | ... | @@ -1708,9 +1750,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 1708 | 1750 | } |
| 1709 | 1751 | } |
| 1710 | 1752 | |
| 1711 | | fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { |
| 1753 | fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize { |
| 1712 | 1754 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1713 | | _ = t; |
| 1714 | 1755 | |
| 1715 | 1756 | var reset_event: ResetEvent = .unset; |
| 1716 | 1757 | |
| ... | ... | @@ -1720,20 +1761,20 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { |
| 1720 | 1761 | for (futures[0..i]) |cleanup_future| { |
| 1721 | 1762 | const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future)); |
| 1722 | 1763 | if (@atomicRmw(?*ResetEvent, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) { |
| 1723 | | cleanup_closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event. |
| 1764 | cleanup_closure.reset_event.waitUncancelable(); // Ensure no reference to our stack-allocated reset_event. |
| 1724 | 1765 | } |
| 1725 | 1766 | } |
| 1726 | 1767 | return i; |
| 1727 | 1768 | } |
| 1728 | 1769 | } |
| 1729 | 1770 | |
| 1730 | | reset_event.wait(); |
| 1771 | try reset_event.wait(t); |
| 1731 | 1772 | |
| 1732 | 1773 | var result: ?usize = null; |
| 1733 | 1774 | for (futures, 0..) |future, i| { |
| 1734 | 1775 | const closure: *AsyncClosure = @ptrCast(@alignCast(future)); |
| 1735 | 1776 | if (@atomicRmw(?*ResetEvent, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) { |
| 1736 | | closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event. |
| 1777 | closure.reset_event.waitUncancelable(); // Ensure no reference to our stack-allocated reset_event. |
| 1737 | 1778 | if (result == null) result = i; // In case multiple are ready, return first. |
| 1738 | 1779 | } |
| 1739 | 1780 | } |
| ... | ... | @@ -3320,11 +3361,12 @@ fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) Hos |
| 3320 | 3361 | return .{ .bytes = dest }; |
| 3321 | 3362 | } |
| 3322 | 3363 | |
| 3323 | | pub fn futexWait(ptr: *const std.atomic.Value(u32), expect: u32) void { |
| 3364 | fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Cancelable!void { |
| 3324 | 3365 | @branchHint(.cold); |
| 3325 | 3366 | |
| 3326 | 3367 | if (native_os == .linux) { |
| 3327 | 3368 | const linux = std.os.linux; |
| 3369 | try t.checkCancel(); |
| 3328 | 3370 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); |
| 3329 | 3371 | if (builtin.mode == .Debug) switch (linux.E.init(rc)) { |
| 3330 | 3372 | .SUCCESS => {}, // notified by `wake()` |
| ... | ... | @@ -3341,7 +3383,28 @@ pub fn futexWait(ptr: *const std.atomic.Value(u32), expect: u32) void { |
| 3341 | 3383 | @compileError("TODO"); |
| 3342 | 3384 | } |
| 3343 | 3385 | |
| 3344 | | pub fn futexWaitDuration(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void { |
| 3386 | pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) void { |
| 3387 | @branchHint(.cold); |
| 3388 | |
| 3389 | if (native_os == .linux) { |
| 3390 | const linux = std.os.linux; |
| 3391 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); |
| 3392 | if (builtin.mode == .Debug) switch (linux.E.init(rc)) { |
| 3393 | .SUCCESS => {}, // notified by `wake()` |
| 3394 | .INTR => {}, // gives caller a chance to check cancellation |
| 3395 | .AGAIN => {}, // ptr.* != expect |
| 3396 | .INVAL => {}, // possibly timeout overflow |
| 3397 | .TIMEDOUT => unreachable, |
| 3398 | .FAULT => unreachable, // ptr was invalid |
| 3399 | else => unreachable, |
| 3400 | }; |
| 3401 | return; |
| 3402 | } |
| 3403 | |
| 3404 | @compileError("TODO"); |
| 3405 | } |
| 3406 | |
| 3407 | pub fn futexWaitDurationUncancelable(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void { |
| 3345 | 3408 | @branchHint(.cold); |
| 3346 | 3409 | |
| 3347 | 3410 | if (native_os == .linux) { |
| ... | ... | @@ -3384,3 +3447,114 @@ pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void { |
| 3384 | 3447 | |
| 3385 | 3448 | @compileError("TODO"); |
| 3386 | 3449 | } |
| 3450 | |
| 3451 | /// A thread-safe logical boolean value which can be `set` and `unset`. |
| 3452 | /// |
| 3453 | /// It can also block threads until the value is set with cancelation via timed |
| 3454 | /// waits. Statically initializable; four bytes on all targets. |
| 3455 | pub const ResetEvent = enum(u32) { |
| 3456 | unset = 0, |
| 3457 | waiting = 1, |
| 3458 | is_set = 2, |
| 3459 | |
| 3460 | /// Returns whether the logical boolean is `set`. |
| 3461 | /// |
| 3462 | /// Once `reset` is called, this returns false until the next `set`. |
| 3463 | /// |
| 3464 | /// The memory accesses before the `set` can be said to happen before |
| 3465 | /// `isSet` returns true. |
| 3466 | pub fn isSet(re: *const ResetEvent) bool { |
| 3467 | if (builtin.single_threaded) return switch (re.*) { |
| 3468 | .unset => false, |
| 3469 | .waiting => unreachable, |
| 3470 | .is_set => true, |
| 3471 | }; |
| 3472 | // Acquire barrier ensures memory accesses before `set` happen before |
| 3473 | // returning true. |
| 3474 | return @atomicLoad(ResetEvent, re, .acquire) == .is_set; |
| 3475 | } |
| 3476 | |
| 3477 | /// Blocks the calling thread until `set` is called. |
| 3478 | /// |
| 3479 | /// This is effectively a more efficient version of `while (!isSet()) {}`. |
| 3480 | /// |
| 3481 | /// The memory accesses before the `set` can be said to happen before `wait` returns. |
| 3482 | pub fn wait(re: *ResetEvent, t: *Threaded) Io.Cancelable!void { |
| 3483 | if (builtin.single_threaded) switch (re.*) { |
| 3484 | .unset => unreachable, // Deadlock, no other threads to wake us up. |
| 3485 | .waiting => unreachable, // Invalid state. |
| 3486 | .is_set => return, |
| 3487 | }; |
| 3488 | if (re.isSet()) { |
| 3489 | @branchHint(.likely); |
| 3490 | return; |
| 3491 | } |
| 3492 | // Try to set the state from `unset` to `waiting` to indicate to the |
| 3493 | // `set` thread that others are blocked on the ResetEvent. Avoid using |
| 3494 | // any strict barriers until we know the ResetEvent is set. |
| 3495 | var state = @atomicLoad(ResetEvent, re, .acquire); |
| 3496 | if (state == .unset) { |
| 3497 | state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting; |
| 3498 | } |
| 3499 | while (state == .waiting) { |
| 3500 | try futexWait(t, @ptrCast(re), @intFromEnum(ResetEvent.waiting)); |
| 3501 | state = @atomicLoad(ResetEvent, re, .acquire); |
| 3502 | } |
| 3503 | assert(state == .is_set); |
| 3504 | } |
| 3505 | |
| 3506 | /// Same as `wait` except uninterruptible. |
| 3507 | pub fn waitUncancelable(re: *ResetEvent) void { |
| 3508 | if (builtin.single_threaded) switch (re.*) { |
| 3509 | .unset => unreachable, // Deadlock, no other threads to wake us up. |
| 3510 | .waiting => unreachable, // Invalid state. |
| 3511 | .is_set => return, |
| 3512 | }; |
| 3513 | if (re.isSet()) { |
| 3514 | @branchHint(.likely); |
| 3515 | return; |
| 3516 | } |
| 3517 | // Try to set the state from `unset` to `waiting` to indicate to the |
| 3518 | // `set` thread that others are blocked on the ResetEvent. Avoid using |
| 3519 | // any strict barriers until we know the ResetEvent is set. |
| 3520 | var state = @atomicLoad(ResetEvent, re, .acquire); |
| 3521 | if (state == .unset) { |
| 3522 | state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting; |
| 3523 | } |
| 3524 | while (state == .waiting) { |
| 3525 | futexWaitUncancelable(@ptrCast(re), @intFromEnum(ResetEvent.waiting)); |
| 3526 | state = @atomicLoad(ResetEvent, re, .acquire); |
| 3527 | } |
| 3528 | assert(state == .is_set); |
| 3529 | } |
| 3530 | |
| 3531 | /// Marks the logical boolean as `set` and unblocks any threads in `wait` |
| 3532 | /// or `timedWait` to observe the new state. |
| 3533 | /// |
| 3534 | /// The logical boolean stays `set` until `reset` is called, making future |
| 3535 | /// `set` calls do nothing semantically. |
| 3536 | /// |
| 3537 | /// The memory accesses before `set` can be said to happen before `isSet` |
| 3538 | /// returns true or `wait`/`timedWait` return successfully. |
| 3539 | pub fn set(re: *ResetEvent) void { |
| 3540 | if (builtin.single_threaded) { |
| 3541 | re.* = .is_set; |
| 3542 | return; |
| 3543 | } |
| 3544 | if (@atomicRmw(ResetEvent, re, .Xchg, .is_set, .release) == .waiting) { |
| 3545 | futexWake(@ptrCast(re), std.math.maxInt(u32)); |
| 3546 | } |
| 3547 | } |
| 3548 | |
| 3549 | /// Unmarks the ResetEvent as if `set` was never called. |
| 3550 | /// |
| 3551 | /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent |
| 3552 | /// calls to `set`, `isSet` and `reset` are allowed. |
| 3553 | pub fn reset(re: *ResetEvent) void { |
| 3554 | if (builtin.single_threaded) { |
| 3555 | re.* = .unset; |
| 3556 | return; |
| 3557 | } |
| 3558 | @atomicStore(ResetEvent, re, .unset, .monotonic); |
| 3559 | } |
| 3560 | }; |