| author | |
| committer | |
| log | 66cb75d1148fffdd161e7829b9e27aa52f0f1616 |
| tree | f682927047f57940ebbd33ab81e254d69b731a39 |
| parent | acefcdbca58efd97bf5346eb7dae22c49efa1a3d |
| signature | Commit is signed but in an unrecognized format. |
closes #1463
Thanks to Shawn Landden for the original pull request.
This commit is based on that code.7 files changed, 106 insertions(+), 24 deletions(-)
CMakeLists.txt+1| ... | ... | @@ -635,6 +635,7 @@ set(ZIG_STD_FILES |
| 635 | 635 | "special/init-lib/src/main.zig" |
| 636 | 636 | "special/panic.zig" |
| 637 | 637 | "special/test_runner.zig" |
| 638 | "spinlock.zig" | |
| 638 | 639 | "unicode.zig" |
| 639 | 640 | "zig/ast.zig" |
| 640 | 641 | "zig/index.zig" |
std/event/loop.zig+6-8| ... | ... | @@ -736,8 +736,8 @@ pub const Loop = struct { |
| 736 | 736 | _ = os.bsdKEvent(self.os_data.fs_kqfd, fs_kevs, empty_kevs, null) catch unreachable; |
| 737 | 737 | }, |
| 738 | 738 | builtin.Os.linux => { |
| 739 | _ = @atomicRmw(u8, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 740 | const rc = os.linux.futex_wake(@ptrToInt(&self.os_data.fs_queue_item), os.linux.FUTEX_WAKE, 1); | |
| 739 | _ = @atomicRmw(i32, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 740 | const rc = os.linux.futex_wake(&self.os_data.fs_queue_item, os.linux.FUTEX_WAKE, 1); | |
| 741 | 741 | switch (os.linux.getErrno(rc)) { |
| 742 | 742 | 0 => {}, |
| 743 | 743 | posix.EINVAL => unreachable, |
| ... | ... | @@ -757,7 +757,7 @@ pub const Loop = struct { |
| 757 | 757 | fn posixFsRun(self: *Loop) void { |
| 758 | 758 | while (true) { |
| 759 | 759 | if (builtin.os == builtin.Os.linux) { |
| 760 | _ = @atomicRmw(u8, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | |
| 760 | _ = @atomicRmw(i32, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | |
| 761 | 761 | } |
| 762 | 762 | while (self.os_data.fs_queue.get()) |node| { |
| 763 | 763 | switch (node.data.msg) { |
| ... | ... | @@ -794,11 +794,9 @@ pub const Loop = struct { |
| 794 | 794 | } |
| 795 | 795 | switch (builtin.os) { |
| 796 | 796 | builtin.Os.linux => { |
| 797 | const rc = os.linux.futex_wait(@ptrToInt(&self.os_data.fs_queue_item), os.linux.FUTEX_WAIT, 0, null); | |
| 797 | const rc = os.linux.futex_wait(&self.os_data.fs_queue_item, os.linux.FUTEX_WAIT, 0, null); | |
| 798 | 798 | switch (os.linux.getErrno(rc)) { |
| 799 | 0 => continue, | |
| 800 | posix.EINTR => continue, | |
| 801 | posix.EAGAIN => continue, | |
| 799 | 0, posix.EINTR, posix.EAGAIN => continue, | |
| 802 | 800 | else => unreachable, |
| 803 | 801 | } |
| 804 | 802 | }, |
| ... | ... | @@ -838,7 +836,7 @@ pub const Loop = struct { |
| 838 | 836 | final_eventfd: i32, |
| 839 | 837 | final_eventfd_event: os.linux.epoll_event, |
| 840 | 838 | fs_thread: *os.Thread, |
| 841 | fs_queue_item: u8, | |
| 839 | fs_queue_item: i32, | |
| 842 | 840 | fs_queue: std.atomic.Queue(fs.Request), |
| 843 | 841 | fs_end_request: fs.RequestNode, |
| 844 | 842 | }; |
std/index.zig+8-6| ... | ... | @@ -1,15 +1,16 @@ |
| 1 | pub const ArrayList = @import("array_list.zig").ArrayList; | |
| 2 | 1 | pub const AlignedArrayList = @import("array_list.zig").AlignedArrayList; |
| 2 | pub const ArrayList = @import("array_list.zig").ArrayList; | |
| 3 | pub const AutoHashMap = @import("hash_map.zig").AutoHashMap; | |
| 3 | 4 | pub const BufMap = @import("buf_map.zig").BufMap; |
| 4 | 5 | pub const BufSet = @import("buf_set.zig").BufSet; |
| 5 | 6 | pub const Buffer = @import("buffer.zig").Buffer; |
| 6 | 7 | pub const BufferOutStream = @import("io.zig").BufferOutStream; |
| 8 | pub const DynLib = @import("dynamic_library.zig").DynLib; | |
| 7 | 9 | pub const HashMap = @import("hash_map.zig").HashMap; |
| 8 | pub const AutoHashMap = @import("hash_map.zig").AutoHashMap; | |
| 9 | 10 | pub const LinkedList = @import("linked_list.zig").LinkedList; |
| 10 | pub const SegmentedList = @import("segmented_list.zig").SegmentedList; | |
| 11 | pub const DynLib = @import("dynamic_library.zig").DynLib; | |
| 12 | 11 | pub const Mutex = @import("mutex.zig").Mutex; |
| 12 | pub const SegmentedList = @import("segmented_list.zig").SegmentedList; | |
| 13 | pub const SpinLock = @import("spinlock.zig").SpinLock; | |
| 13 | 14 | |
| 14 | 15 | pub const atomic = @import("atomic/index.zig"); |
| 15 | 16 | pub const base64 = @import("base64.zig"); |
| ... | ... | @@ -45,15 +46,16 @@ pub const lazyInit = @import("lazy_init.zig").lazyInit; |
| 45 | 46 | |
| 46 | 47 | test "std" { |
| 47 | 48 | // run tests from these |
| 48 | _ = @import("atomic/index.zig"); | |
| 49 | 49 | _ = @import("array_list.zig"); |
| 50 | _ = @import("atomic/index.zig"); | |
| 50 | 51 | _ = @import("buf_map.zig"); |
| 51 | 52 | _ = @import("buf_set.zig"); |
| 52 | 53 | _ = @import("buffer.zig"); |
| 53 | 54 | _ = @import("hash_map.zig"); |
| 54 | 55 | _ = @import("linked_list.zig"); |
| 55 | _ = @import("segmented_list.zig"); | |
| 56 | 56 | _ = @import("mutex.zig"); |
| 57 | _ = @import("segmented_list.zig"); | |
| 58 | _ = @import("spinlock.zig"); | |
| 57 | 59 | |
| 58 | 60 | _ = @import("base64.zig"); |
| 59 | 61 | _ = @import("build.zig"); |
std/mutex.zig+54-5| ... | ... | @@ -3,25 +3,74 @@ const builtin = @import("builtin"); |
| 3 | 3 | const AtomicOrder = builtin.AtomicOrder; |
| 4 | 4 | const AtomicRmwOp = builtin.AtomicRmwOp; |
| 5 | 5 | const assert = std.debug.assert; |
| 6 | const SpinLock = std.SpinLock; | |
| 7 | const linux = std.os.linux; | |
| 6 | 8 | |
| 7 | /// TODO use syscalls instead of a spinlock | |
| 9 | /// Lock may be held only once. If the same thread | |
| 10 | /// tries to acquire the same mutex twice, it deadlocks. | |
| 8 | 11 | pub const Mutex = struct { |
| 9 | lock: u8, // TODO use a bool | |
| 12 | /// 0: unlocked | |
| 13 | /// 1: locked, no waiters | |
| 14 | /// 2: locked, one or more waiters | |
| 15 | linux_lock: @typeOf(linux_lock_init), | |
| 16 | ||
| 17 | /// TODO better implementation than spin lock | |
| 18 | spin_lock: @typeOf(spin_lock_init), | |
| 19 | ||
| 20 | const linux_lock_init = if (builtin.os == builtin.Os.linux) i32(0) else {}; | |
| 21 | const spin_lock_init = if (builtin.os != builtin.Os.linux) SpinLock.init() else {}; | |
| 10 | 22 | |
| 11 | 23 | pub const Held = struct { |
| 12 | 24 | mutex: *Mutex, |
| 13 | 25 | |
| 14 | 26 | pub fn release(self: Held) void { |
| 15 | assert(@atomicRmw(u8, &self.mutex.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); | |
| 27 | if (builtin.os == builtin.Os.linux) { | |
| 28 | // Always unlock. If the previous state was Locked-No-Waiters, then we're done. | |
| 29 | // Otherwise, wake a waiter up. | |
| 30 | const prev = @atomicRmw(i32, &self.mutex.linux_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.Release); | |
| 31 | if (prev != 1) { | |
| 32 | assert(prev == 2); | |
| 33 | const rc = linux.futex_wake(&self.mutex.linux_lock, linux.FUTEX_WAKE, 1); | |
| 34 | switch (linux.getErrno(rc)) { | |
| 35 | 0 => {}, | |
| 36 | linux.EINVAL => unreachable, | |
| 37 | else => unreachable, | |
| 38 | } | |
| 39 | } | |
| 40 | } else { | |
| 41 | SpinLock.Held.release(SpinLock.Held{ .spinlock = &self.mutex.spin_lock }); | |
| 42 | } | |
| 16 | 43 | } |
| 17 | 44 | }; |
| 18 | 45 | |
| 19 | 46 | pub fn init() Mutex { |
| 20 | return Mutex{ .lock = 0 }; | |
| 47 | return Mutex{ | |
| 48 | .linux_lock = linux_lock_init, | |
| 49 | .spin_lock = spin_lock_init, | |
| 50 | }; | |
| 21 | 51 | } |
| 22 | 52 | |
| 23 | 53 | pub fn acquire(self: *Mutex) Held { |
| 24 | while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} | |
| 54 | if (builtin.os == builtin.Os.linux) { | |
| 55 | // First try to go from Unlocked to Locked-No-Waiters. If this succeeds, no syscalls are needed. | |
| 56 | // Otherwise, we need to be in the Locked-With-Waiters state. If we are already in that state, | |
| 57 | // proceed to futex_wait. Otherwise, try to go from Locked-No-Waiters to Locked-With-Waiters. | |
| 58 | // If that succeeds, proceed to futex_wait. Otherwise start the whole loop over again. | |
| 59 | while (@cmpxchgWeak(i32, &self.linux_lock, 0, 1, AtomicOrder.Acquire, AtomicOrder.Monotonic)) |l| { | |
| 60 | if (l == 2 or | |
| 61 | @cmpxchgWeak(i32, &self.linux_lock, 1, 2, AtomicOrder.Acquire, AtomicOrder.Monotonic) == null) | |
| 62 | { | |
| 63 | const rc = linux.futex_wait(&self.linux_lock, linux.FUTEX_WAIT, 2, null); | |
| 64 | switch (linux.getErrno(rc)) { | |
| 65 | 0, linux.EINTR, linux.EAGAIN => continue, | |
| 66 | linux.EINVAL => unreachable, | |
| 67 | else => unreachable, | |
| 68 | } | |
| 69 | } | |
| 70 | } | |
| 71 | } else { | |
| 72 | _ = self.spin_lock.acquire(); | |
| 73 | } | |
| 25 | 74 | return Held{ .mutex = self }; |
| 26 | 75 | } |
| 27 | 76 | }; |
std/os/index.zig+1-1| ... | ... | @@ -2839,7 +2839,7 @@ pub const Thread = struct { |
| 2839 | 2839 | while (true) { |
| 2840 | 2840 | const pid_value = @atomicLoad(i32, &self.data.handle, builtin.AtomicOrder.SeqCst); |
| 2841 | 2841 | if (pid_value == 0) break; |
| 2842 | const rc = linux.futex_wait(@ptrToInt(&self.data.handle), linux.FUTEX_WAIT, pid_value, null); | |
| 2842 | const rc = linux.futex_wait(&self.data.handle, linux.FUTEX_WAIT, pid_value, null); | |
| 2843 | 2843 | switch (linux.getErrno(rc)) { |
| 2844 | 2844 | 0 => continue, |
| 2845 | 2845 | posix.EINTR => continue, |
std/os/linux/index.zig+4-4| ... | ... | @@ -728,12 +728,12 @@ pub inline fn vfork() usize { |
| 728 | 728 | return @inlineCall(syscall0, SYS_vfork); |
| 729 | 729 | } |
| 730 | 730 | |
| 731 | pub fn futex_wait(uaddr: usize, futex_op: u32, val: i32, timeout: ?*timespec) usize { | |
| 732 | return syscall4(SYS_futex, uaddr, futex_op, @bitCast(u32, val), @ptrToInt(timeout)); | |
| 731 | pub fn futex_wait(uaddr: *const i32, futex_op: u32, val: i32, timeout: ?*timespec) usize { | |
| 732 | return syscall4(SYS_futex, @ptrToInt(uaddr), futex_op, @bitCast(u32, val), @ptrToInt(timeout)); | |
| 733 | 733 | } |
| 734 | 734 | |
| 735 | pub fn futex_wake(uaddr: usize, futex_op: u32, val: i32) usize { | |
| 736 | return syscall3(SYS_futex, uaddr, futex_op, @bitCast(u32, val)); | |
| 735 | pub fn futex_wake(uaddr: *const i32, futex_op: u32, val: i32) usize { | |
| 736 | return syscall3(SYS_futex, @ptrToInt(uaddr), futex_op, @bitCast(u32, val)); | |
| 737 | 737 | } |
| 738 | 738 | |
| 739 | 739 | pub fn getcwd(buf: [*]u8, size: usize) usize { |
std/spinlock.zig created+32| ... | ... | @@ -0,0 +1,32 @@ |
| 1 | const std = @import("index.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const AtomicOrder = builtin.AtomicOrder; | |
| 4 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 5 | const assert = std.debug.assert; | |
| 6 | ||
| 7 | pub const SpinLock = struct { | |
| 8 | lock: u8, // TODO use a bool or enum | |
| 9 | ||
| 10 | pub const Held = struct { | |
| 11 | spinlock: *SpinLock, | |
| 12 | ||
| 13 | pub fn release(self: Held) void { | |
| 14 | assert(@atomicRmw(u8, &self.spinlock.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); | |
| 15 | } | |
| 16 | }; | |
| 17 | ||
| 18 | pub fn init() SpinLock { | |
| 19 | return SpinLock{ .lock = 0 }; | |
| 20 | } | |
| 21 | ||
| 22 | pub fn acquire(self: *SpinLock) Held { | |
| 23 | while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} | |
| 24 | return Held{ .spinlock = self }; | |
| 25 | } | |
| 26 | }; | |
| 27 | ||
| 28 | test "spinlock" { | |
| 29 | var lock = SpinLock.init(); | |
| 30 | const held = lock.acquire(); | |
| 31 | defer held.release(); | |
| 32 | } |