| ... | @@ -16,107 +16,90 @@ const Loop = std.event.Loop; | ... | @@ -16,107 +16,90 @@ const Loop = std.event.Loop; |
| 16 | /// Allows only one actor to hold the lock. | 16 | /// Allows only one actor to hold the lock. |
| 17 | /// TODO: make this API also work in blocking I/O mode. | 17 | /// TODO: make this API also work in blocking I/O mode. |
| 18 | pub const Lock = struct { | 18 | pub const Lock = struct { |
| 19 | shared: bool, | 19 | mutex: std.Mutex = std.Mutex{}, |
| 20 | queue: Queue, | 20 | head: usize = UNLOCKED, |
| 21 | queue_empty: bool, | | |
| 22 | | 21 | |
| 23 | const Queue = std.atomic.Queue(anyframe); | 22 | const UNLOCKED = 0; |
| | 23 | const LOCKED = 69; |
| 24 | | 24 | |
| 25 | const global_event_loop = Loop.instance orelse | 25 | const global_event_loop = Loop.instance orelse |
| 26 | @compileError("std.event.Lock currently only works with event-based I/O"); | 26 | @compileError("std.event.Lock currently only works with event-based I/O"); |
| 27 | | 27 | |
| 28 | pub const Held = struct { | 28 | const Waiter = struct { |
| 29 | lock: *Lock, | 29 | next: ?*Waiter, |
| 30 | | 30 | tail: *Waiter, |
| 31 | pub fn release(self: Held) void { | 31 | node: Loop.NextTickNode, |
| 32 | // Resume the next item from the queue. | 32 | }; |
| 33 | if (self.lock.queue.get()) |node| { | | |
| 34 | global_event_loop.onNextTick(node); | | |
| 35 | return; | | |
| 36 | } | | |
| 37 | | | |
| 38 | // We need to release the lock. | | |
| 39 | @atomicStore(bool, &self.lock.queue_empty, true, .SeqCst); | | |
| 40 | @atomicStore(bool, &self.lock.shared, false, .SeqCst); | | |
| 41 | | | |
| 42 | // There might be a queue item. If we know the queue is empty, we can be done, | | |
| 43 | // because the other actor will try to obtain the lock. | | |
| 44 | // But if there's a queue item, we are the actor which must loop and attempt | | |
| 45 | // to grab the lock again. | | |
| 46 | if (@atomicLoad(bool, &self.lock.queue_empty, .SeqCst)) { | | |
| 47 | return; | | |
| 48 | } | | |
| 49 | | | |
| 50 | while (true) { | | |
| 51 | if (@atomicRmw(bool, &self.lock.shared, .Xchg, true, .SeqCst)) { | | |
| 52 | // We did not obtain the lock. Great, the queue is someone else's problem. | | |
| 53 | return; | | |
| 54 | } | | |
| 55 | | | |
| 56 | // Resume the next item from the queue. | | |
| 57 | if (self.lock.queue.get()) |node| { | | |
| 58 | global_event_loop.onNextTick(node); | | |
| 59 | return; | | |
| 60 | } | | |
| 61 | | 33 | |
| 62 | // Release the lock again. | 34 | pub fn acquire(self: *Lock) Held { |
| 63 | @atomicStore(bool, &self.lock.queue_empty, true, .SeqCst); | 35 | const held = self.mutex.acquire(); |
| 64 | @atomicStore(bool, &self.lock.shared, false, .SeqCst); | | |
| 65 | | 36 | |
| 66 | // Find out if we can be done. | 37 | if (self.head == UNLOCKED) { |
| 67 | if (@atomicLoad(bool, &self.lock.queue_empty, .SeqCst)) { | 38 | self.head = LOCKED; |
| 68 | return; | 39 | held.release(); |
| 69 | } | 40 | return Held{ .lock = self }; |
| 70 | } | | |
| 71 | } | 41 | } |
| 72 | }; | | |
| 73 | | 42 | |
| 74 | pub fn init() Lock { | 43 | var waiter: Waiter = undefined; |
| 75 | return Lock{ | 44 | waiter.next = null; |
| 76 | .shared = false, | 45 | waiter.tail = &waiter; |
| 77 | .queue = Queue.init(), | | |
| 78 | .queue_empty = true, | | |
| 79 | }; | | |
| 80 | } | | |
| 81 | | 46 | |
| 82 | pub fn initLocked() Lock { | 47 | const head = switch (self.head) { |
| 83 | return Lock{ | 48 | UNLOCKED => unreachable, |
| 84 | .shared = true, | 49 | LOCKED => null, |
| 85 | .queue = Queue.init(), | 50 | else => @intToPtr(?*Waiter, self.head), |
| 86 | .queue_empty = true, | | |
| 87 | }; | 51 | }; |
| 88 | } | | |
| 89 | | | |
| 90 | /// Must be called when not locked. Not thread safe. | | |
| 91 | /// All calls to acquire() and release() must complete before calling deinit(). | | |
| 92 | pub fn deinit(self: *Lock) void { | | |
| 93 | assert(!self.shared); | | |
| 94 | while (self.queue.get()) |node| resume node.data; | | |
| 95 | } | | |
| 96 | | 52 | |
| 97 | pub fn acquire(self: *Lock) callconv(.Async) Held { | 53 | if (head) |h| { |
| 98 | var my_tick_node = Loop.NextTickNode.init(@frame()); | 54 | h.tail.next = &waiter; |
| | 55 | h.tail = &waiter; |
| | 56 | } else { |
| | 57 | self.head = @ptrToInt(&waiter); |
| | 58 | } |
| 99 | | 59 | |
| 100 | errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire | | |
| 101 | suspend { | 60 | suspend { |
| 102 | self.queue.put(&my_tick_node); | 61 | waiter.node = Loop.NextTickNode{ |
| 103 | | 62 | .prev = undefined, |
| 104 | // At this point, we are in the queue, so we might have already been resumed. | 63 | .next = undefined, |
| | 64 | .data = @frame(), |
| | 65 | }; |
| | 66 | held.release(); |
| | 67 | } |
| 105 | | 68 | |
| 106 | // We set this bit so that later we can rely on the fact, that if queue_empty == true, some actor | 69 | return Held{ .lock = self }; |
| 107 | // will attempt to grab the lock. | 70 | } |
| 108 | @atomicStore(bool, &self.queue_empty, false, .SeqCst); | | |
| 109 | | 71 | |
| 110 | if (!@atomicRmw(bool, &self.shared, .Xchg, true, .SeqCst)) { | 72 | pub const Held = struct { |
| 111 | if (self.queue.get()) |node| { | 73 | lock: *Lock, |
| 112 | // Whether this node is us or someone else, we tail resume it. | 74 | |
| 113 | resume node.data; | 75 | pub fn release(self: Held) void { |
| | 76 | const waiter = blk: { |
| | 77 | const held = self.lock.mutex.acquire(); |
| | 78 | defer held.release(); |
| | 79 | |
| | 80 | switch (self.lock.head) { |
| | 81 | UNLOCKED => { |
| | 82 | std.debug.panic("Lock unlocked when already unlocked", .{}); |
| | 83 | }, |
| | 84 | LOCKED => { |
| | 85 | self.lock.head = UNLOCKED; |
| | 86 | break :blk null; |
| | 87 | }, |
| | 88 | else => { |
| | 89 | const waiter = @intToPtr(*Waiter, self.lock.head); |
| | 90 | self.lock.head = if (waiter.next == null) LOCKED else @ptrToInt(waiter.next); |
| | 91 | if (waiter.next) |next| |
| | 92 | next.tail = waiter.tail; |
| | 93 | break :blk waiter; |
| | 94 | }, |
| 114 | } | 95 | } |
| | 96 | }; |
| | 97 | |
| | 98 | if (waiter) |w| { |
| | 99 | global_event_loop.onNextTick(&w.node); |
| 115 | } | 100 | } |
| 116 | } | 101 | } |
| 117 | | 102 | }; |
| 118 | return Held{ .lock = self }; | | |
| 119 | } | | |
| 120 | }; | 103 | }; |
| 121 | | 104 | |
| 122 | test "std.event.Lock" { | 105 | test "std.event.Lock" { |
| ... | @@ -128,41 +111,16 @@ test "std.event.Lock" { | ... | @@ -128,41 +111,16 @@ test "std.event.Lock" { |
| 128 | // TODO https://github.com/ziglang/zig/issues/3251 | 111 | // TODO https://github.com/ziglang/zig/issues/3251 |
| 129 | if (builtin.os.tag == .freebsd) return error.SkipZigTest; | 112 | if (builtin.os.tag == .freebsd) return error.SkipZigTest; |
| 130 | | 113 | |
| 131 | // TODO this file has bit-rotted. repair it | 114 | var lock = Lock{}; |
| 132 | if (true) return error.SkipZigTest; | 115 | testLock(&lock); |
| 133 | | | |
| 134 | var lock = Lock.init(); | | |
| 135 | defer lock.deinit(); | | |
| 136 | | | |
| 137 | _ = async testLock(&lock); | | |
| 138 | | 116 | |
| 139 | const expected_result = [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len; | 117 | const expected_result = [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len; |
| 140 | testing.expectEqualSlices(i32, &expected_result, &shared_test_data); | 118 | testing.expectEqualSlices(i32, &expected_result, &shared_test_data); |
| 141 | } | 119 | } |
| 142 | fn testLock(lock: *Lock) callconv(.Async) void { | 120 | fn testLock(lock: *Lock) void { |
| 143 | var handle1 = async lockRunner(lock); | 121 | var handle1 = async lockRunner(lock); |
| 144 | var tick_node1 = Loop.NextTickNode{ | | |
| 145 | .prev = undefined, | | |
| 146 | .next = undefined, | | |
| 147 | .data = &handle1, | | |
| 148 | }; | | |
| 149 | Loop.instance.?.onNextTick(&tick_node1); | | |
| 150 | | | |
| 151 | var handle2 = async lockRunner(lock); | 122 | var handle2 = async lockRunner(lock); |
| 152 | var tick_node2 = Loop.NextTickNode{ | | |
| 153 | .prev = undefined, | | |
| 154 | .next = undefined, | | |
| 155 | .data = &handle2, | | |
| 156 | }; | | |
| 157 | Loop.instance.?.onNextTick(&tick_node2); | | |
| 158 | | | |
| 159 | var handle3 = async lockRunner(lock); | 123 | var handle3 = async lockRunner(lock); |
| 160 | var tick_node3 = Loop.NextTickNode{ | | |
| 161 | .prev = undefined, | | |
| 162 | .next = undefined, | | |
| 163 | .data = &handle3, | | |
| 164 | }; | | |
| 165 | Loop.instance.?.onNextTick(&tick_node3); | | |
| 166 | | 124 | |
| 167 | await handle1; | 125 | await handle1; |
| 168 | await handle2; | 126 | await handle2; |
| ... | @@ -171,13 +129,13 @@ fn testLock(lock: *Lock) callconv(.Async) void { | ... | @@ -171,13 +129,13 @@ fn testLock(lock: *Lock) callconv(.Async) void { |
| 171 | | 129 | |
| 172 | var shared_test_data = [1]i32{0} ** 10; | 130 | var shared_test_data = [1]i32{0} ** 10; |
| 173 | var shared_test_index: usize = 0; | 131 | var shared_test_index: usize = 0; |
| 174 | fn lockRunner(lock: *Lock) callconv(.Async) void { | 132 | |
| 175 | suspend; // resumed by onNextTick | 133 | fn lockRunner(lock: *Lock) void { |
| | 134 | Lock.global_event_loop.yield(); |
| 176 | | 135 | |
| 177 | var i: usize = 0; | 136 | var i: usize = 0; |
| 178 | while (i < shared_test_data.len) : (i += 1) { | 137 | while (i < shared_test_data.len) : (i += 1) { |
| 179 | var lock_frame = async lock.acquire(); | 138 | const handle = lock.acquire(); |
| 180 | const handle = await lock_frame; | | |
| 181 | defer handle.release(); | 139 | defer handle.release(); |
| 182 | | 140 | |
| 183 | shared_test_index = 0; | 141 | shared_test_index = 0; |