| author | |
| committer | |
| log | 3b4432d9a6ae7f01f66e5ddcc15bf579d2c47460 |
| tree | d24b87f33bf868ad6b4857bb500a80441744cc4a |
| parent | 2f52f95b928c1dcb42afb22243c27fb9661cbd1b |
| parent | 12508025a4b3a841819b913e4ed88810ca04ba79 |
| signature |
Integrate std.time.sleep with the event loop4 files changed, 391 insertions(+), 1 deletions(-)
lib/std/auto_reset_event.zig created+229| ... | @@ -0,0 +1,229 @@ | ||
| 1 | // SPDX-License-Identifier: MIT | ||
| 2 | // Copyright (c) 2015-2020 Zig Contributors | ||
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | ||
| 4 | // The MIT license requires this copyright notice to be included in all copies | ||
| 5 | // and substantial portions of the software. | ||
| 6 | const std = @import("std.zig"); | ||
| 7 | const builtin = @import("builtin"); | ||
| 8 | const testing = std.testing; | ||
| 9 | const assert = std.debug.assert; | ||
| 10 | |||
| 11 | /// Similar to std.ResetEvent but on `set()` it also (atomically) does `reset()`. | ||
| 12 | /// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like). | ||
| 13 | pub const AutoResetEvent = struct { | ||
| 14 | // AutoResetEvent has 3 possible states: | ||
| 15 | // - UNSET: the AutoResetEvent is currently unset | ||
| 16 | // - SET: the AutoResetEvent was notified before a wait() was called | ||
| 17 | // - <std.ResetEvent pointer>: there is an active waiter waiting for a notification. | ||
| 18 | // | ||
| 19 | // When attempting to wait: | ||
| 20 | // if the event is unset, it registers a ResetEvent pointer to be notified when the event is set | ||
| 21 | // if the event is already set, then it consumes the notification and resets the event. | ||
| 22 | // | ||
| 23 | // When attempting to notify: | ||
| 24 | // if the event is unset, then we set the event | ||
| 25 | // if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent | ||
| 26 | // | ||
| 27 | // This ensures that the event is automatically reset after a wait() has been issued | ||
| 28 | // and avoids the race condition when using std.ResetEvent in the following scenario: | ||
| 29 | // thread 1 | thread 2 | ||
| 30 | // std.ResetEvent.wait() | | ||
| 31 | // | std.ResetEvent.set() | ||
| 32 | // | std.ResetEvent.set() | ||
| 33 | // std.ResetEvent.reset() | | ||
| 34 | // std.ResetEvent.wait() | (missed the second .set() notification above) | ||
| 35 | |||
| 36 | |||
| 37 | state: usize = UNSET, | ||
| 38 | |||
| 39 | const UNSET = 0; | ||
| 40 | const SET = 1; | ||
| 41 | |||
| 42 | // the minimum alignment for the `*std.ResetEvent` created by wait*() | ||
| 43 | const event_align = std.math.max(@alignOf(std.ResetEvent), 2); | ||
| 44 | |||
| 45 | pub fn wait(self: *AutoResetEvent) void { | ||
| 46 | self.waitFor(null) catch unreachable; | ||
| 47 | } | ||
| 48 | |||
| 49 | pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void { | ||
| 50 | return self.waitFor(timeout); | ||
| 51 | } | ||
| 52 | |||
| 53 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { | ||
| 54 | // lazily initialized std.ResetEvent | ||
| 55 | var reset_event: std.ResetEvent align(event_align) = undefined; | ||
| 56 | var has_reset_event = false; | ||
| 57 | defer if (has_reset_event) { | ||
| 58 | reset_event.deinit(); | ||
| 59 | }; | ||
| 60 | |||
| 61 | var state = @atomicLoad(usize, &self.state, .SeqCst); | ||
| 62 | while (true) { | ||
| 63 | // consume a notification if there is any | ||
| 64 | if (state == SET) { | ||
| 65 | @atomicStore(usize, &self.state, UNSET, .SeqCst); | ||
| 66 | return; | ||
| 67 | } | ||
| 68 | |||
| 69 | // check if theres currently a pending ResetEvent pointer already registered | ||
| 70 | if (state != UNSET) { | ||
| 71 | unreachable; // multiple waiting threads on the same AutoResetEvent | ||
| 72 | } | ||
| 73 | |||
| 74 | // lazily initialize the ResetEvent if it hasn't been already | ||
| 75 | if (!has_reset_event) { | ||
| 76 | has_reset_event = true; | ||
| 77 | reset_event = std.ResetEvent.init(); | ||
| 78 | } | ||
| 79 | |||
| 80 | // Since the AutoResetEvent currently isnt set, | ||
| 81 | // try to register our ResetEvent on it to wait | ||
| 82 | // for a set() call from another thread. | ||
| 83 | if (@cmpxchgWeak( | ||
| 84 | usize, | ||
| 85 | &self.state, | ||
| 86 | UNSET, | ||
| 87 | @ptrToInt(&reset_event), | ||
| 88 | .SeqCst, | ||
| 89 | .SeqCst, | ||
| 90 | )) |new_state| { | ||
| 91 | state = new_state; | ||
| 92 | continue; | ||
| 93 | } | ||
| 94 | |||
| 95 | // if no timeout was specified, then just wait forever | ||
| 96 | const timeout_ns = timeout orelse { | ||
| 97 | reset_event.wait(); | ||
| 98 | return; | ||
| 99 | }; | ||
| 100 | |||
| 101 | // wait with a timeout and return if signalled via set() | ||
| 102 | if (reset_event.timedWait(timeout_ns)) |_| { | ||
| 103 | return; | ||
| 104 | } else |timed_out| {} | ||
| 105 | |||
| 106 | // If we timed out, we need to transition the AutoResetEvent back to UNSET. | ||
| 107 | // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent. | ||
| 108 | state = @cmpxchgStrong( | ||
| 109 | usize, | ||
| 110 | &self.state, | ||
| 111 | @ptrToInt(&reset_event), | ||
| 112 | UNSET, | ||
| 113 | .SeqCst, | ||
| 114 | .SeqCst, | ||
| 115 | ) orelse return error.TimedOut; | ||
| 116 | |||
| 117 | // We didn't manage to unregister ourselves from the state. | ||
| 118 | if (state == SET) { | ||
| 119 | unreachable; // AutoResetEvent notified without waking up the waiting thread | ||
| 120 | } else if (state != UNSET) { | ||
| 121 | unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out | ||
| 122 | } | ||
| 123 | |||
| 124 | // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up. | ||
| 125 | // We need to wait for it to wake up our ResetEvent before we can return and invalidate it. | ||
| 126 | // We don't return error.TimedOut here as it technically notified us while we were "timing out". | ||
| 127 | reset_event.wait(); | ||
| 128 | return; | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | pub fn set(self: *AutoResetEvent) void { | ||
| 133 | var state = @atomicLoad(usize, &self.state, .SeqCst); | ||
| 134 | while (true) { | ||
| 135 | // If the AutoResetEvent is already set, there is nothing else left to do | ||
| 136 | if (state == SET) { | ||
| 137 | return; | ||
| 138 | } | ||
| 139 | |||
| 140 | // If the AutoResetEvent isn't set, | ||
| 141 | // then try to leave a notification for the wait() thread that we set() it. | ||
| 142 | if (state == UNSET) { | ||
| 143 | state = @cmpxchgWeak( | ||
| 144 | usize, | ||
| 145 | &self.state, | ||
| 146 | UNSET, | ||
| 147 | SET, | ||
| 148 | .SeqCst, | ||
| 149 | .SeqCst, | ||
| 150 | ) orelse return; | ||
| 151 | continue; | ||
| 152 | } | ||
| 153 | |||
| 154 | // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting. | ||
| 155 | // Try to acquire ownership of it so that we can wake it up. | ||
| 156 | // This also resets the AutoResetEvent so that there is no race condition as defined above. | ||
| 157 | if (@cmpxchgWeak( | ||
| 158 | usize, | ||
| 159 | &self.state, | ||
| 160 | state, | ||
| 161 | UNSET, | ||
| 162 | .SeqCst, | ||
| 163 | .SeqCst, | ||
| 164 | )) |new_state| { | ||
| 165 | state = new_state; | ||
| 166 | continue; | ||
| 167 | } | ||
| 168 | |||
| 169 | const reset_event = @intToPtr(*align(event_align) std.ResetEvent, state); | ||
| 170 | reset_event.set(); | ||
| 171 | return; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | }; | ||
| 175 | |||
| 176 | test "std.AutoResetEvent" { | ||
| 177 | // test local code paths | ||
| 178 | { | ||
| 179 | var event = AutoResetEvent{}; | ||
| 180 | testing.expectError(error.TimedOut, event.timedWait(1)); | ||
| 181 | event.set(); | ||
| 182 | event.wait(); | ||
| 183 | } | ||
| 184 | |||
| 185 | // test cross-thread signaling | ||
| 186 | if (builtin.single_threaded) | ||
| 187 | return; | ||
| 188 | |||
| 189 | const Context = struct { | ||
| 190 | value: u128 = 0, | ||
| 191 | in: AutoResetEvent = AutoResetEvent{}, | ||
| 192 | out: AutoResetEvent = AutoResetEvent{}, | ||
| 193 | |||
| 194 | const Self = @This(); | ||
| 195 | |||
| 196 | fn sender(self: *Self) void { | ||
| 197 | testing.expect(self.value == 0); | ||
| 198 | self.value = 1; | ||
| 199 | self.out.set(); | ||
| 200 | |||
| 201 | self.in.wait(); | ||
| 202 | testing.expect(self.value == 2); | ||
| 203 | self.value = 3; | ||
| 204 | self.out.set(); | ||
| 205 | |||
| 206 | self.in.wait(); | ||
| 207 | testing.expect(self.value == 4); | ||
| 208 | } | ||
| 209 | |||
| 210 | fn receiver(self: *Self) void { | ||
| 211 | self.out.wait(); | ||
| 212 | testing.expect(self.value == 1); | ||
| 213 | self.value = 2; | ||
| 214 | self.in.set(); | ||
| 215 | |||
| 216 | self.out.wait(); | ||
| 217 | testing.expect(self.value == 3); | ||
| 218 | self.value = 4; | ||
| 219 | self.in.set(); | ||
| 220 | } | ||
| 221 | }; | ||
| 222 | |||
| 223 | var context = Context{}; | ||
| 224 | const send_thread = try std.Thread.spawn(&context, Context.sender); | ||
| 225 | const recv_thread = try std.Thread.spawn(&context, Context.receiver); | ||
| 226 | |||
| 227 | send_thread.wait(); | ||
| 228 | recv_thread.wait(); | ||
| 229 | } | ||
| \ No newline at end of file | |||
lib/std/event/loop.zig+157| ... | @@ -35,6 +35,9 @@ pub const Loop = struct { | ... | @@ -35,6 +35,9 @@ pub const Loop = struct { |
| 35 | /// This is only used by `Loop` for the thread pool and associated resources. | 35 | /// This is only used by `Loop` for the thread pool and associated resources. |
| 36 | arena: std.heap.ArenaAllocator, | 36 | arena: std.heap.ArenaAllocator, |
| 37 | 37 | ||
| 38 | /// State which manages frames that are sleeping on timers | ||
| 39 | delay_queue: DelayQueue, | ||
| 40 | |||
| 38 | /// Pre-allocated eventfds. All permanently active. | 41 | /// Pre-allocated eventfds. All permanently active. |
| 39 | /// This is how `Loop` sends promises to be resumed on other threads. | 42 | /// This is how `Loop` sends promises to be resumed on other threads. |
| 40 | available_eventfd_resume_nodes: std.atomic.Stack(ResumeNode.EventFd), | 43 | available_eventfd_resume_nodes: std.atomic.Stack(ResumeNode.EventFd), |
| ... | @@ -162,6 +165,7 @@ pub const Loop = struct { | ... | @@ -162,6 +165,7 @@ pub const Loop = struct { |
| 162 | .fs_queue = std.atomic.Queue(Request).init(), | 165 | .fs_queue = std.atomic.Queue(Request).init(), |
| 163 | .fs_thread = undefined, | 166 | .fs_thread = undefined, |
| 164 | .fs_thread_wakeup = std.ResetEvent.init(), | 167 | .fs_thread_wakeup = std.ResetEvent.init(), |
| 168 | .delay_queue = undefined, | ||
| 165 | }; | 169 | }; |
| 166 | errdefer self.fs_thread_wakeup.deinit(); | 170 | errdefer self.fs_thread_wakeup.deinit(); |
| 167 | errdefer self.arena.deinit(); | 171 | errdefer self.arena.deinit(); |
| ... | @@ -186,6 +190,9 @@ pub const Loop = struct { | ... | @@ -186,6 +190,9 @@ pub const Loop = struct { |
| 186 | self.posixFsRequest(&self.fs_end_request); | 190 | self.posixFsRequest(&self.fs_end_request); |
| 187 | self.fs_thread.wait(); | 191 | self.fs_thread.wait(); |
| 188 | }; | 192 | }; |
| 193 | |||
| 194 | if (!std.builtin.single_threaded) | ||
| 195 | try self.delay_queue.init(); | ||
| 189 | } | 196 | } |
| 190 | 197 | ||
| 191 | pub fn deinit(self: *Loop) void { | 198 | pub fn deinit(self: *Loop) void { |
| ... | @@ -645,6 +652,10 @@ pub const Loop = struct { | ... | @@ -645,6 +652,10 @@ pub const Loop = struct { |
| 645 | for (self.extra_threads) |extra_thread| { | 652 | for (self.extra_threads) |extra_thread| { |
| 646 | extra_thread.wait(); | 653 | extra_thread.wait(); |
| 647 | } | 654 | } |
| 655 | |||
| 656 | @atomicStore(bool, &self.delay_queue.is_running, false, .SeqCst); | ||
| 657 | self.delay_queue.event.set(); | ||
| 658 | self.delay_queue.thread.wait(); | ||
| 648 | } | 659 | } |
| 649 | 660 | ||
| 650 | /// Runs the provided function asynchronously. The function's frame is allocated | 661 | /// Runs the provided function asynchronously. The function's frame is allocated |
| ... | @@ -748,6 +759,128 @@ pub const Loop = struct { | ... | @@ -748,6 +759,128 @@ pub const Loop = struct { |
| 748 | } | 759 | } |
| 749 | } | 760 | } |
| 750 | 761 | ||
| 762 | pub fn sleep(self: *Loop, nanoseconds: u64) void { | ||
| 763 | if (std.builtin.single_threaded) | ||
| 764 | @compileError("TODO: integrate timers with epoll/kevent/iocp for single-threaded"); | ||
| 765 | |||
| 766 | suspend { | ||
| 767 | const now = self.delay_queue.timer.read(); | ||
| 768 | |||
| 769 | var entry: DelayQueue.Waiters.Entry = undefined; | ||
| 770 | entry.init(@frame(), now + nanoseconds); | ||
| 771 | self.delay_queue.waiters.insert(&entry); | ||
| 772 | |||
| 773 | // Speculatively wake up the timer thread when we add a new entry. | ||
| 774 | // If the timer thread is sleeping on a longer entry, we need to | ||
| 775 | // interrupt it so that our entry can be expired in time. | ||
| 776 | self.delay_queue.event.set(); | ||
| 777 | } | ||
| 778 | } | ||
| 779 | |||
| 780 | const DelayQueue = struct { | ||
| 781 | timer: std.time.Timer, | ||
| 782 | waiters: Waiters, | ||
| 783 | thread: *std.Thread, | ||
| 784 | event: std.AutoResetEvent, | ||
| 785 | is_running: bool, | ||
| 786 | |||
| 787 | /// Initialize the delay queue by spawning the timer thread | ||
| 788 | /// and starting any timer resources. | ||
| 789 | fn init(self: *DelayQueue) !void { | ||
| 790 | self.* = DelayQueue{ | ||
| 791 | .timer = try std.time.Timer.start(), | ||
| 792 | .waiters = DelayQueue.Waiters{ | ||
| 793 | .entries = std.atomic.Queue(anyframe).init(), | ||
| 794 | }, | ||
| 795 | .thread = try std.Thread.spawn(self, DelayQueue.run), | ||
| 796 | .event = std.AutoResetEvent{}, | ||
| 797 | .is_running = true, | ||
| 798 | }; | ||
| 799 | } | ||
| 800 | |||
| 801 | /// Entry point for the timer thread | ||
| 802 | /// which waits for timer entries to expire and reschedules them. | ||
| 803 | fn run(self: *DelayQueue) void { | ||
| 804 | const loop = @fieldParentPtr(Loop, "delay_queue", self); | ||
| 805 | |||
| 806 | while (@atomicLoad(bool, &self.is_running, .SeqCst)) { | ||
| 807 | const now = self.timer.read(); | ||
| 808 | |||
| 809 | if (self.waiters.popExpired(now)) |entry| { | ||
| 810 | loop.onNextTick(&entry.node); | ||
| 811 | continue; | ||
| 812 | } | ||
| 813 | |||
| 814 | if (self.waiters.nextExpire()) |expires| { | ||
| 815 | if (now >= expires) | ||
| 816 | continue; | ||
| 817 | self.event.timedWait(expires - now) catch {}; | ||
| 818 | } else { | ||
| 819 | self.event.wait(); | ||
| 820 | } | ||
| 821 | } | ||
| 822 | } | ||
| 823 | |||
| 824 | // TODO: use a tickless heirarchical timer wheel: | ||
| 825 | // https://github.com/wahern/timeout/ | ||
| 826 | const Waiters = struct { | ||
| 827 | entries: std.atomic.Queue(anyframe), | ||
| 828 | |||
| 829 | const Entry = struct { | ||
| 830 | node: NextTickNode, | ||
| 831 | expires: u64, | ||
| 832 | |||
| 833 | fn init(self: *Entry, frame: anyframe, expires: u64) void { | ||
| 834 | self.node.data = frame; | ||
| 835 | self.expires = expires; | ||
| 836 | } | ||
| 837 | }; | ||
| 838 | |||
| 839 | /// Registers the entry into the queue of waiting frames | ||
| 840 | fn insert(self: *Waiters, entry: *Entry) void { | ||
| 841 | self.entries.put(&entry.node); | ||
| 842 | } | ||
| 843 | |||
| 844 | /// Dequeues one expired event relative to `now` | ||
| 845 | fn popExpired(self: *Waiters, now: u64) ?*Entry { | ||
| 846 | const entry = self.peekExpiringEntry() orelse return null; | ||
| 847 | if (entry.expires > now) | ||
| 848 | return null; | ||
| 849 | |||
| 850 | assert(self.entries.remove(&entry.node)); | ||
| 851 | return entry; | ||
| 852 | } | ||
| 853 | |||
| 854 | /// Returns an estimate for the amount of time | ||
| 855 | /// to wait until the next waiting entry expires. | ||
| 856 | fn nextExpire(self: *Waiters) ?u64 { | ||
| 857 | const entry = self.peekExpiringEntry() orelse return null; | ||
| 858 | return entry.expires; | ||
| 859 | } | ||
| 860 | |||
| 861 | fn peekExpiringEntry(self: *Waiters) ?*Entry { | ||
| 862 | const held = self.entries.mutex.acquire(); | ||
| 863 | defer held.release(); | ||
| 864 | |||
| 865 | // starting from the head | ||
| 866 | var head = self.entries.head orelse return null; | ||
| 867 | |||
| 868 | // traverse the list of waiting entires to | ||
| 869 | // find the Node with the smallest `expires` field | ||
| 870 | var min = head; | ||
| 871 | while (head.next) |node| { | ||
| 872 | const minEntry = @fieldParentPtr(Entry, "node", min); | ||
| 873 | const nodeEntry = @fieldParentPtr(Entry, "node", node); | ||
| 874 | if (nodeEntry.expires < minEntry.expires) | ||
| 875 | min = node; | ||
| 876 | head = node; | ||
| 877 | } | ||
| 878 | |||
| 879 | return @fieldParentPtr(Entry, "node", min); | ||
| 880 | } | ||
| 881 | }; | ||
| 882 | }; | ||
| 883 | |||
| 751 | /// ------- I/0 APIs ------- | 884 | /// ------- I/0 APIs ------- |
| 752 | pub fn accept( | 885 | pub fn accept( |
| 753 | self: *Loop, | 886 | self: *Loop, |
| ... | @@ -1550,3 +1683,27 @@ test "std.event.Loop - runDetached" { | ... | @@ -1550,3 +1683,27 @@ test "std.event.Loop - runDetached" { |
| 1550 | fn testRunDetached() void { | 1683 | fn testRunDetached() void { |
| 1551 | testRunDetachedData += 1; | 1684 | testRunDetachedData += 1; |
| 1552 | } | 1685 | } |
| 1686 | |||
| 1687 | test "std.event.Loop - sleep" { | ||
| 1688 | // https://github.com/ziglang/zig/issues/1908 | ||
| 1689 | if (builtin.single_threaded) return error.SkipZigTest; | ||
| 1690 | if (!std.io.is_async) return error.SkipZigTest; | ||
| 1691 | |||
| 1692 | const frames = try testing.allocator.alloc(@Frame(testSleep), 10); | ||
| 1693 | defer testing.allocator.free(frames); | ||
| 1694 | |||
| 1695 | const wait_time = 100 * std.time.ns_per_ms; | ||
| 1696 | var sleep_count: usize = 0; | ||
| 1697 | |||
| 1698 | for (frames) |*frame| | ||
| 1699 | frame.* = async testSleep(wait_time, &sleep_count); | ||
| 1700 | for (frames) |*frame| | ||
| 1701 | await frame; | ||
| 1702 | |||
| 1703 | testing.expect(sleep_count == frames.len); | ||
| 1704 | } | ||
| 1705 | |||
| 1706 | fn testSleep(wait_ns: u64, sleep_count: *usize) void { | ||
| 1707 | Loop.instance.?.sleep(wait_ns); | ||
| 1708 | _ = @atomicRmw(usize, sleep_count, .Add, 1, .SeqCst); | ||
| 1709 | } |
lib/std/std.zig+1| ... | @@ -14,6 +14,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; | ... | @@ -14,6 +14,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; |
| 14 | pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; | 14 | pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; |
| 15 | pub const AutoHashMap = hash_map.AutoHashMap; | 15 | pub const AutoHashMap = hash_map.AutoHashMap; |
| 16 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; | 16 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; |
| 17 | pub const AutoResetEvent = @import("auto_reset_event.zig").AutoResetEvent; | ||
| 17 | pub const BufMap = @import("buf_map.zig").BufMap; | 18 | pub const BufMap = @import("buf_map.zig").BufMap; |
| 18 | pub const BufSet = @import("buf_set.zig").BufSet; | 19 | pub const BufSet = @import("buf_set.zig").BufSet; |
| 19 | pub const ChildProcess = @import("child_process.zig").ChildProcess; | 20 | pub const ChildProcess = @import("child_process.zig").ChildProcess; |
lib/std/time.zig+4-1| ... | @@ -14,8 +14,11 @@ const is_windows = std.Target.current.os.tag == .windows; | ... | @@ -14,8 +14,11 @@ const is_windows = std.Target.current.os.tag == .windows; |
| 14 | pub const epoch = @import("time/epoch.zig"); | 14 | pub const epoch = @import("time/epoch.zig"); |
| 15 | 15 | ||
| 16 | /// Spurious wakeups are possible and no precision of timing is guaranteed. | 16 | /// Spurious wakeups are possible and no precision of timing is guaranteed. |
| 17 | /// TODO integrate with evented I/O | ||
| 18 | pub fn sleep(nanoseconds: u64) void { | 17 | pub fn sleep(nanoseconds: u64) void { |
| 18 | // TODO: opting out of async sleeping? | ||
| 19 | if (std.io.is_async) | ||
| 20 | return std.event.Loop.instance.?.sleep(nanoseconds); | ||
| 21 | |||
| 19 | if (is_windows) { | 22 | if (is_windows) { |
| 20 | const big_ms_from_ns = nanoseconds / ns_per_ms; | 23 | const big_ms_from_ns = nanoseconds / ns_per_ms; |
| 21 | const ms = math.cast(os.windows.DWORD, big_ms_from_ns) catch math.maxInt(os.windows.DWORD); | 24 | const ms = math.cast(os.windows.DWORD, big_ms_from_ns) catch math.maxInt(os.windows.DWORD); |