| 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,7 +431,8 @@ set(ZIG_CPP_SOURCES |
| 431 | set(ZIG_STD_FILES | 431 | set(ZIG_STD_FILES |
| 432 | "array_list.zig" | 432 | "array_list.zig" |
| 433 | "atomic/index.zig" | 433 | "atomic/index.zig" |
| 434 | "atomic/queue.zig" | 434 | "atomic/queue_mpmc.zig" |
| 435 | "atomic/queue_mpsc.zig" | ||
| 435 | "atomic/stack.zig" | 436 | "atomic/stack.zig" |
| 436 | "base64.zig" | 437 | "base64.zig" |
| 437 | "buf_map.zig" | 438 | "buf_map.zig" |
std/atomic/index.zig+5-3| ... | @@ -1,7 +1,9 @@ | ... | @@ -1,7 +1,9 @@ |
| 1 | pub const Stack = @import("stack.zig").Stack; | 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 | test "std.atomic" { | 5 | test "std.atomic" { |
| 5 | _ = @import("stack.zig").Stack; | 6 | _ = @import("stack.zig"); |
| 6 | _ = @import("queue.zig").Queue; | 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 | } | ||