authorgravatar for 45520026+kprotty@users.noreply.github.comkprotty <45520026+kprotty@users.noreply.github.com> 2020-09-27 14:05:38-05:00
committergravatar for 45520026+kprotty@users.noreply.github.comkprotty <45520026+kprotty@users.noreply.github.com> 2020-09-27 14:05:38-05:00
loga31d9f92f282a878836a3ecac5a48d5f4037868c
treea8fc0335698ab4b3fc2aa71eb1cf91a5cb1c9d5c
parent8794ce6f79886e5ebbf0476d56917e219b52c561

new std.event.Lock implementation


1 files changed, 72 insertions(+), 114 deletions(-)

lib/std/event/lock.zig+72-114
...@@ -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.
18pub const Lock = struct {18pub const Lock = struct {
19 shared: bool,19 mutex: std.Mutex = std.Mutex{},
20 queue: Queue,20 head: usize = UNLOCKED,
21 queue_empty: bool,
2221
23 const Queue = std.atomic.Queue(anyframe);22 const UNLOCKED = 0;
23 const LOCKED = 69;
2424
25 const global_event_loop = Loop.instance orelse25 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");
2727
28 pub const Held = struct {28 const Waiter = struct {
29 lock: *Lock,29 next: ?*Waiter,
3030 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 }
6133
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);
6536
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 };
7342
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 }
8146
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 }
9652
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 }
9959
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{
10362 .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 }
10568
106 // We set this bit so that later we can rely on the fact, that if queue_empty == true, some actor69 return Held{ .lock = self };
107 // will attempt to grab the lock.70 }
108 @atomicStore(bool, &self.queue_empty, false, .SeqCst);
10971
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 }
117102 };
118 return Held{ .lock = self };
119 }
120};103};
121104
122test "std.event.Lock" {105test "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/3251111 // 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;
130113
131 // TODO this file has bit-rotted. repair it114 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);
138116
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}
142fn testLock(lock: *Lock) callconv(.Async) void {120fn 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);
166124
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 {
171129
172var shared_test_data = [1]i32{0} ** 10;130var shared_test_data = [1]i32{0} ** 10;
173var shared_test_index: usize = 0;131var shared_test_index: usize = 0;
174fn lockRunner(lock: *Lock) callconv(.Async) void {132
175 suspend; // resumed by onNextTick133fn lockRunner(lock: *Lock) void {
134 Lock.global_event_loop.yield();
176135
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();
182140
183 shared_test_index = 0;141 shared_test_index = 0;