| author | |
| committer | |
| log | 0874a5ba77a1d049a0e9e7f9f249605c109a731c |
| tree | 7bd78eb0b0f313877c32de5b828f0206580de060 |
| parent | 4a35d7eeebec3f345e2482bc189f07c19dcf6f8b |
5 files changed, 364 insertions(+), 143 deletions(-)
CMakeLists.txt+2-1| ... | ... | @@ -431,7 +431,8 @@ set(ZIG_CPP_SOURCES |
| 431 | 431 | set(ZIG_STD_FILES |
| 432 | 432 | "array_list.zig" |
| 433 | 433 | "atomic/index.zig" |
| 434 | "atomic/queue.zig" | |
| 434 | "atomic/queue_mpmc.zig" | |
| 435 | "atomic/queue_mpsc.zig" | |
| 435 | 436 | "atomic/stack.zig" |
| 436 | 437 | "base64.zig" |
| 437 | 438 | "buf_map.zig" |
std/atomic/index.zig+5-3| ... | ... | @@ -1,7 +1,9 @@ |
| 1 | 1 | pub const Stack = @import("stack.zig").Stack; |
| 2 | pub const Queue = @import("queue.zig").Queue; | |
| 2 | pub const QueueMpsc = @import("queue_mpsc.zig").QueueMpsc; | |
| 3 | pub const QueueMpmc = @import("queue_mpmc.zig").QueueMpmc; | |
| 3 | 4 | |
| 4 | 5 | test "std.atomic" { |
| 5 | _ = @import("stack.zig").Stack; | |
| 6 | _ = @import("queue.zig").Queue; | |
| 6 | _ = @import("stack.zig"); | |
| 7 | _ = @import("queue_mpsc.zig"); | |
| 8 | _ = @import("queue_mpmc.zig"); | |
| 7 | 9 | } |
std/atomic/queue.zig deleted-139| ... | ... | @@ -1,139 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const AtomicOrder = builtin.AtomicOrder; | |
| 3 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 4 | ||
| 5 | /// Many reader, many writer, non-allocating, thread-safe, lock-free | |
| 6 | pub fn Queue(comptime T: type) type { | |
| 7 | return struct { | |
| 8 | head: *Node, | |
| 9 | tail: *Node, | |
| 10 | root: Node, | |
| 11 | ||
| 12 | pub const Self = this; | |
| 13 | ||
| 14 | pub const Node = struct { | |
| 15 | next: ?*Node, | |
| 16 | data: T, | |
| 17 | }; | |
| 18 | ||
| 19 | // TODO: well defined copy elision: https://github.com/ziglang/zig/issues/287 | |
| 20 | pub fn init(self: *Self) void { | |
| 21 | self.root.next = null; | |
| 22 | self.head = &self.root; | |
| 23 | self.tail = &self.root; | |
| 24 | } | |
| 25 | ||
| 26 | pub fn put(self: *Self, node: *Node) void { | |
| 27 | node.next = null; | |
| 28 | ||
| 29 | const tail = @atomicRmw(*Node, &self.tail, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); | |
| 30 | _ = @atomicRmw(?*Node, &tail.next, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn get(self: *Self) ?*Node { | |
| 34 | var head = @atomicLoad(*Node, &self.head, AtomicOrder.SeqCst); | |
| 35 | while (true) { | |
| 36 | const node = head.next orelse return null; | |
| 37 | head = @cmpxchgWeak(*Node, &self.head, head, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return node; | |
| 38 | } | |
| 39 | } | |
| 40 | }; | |
| 41 | } | |
| 42 | ||
| 43 | const std = @import("std"); | |
| 44 | const Context = struct { | |
| 45 | allocator: *std.mem.Allocator, | |
| 46 | queue: *Queue(i32), | |
| 47 | put_sum: isize, | |
| 48 | get_sum: isize, | |
| 49 | get_count: usize, | |
| 50 | puts_done: u8, // TODO make this a bool | |
| 51 | }; | |
| 52 | ||
| 53 | // TODO add lazy evaluated build options and then put puts_per_thread behind | |
| 54 | // some option such as: "AggressiveMultithreadedFuzzTest". In the AppVeyor | |
| 55 | // CI we would use a less aggressive setting since at 1 core, while we still | |
| 56 | // want this test to pass, we need a smaller value since there is so much thrashing | |
| 57 | // we would also use a less aggressive setting when running in valgrind | |
| 58 | const puts_per_thread = 500; | |
| 59 | const put_thread_count = 3; | |
| 60 | ||
| 61 | test "std.atomic.queue" { | |
| 62 | var direct_allocator = std.heap.DirectAllocator.init(); | |
| 63 | defer direct_allocator.deinit(); | |
| 64 | ||
| 65 | var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 300 * 1024); | |
| 66 | defer direct_allocator.allocator.free(plenty_of_memory); | |
| 67 | ||
| 68 | var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); | |
| 69 | var a = &fixed_buffer_allocator.allocator; | |
| 70 | ||
| 71 | var queue: Queue(i32) = undefined; | |
| 72 | queue.init(); | |
| 73 | var context = Context{ | |
| 74 | .allocator = a, | |
| 75 | .queue = &queue, | |
| 76 | .put_sum = 0, | |
| 77 | .get_sum = 0, | |
| 78 | .puts_done = 0, | |
| 79 | .get_count = 0, | |
| 80 | }; | |
| 81 | ||
| 82 | var putters: [put_thread_count]*std.os.Thread = undefined; | |
| 83 | for (putters) |*t| { | |
| 84 | t.* = try std.os.spawnThread(&context, startPuts); | |
| 85 | } | |
| 86 | var getters: [put_thread_count]*std.os.Thread = undefined; | |
| 87 | for (getters) |*t| { | |
| 88 | t.* = try std.os.spawnThread(&context, startGets); | |
| 89 | } | |
| 90 | ||
| 91 | for (putters) |t| | |
| 92 | t.wait(); | |
| 93 | _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 94 | for (getters) |t| | |
| 95 | t.wait(); | |
| 96 | ||
| 97 | if (context.put_sum != context.get_sum) { | |
| 98 | std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum); | |
| 99 | } | |
| 100 | ||
| 101 | if (context.get_count != puts_per_thread * put_thread_count) { | |
| 102 | std.debug.panic( | |
| 103 | "failure\nget_count:{} != puts_per_thread:{} * put_thread_count:{}", | |
| 104 | context.get_count, | |
| 105 | u32(puts_per_thread), | |
| 106 | u32(put_thread_count), | |
| 107 | ); | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | fn startPuts(ctx: *Context) u8 { | |
| 112 | var put_count: usize = puts_per_thread; | |
| 113 | var r = std.rand.DefaultPrng.init(0xdeadbeef); | |
| 114 | while (put_count != 0) : (put_count -= 1) { | |
| 115 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | |
| 116 | const x = @bitCast(i32, r.random.scalar(u32)); | |
| 117 | const node = ctx.allocator.create(Queue(i32).Node{ | |
| 118 | .next = undefined, | |
| 119 | .data = x, | |
| 120 | }) catch unreachable; | |
| 121 | ctx.queue.put(node); | |
| 122 | _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); | |
| 123 | } | |
| 124 | return 0; | |
| 125 | } | |
| 126 | ||
| 127 | fn startGets(ctx: *Context) u8 { | |
| 128 | while (true) { | |
| 129 | const last = @atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1; | |
| 130 | ||
| 131 | while (ctx.queue.get()) |node| { | |
| 132 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | |
| 133 | _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); | |
| 134 | _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); | |
| 135 | } | |
| 136 | ||
| 137 | if (last) return 0; | |
| 138 | } | |
| 139 | } |
std/atomic/queue_mpmc.zig created+214| ... | ... | @@ -0,0 +1,214 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const AtomicOrder = builtin.AtomicOrder; | |
| 3 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 4 | ||
| 5 | /// Many producer, many consumer, non-allocating, thread-safe, lock-free | |
| 6 | /// This implementation has a crippling limitation - it hangs onto node | |
| 7 | /// memory for 1 extra get() and 1 extra put() operation - when get() returns a node, that | |
| 8 | /// node must not be freed until both the next get() and the next put() completes. | |
| 9 | pub fn QueueMpmc(comptime T: type) type { | |
| 10 | return struct { | |
| 11 | head: *Node, | |
| 12 | tail: *Node, | |
| 13 | root: Node, | |
| 14 | ||
| 15 | pub const Self = this; | |
| 16 | ||
| 17 | pub const Node = struct { | |
| 18 | next: ?*Node, | |
| 19 | data: T, | |
| 20 | }; | |
| 21 | ||
| 22 | /// TODO: well defined copy elision: https://github.com/ziglang/zig/issues/287 | |
| 23 | pub fn init(self: *Self) void { | |
| 24 | self.root.next = null; | |
| 25 | self.head = &self.root; | |
| 26 | self.tail = &self.root; | |
| 27 | } | |
| 28 | ||
| 29 | pub fn put(self: *Self, node: *Node) void { | |
| 30 | node.next = null; | |
| 31 | ||
| 32 | const tail = @atomicRmw(*Node, &self.tail, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); | |
| 33 | _ = @atomicRmw(?*Node, &tail.next, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); | |
| 34 | } | |
| 35 | ||
| 36 | /// node must not be freed until both the next get() and the next put() complete | |
| 37 | pub fn get(self: *Self) ?*Node { | |
| 38 | var head = @atomicLoad(*Node, &self.head, AtomicOrder.SeqCst); | |
| 39 | while (true) { | |
| 40 | const node = head.next orelse return null; | |
| 41 | head = @cmpxchgWeak(*Node, &self.head, head, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return node; | |
| 42 | } | |
| 43 | } | |
| 44 | ||
| 45 | ///// This is a debug function that is not thread-safe. | |
| 46 | pub fn dump(self: *Self) void { | |
| 47 | std.debug.warn("head: "); | |
| 48 | dumpRecursive(self.head, 0); | |
| 49 | std.debug.warn("tail: "); | |
| 50 | dumpRecursive(self.tail, 0); | |
| 51 | } | |
| 52 | ||
| 53 | fn dumpRecursive(optional_node: ?*Node, indent: usize) void { | |
| 54 | var stderr_file = std.io.getStdErr() catch return; | |
| 55 | const stderr = &std.io.FileOutStream.init(&stderr_file).stream; | |
| 56 | stderr.writeByteNTimes(' ', indent) catch return; | |
| 57 | if (optional_node) |node| { | |
| 58 | std.debug.warn("0x{x}={}\n", @ptrToInt(node), node.data); | |
| 59 | dumpRecursive(node.next, indent + 1); | |
| 60 | } else { | |
| 61 | std.debug.warn("(null)\n"); | |
| 62 | } | |
| 63 | } | |
| 64 | }; | |
| 65 | } | |
| 66 | ||
| 67 | const std = @import("std"); | |
| 68 | const assert = std.debug.assert; | |
| 69 | ||
| 70 | const Context = struct { | |
| 71 | allocator: *std.mem.Allocator, | |
| 72 | queue: *QueueMpmc(i32), | |
| 73 | put_sum: isize, | |
| 74 | get_sum: isize, | |
| 75 | get_count: usize, | |
| 76 | puts_done: u8, // TODO make this a bool | |
| 77 | }; | |
| 78 | ||
| 79 | // TODO add lazy evaluated build options and then put puts_per_thread behind | |
| 80 | // some option such as: "AggressiveMultithreadedFuzzTest". In the AppVeyor | |
| 81 | // CI we would use a less aggressive setting since at 1 core, while we still | |
| 82 | // want this test to pass, we need a smaller value since there is so much thrashing | |
| 83 | // we would also use a less aggressive setting when running in valgrind | |
| 84 | const puts_per_thread = 500; | |
| 85 | const put_thread_count = 3; | |
| 86 | ||
| 87 | test "std.atomic.queue_mpmc" { | |
| 88 | var direct_allocator = std.heap.DirectAllocator.init(); | |
| 89 | defer direct_allocator.deinit(); | |
| 90 | ||
| 91 | var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 300 * 1024); | |
| 92 | defer direct_allocator.allocator.free(plenty_of_memory); | |
| 93 | ||
| 94 | var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); | |
| 95 | var a = &fixed_buffer_allocator.allocator; | |
| 96 | ||
| 97 | var queue: QueueMpmc(i32) = undefined; | |
| 98 | queue.init(); | |
| 99 | var context = Context{ | |
| 100 | .allocator = a, | |
| 101 | .queue = &queue, | |
| 102 | .put_sum = 0, | |
| 103 | .get_sum = 0, | |
| 104 | .puts_done = 0, | |
| 105 | .get_count = 0, | |
| 106 | }; | |
| 107 | ||
| 108 | var putters: [put_thread_count]*std.os.Thread = undefined; | |
| 109 | for (putters) |*t| { | |
| 110 | t.* = try std.os.spawnThread(&context, startPuts); | |
| 111 | } | |
| 112 | var getters: [put_thread_count]*std.os.Thread = undefined; | |
| 113 | for (getters) |*t| { | |
| 114 | t.* = try std.os.spawnThread(&context, startGets); | |
| 115 | } | |
| 116 | ||
| 117 | for (putters) |t| | |
| 118 | t.wait(); | |
| 119 | _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 120 | for (getters) |t| | |
| 121 | t.wait(); | |
| 122 | ||
| 123 | if (context.put_sum != context.get_sum) { | |
| 124 | std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum); | |
| 125 | } | |
| 126 | ||
| 127 | if (context.get_count != puts_per_thread * put_thread_count) { | |
| 128 | std.debug.panic( | |
| 129 | "failure\nget_count:{} != puts_per_thread:{} * put_thread_count:{}", | |
| 130 | context.get_count, | |
| 131 | u32(puts_per_thread), | |
| 132 | u32(put_thread_count), | |
| 133 | ); | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | fn startPuts(ctx: *Context) u8 { | |
| 138 | var put_count: usize = puts_per_thread; | |
| 139 | var r = std.rand.DefaultPrng.init(0xdeadbeef); | |
| 140 | while (put_count != 0) : (put_count -= 1) { | |
| 141 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | |
| 142 | const x = @bitCast(i32, r.random.scalar(u32)); | |
| 143 | const node = ctx.allocator.create(QueueMpmc(i32).Node{ | |
| 144 | .next = undefined, | |
| 145 | .data = x, | |
| 146 | }) catch unreachable; | |
| 147 | ctx.queue.put(node); | |
| 148 | _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); | |
| 149 | } | |
| 150 | return 0; | |
| 151 | } | |
| 152 | ||
| 153 | fn startGets(ctx: *Context) u8 { | |
| 154 | while (true) { | |
| 155 | const last = @atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1; | |
| 156 | ||
| 157 | while (ctx.queue.get()) |node| { | |
| 158 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | |
| 159 | _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); | |
| 160 | _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); | |
| 161 | } | |
| 162 | ||
| 163 | if (last) return 0; | |
| 164 | } | |
| 165 | } | |
| 166 | ||
| 167 | test "std.atomic.queue_mpmc single-threaded" { | |
| 168 | var queue: QueueMpmc(i32) = undefined; | |
| 169 | queue.init(); | |
| 170 | ||
| 171 | var node_0 = QueueMpmc(i32).Node{ | |
| 172 | .data = 0, | |
| 173 | .next = undefined, | |
| 174 | }; | |
| 175 | queue.put(&node_0); | |
| 176 | ||
| 177 | var node_1 = QueueMpmc(i32).Node{ | |
| 178 | .data = 1, | |
| 179 | .next = undefined, | |
| 180 | }; | |
| 181 | queue.put(&node_1); | |
| 182 | ||
| 183 | assert(queue.get().?.data == 0); | |
| 184 | ||
| 185 | var node_2 = QueueMpmc(i32).Node{ | |
| 186 | .data = 2, | |
| 187 | .next = undefined, | |
| 188 | }; | |
| 189 | queue.put(&node_2); | |
| 190 | ||
| 191 | var node_3 = QueueMpmc(i32).Node{ | |
| 192 | .data = 3, | |
| 193 | .next = undefined, | |
| 194 | }; | |
| 195 | queue.put(&node_3); | |
| 196 | ||
| 197 | assert(queue.get().?.data == 1); | |
| 198 | ||
| 199 | assert(queue.get().?.data == 2); | |
| 200 | ||
| 201 | var node_4 = QueueMpmc(i32).Node{ | |
| 202 | .data = 4, | |
| 203 | .next = undefined, | |
| 204 | }; | |
| 205 | queue.put(&node_4); | |
| 206 | ||
| 207 | assert(queue.get().?.data == 3); | |
| 208 | // if we were to set node_3.next to null here, it would cause this test | |
| 209 | // to fail. this demonstrates the limitation of hanging on to extra memory. | |
| 210 | ||
| 211 | assert(queue.get().?.data == 4); | |
| 212 | ||
| 213 | assert(queue.get() == null); | |
| 214 | } |
std/atomic/queue_mpsc.zig created+143| ... | ... | @@ -0,0 +1,143 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const builtin = @import("builtin"); | |
| 4 | const AtomicOrder = builtin.AtomicOrder; | |
| 5 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 6 | ||
| 7 | /// Many producer, single consumer, non-allocating, thread-safe, lock-free | |
| 8 | pub fn QueueMpsc(comptime T: type) type { | |
| 9 | return struct { | |
| 10 | inboxes: [2]std.atomic.Stack(T), | |
| 11 | outbox: std.atomic.Stack(T), | |
| 12 | inbox_index: usize, | |
| 13 | ||
| 14 | pub const Self = this; | |
| 15 | ||
| 16 | pub const Node = std.atomic.Stack(T).Node; | |
| 17 | ||
| 18 | pub fn init() Self { | |
| 19 | return Self{ | |
| 20 | .inboxes = []std.atomic.Stack(T){ | |
| 21 | std.atomic.Stack(T).init(), | |
| 22 | std.atomic.Stack(T).init(), | |
| 23 | }, | |
| 24 | .outbox = std.atomic.Stack(T).init(), | |
| 25 | .inbox_index = 0, | |
| 26 | }; | |
| 27 | } | |
| 28 | ||
| 29 | pub fn put(self: *Self, node: *Node) void { | |
| 30 | const inbox_index = @atomicLoad(usize, &self.inbox_index, AtomicOrder.SeqCst); | |
| 31 | const inbox = &self.inboxes[inbox_index]; | |
| 32 | inbox.push(node); | |
| 33 | } | |
| 34 | ||
| 35 | pub fn get(self: *Self) ?*Node { | |
| 36 | if (self.outbox.pop()) |node| { | |
| 37 | return node; | |
| 38 | } | |
| 39 | const prev_inbox_index = @atomicRmw(usize, &self.inbox_index, AtomicRmwOp.Xor, 0x1, AtomicOrder.SeqCst); | |
| 40 | const prev_inbox = &self.inboxes[prev_inbox_index]; | |
| 41 | while (prev_inbox.pop()) |node| { | |
| 42 | self.outbox.push(node); | |
| 43 | } | |
| 44 | return self.outbox.pop(); | |
| 45 | } | |
| 46 | }; | |
| 47 | } | |
| 48 | ||
| 49 | const Context = struct { | |
| 50 | allocator: *std.mem.Allocator, | |
| 51 | queue: *QueueMpsc(i32), | |
| 52 | put_sum: isize, | |
| 53 | get_sum: isize, | |
| 54 | get_count: usize, | |
| 55 | puts_done: u8, // TODO make this a bool | |
| 56 | }; | |
| 57 | ||
| 58 | // TODO add lazy evaluated build options and then put puts_per_thread behind | |
| 59 | // some option such as: "AggressiveMultithreadedFuzzTest". In the AppVeyor | |
| 60 | // CI we would use a less aggressive setting since at 1 core, while we still | |
| 61 | // want this test to pass, we need a smaller value since there is so much thrashing | |
| 62 | // we would also use a less aggressive setting when running in valgrind | |
| 63 | const puts_per_thread = 500; | |
| 64 | const put_thread_count = 3; | |
| 65 | ||
| 66 | test "std.atomic.queue_mpsc" { | |
| 67 | var direct_allocator = std.heap.DirectAllocator.init(); | |
| 68 | defer direct_allocator.deinit(); | |
| 69 | ||
| 70 | var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 300 * 1024); | |
| 71 | defer direct_allocator.allocator.free(plenty_of_memory); | |
| 72 | ||
| 73 | var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); | |
| 74 | var a = &fixed_buffer_allocator.allocator; | |
| 75 | ||
| 76 | var queue = QueueMpsc(i32).init(); | |
| 77 | var context = Context{ | |
| 78 | .allocator = a, | |
| 79 | .queue = &queue, | |
| 80 | .put_sum = 0, | |
| 81 | .get_sum = 0, | |
| 82 | .puts_done = 0, | |
| 83 | .get_count = 0, | |
| 84 | }; | |
| 85 | ||
| 86 | var putters: [put_thread_count]*std.os.Thread = undefined; | |
| 87 | for (putters) |*t| { | |
| 88 | t.* = try std.os.spawnThread(&context, startPuts); | |
| 89 | } | |
| 90 | var getters: [1]*std.os.Thread = undefined; | |
| 91 | for (getters) |*t| { | |
| 92 | t.* = try std.os.spawnThread(&context, startGets); | |
| 93 | } | |
| 94 | ||
| 95 | for (putters) |t| | |
| 96 | t.wait(); | |
| 97 | _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 98 | for (getters) |t| | |
| 99 | t.wait(); | |
| 100 | ||
| 101 | if (context.put_sum != context.get_sum) { | |
| 102 | std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum); | |
| 103 | } | |
| 104 | ||
| 105 | if (context.get_count != puts_per_thread * put_thread_count) { | |
| 106 | std.debug.panic( | |
| 107 | "failure\nget_count:{} != puts_per_thread:{} * put_thread_count:{}", | |
| 108 | context.get_count, | |
| 109 | u32(puts_per_thread), | |
| 110 | u32(put_thread_count), | |
| 111 | ); | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | fn startPuts(ctx: *Context) u8 { | |
| 116 | var put_count: usize = puts_per_thread; | |
| 117 | var r = std.rand.DefaultPrng.init(0xdeadbeef); | |
| 118 | while (put_count != 0) : (put_count -= 1) { | |
| 119 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | |
| 120 | const x = @bitCast(i32, r.random.scalar(u32)); | |
| 121 | const node = ctx.allocator.create(QueueMpsc(i32).Node{ | |
| 122 | .next = undefined, | |
| 123 | .data = x, | |
| 124 | }) catch unreachable; | |
| 125 | ctx.queue.put(node); | |
| 126 | _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); | |
| 127 | } | |
| 128 | return 0; | |
| 129 | } | |
| 130 | ||
| 131 | fn startGets(ctx: *Context) u8 { | |
| 132 | while (true) { | |
| 133 | const last = @atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1; | |
| 134 | ||
| 135 | while (ctx.queue.get()) |node| { | |
| 136 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | |
| 137 | _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); | |
| 138 | _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); | |
| 139 | } | |
| 140 | ||
| 141 | if (last) return 0; | |
| 142 | } | |
| 143 | } |