| ... | @@ -24,8 +24,6 @@ supports_ansi_escape_codes: bool, | ... | @@ -24,8 +24,6 @@ supports_ansi_escape_codes: bool, |
| 24 | | 24 | |
| 25 | root: Node, | 25 | root: Node, |
| 26 | | 26 | |
| 27 | /// Protects all the state shared between the update thread and the public API calls. | | |
| 28 | mutex: std.Thread.Mutex, | | |
| 29 | update_thread: ?std.Thread, | 27 | update_thread: ?std.Thread, |
| 30 | | 28 | |
| 31 | /// Atomically set by SIGWINCH as well as the root done() function. | 29 | /// Atomically set by SIGWINCH as well as the root done() function. |
| ... | @@ -33,6 +31,7 @@ redraw_event: std.Thread.ResetEvent, | ... | @@ -33,6 +31,7 @@ redraw_event: std.Thread.ResetEvent, |
| 33 | /// Ensure there is only 1 global Progress object. | 31 | /// Ensure there is only 1 global Progress object. |
| 34 | initialized: bool, | 32 | initialized: bool, |
| 35 | /// Indicates a request to shut down and reset global state. | 33 | /// Indicates a request to shut down and reset global state. |
| | 34 | /// Accessed atomically. |
| 36 | done: bool, | 35 | done: bool, |
| 37 | | 36 | |
| 38 | refresh_rate_ns: u64, | 37 | refresh_rate_ns: u64, |
| ... | @@ -65,10 +64,13 @@ pub const Options = struct { | ... | @@ -65,10 +64,13 @@ pub const Options = struct { |
| 65 | /// Represents one unit of progress. Each node can have children nodes, or | 64 | /// Represents one unit of progress. Each node can have children nodes, or |
| 66 | /// one can use integers with `update`. | 65 | /// one can use integers with `update`. |
| 67 | pub const Node = struct { | 66 | pub const Node = struct { |
| 68 | parent: ?*Node, | 67 | mutex: std.Thread.Mutex, |
| | 68 | /// Links to the parent and child nodes. |
| | 69 | parent_list_node: std.DoublyLinkedList(void).Node, |
| | 70 | /// Links to the prev and next sibling nodes. |
| | 71 | sibling_list_node: std.DoublyLinkedList(void).Node, |
| | 72 | |
| 69 | name: []const u8, | 73 | name: []const u8, |
| 70 | /// Must be handled atomically to be thread-safe. | | |
| 71 | recently_updated_child: ?*Node = null, | | |
| 72 | /// Must be handled atomically to be thread-safe. 0 means null. | 74 | /// Must be handled atomically to be thread-safe. 0 means null. |
| 73 | unprotected_estimated_total_items: usize, | 75 | unprotected_estimated_total_items: usize, |
| 74 | /// Must be handled atomically to be thread-safe. | 76 | /// Must be handled atomically to be thread-safe. |
| ... | @@ -78,50 +80,57 @@ pub const Node = struct { | ... | @@ -78,50 +80,57 @@ pub const Node = struct { |
| 78 | | 80 | |
| 79 | /// Create a new child progress node. Thread-safe. | 81 | /// Create a new child progress node. Thread-safe. |
| 80 | /// | 82 | /// |
| 81 | /// Call `Node.end` when done. | 83 | /// It is expected for the memory of the result to be stored in the |
| | 84 | /// caller's stack and therefore is required to call `activate` immediately |
| | 85 | /// on the result after initializing the memory location and `end` when done. |
| 82 | /// | 86 | /// |
| 83 | /// Passing 0 for `estimated_total_items` means unknown. | 87 | /// Passing 0 for `estimated_total_items` means unknown. |
| 84 | pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node { | 88 | pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node { |
| 85 | return .{ | 89 | return .{ |
| 86 | .parent = self, | 90 | .mutex = .{}, |
| | 91 | .parent_list_node = .{ |
| | 92 | .prev = &self.parent_list_node, |
| | 93 | .next = null, |
| | 94 | .data = {}, |
| | 95 | }, |
| | 96 | .sibling_list_node = .{ .data = {} }, |
| 87 | .name = name, | 97 | .name = name, |
| 88 | .unprotected_estimated_total_items = estimated_total_items, | 98 | .unprotected_estimated_total_items = estimated_total_items, |
| 89 | .unprotected_completed_items = 0, | 99 | .unprotected_completed_items = 0, |
| 90 | }; | 100 | }; |
| 91 | } | 101 | } |
| 92 | | 102 | |
| | 103 | /// To be called exactly once after `start`. |
| | 104 | pub fn activate(n: *Node) void { |
| | 105 | const p = n.parent().?; |
| | 106 | p.mutex.lock(); |
| | 107 | defer p.mutex.unlock(); |
| | 108 | assert(p.parent_list_node.next == null); |
| | 109 | p.parent_list_node.next = &n.parent_list_node; |
| | 110 | } |
| | 111 | |
| 93 | /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe. | 112 | /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe. |
| 94 | pub fn completeOne(self: *Node) void { | 113 | pub fn completeOne(self: *Node) void { |
| 95 | _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .monotonic); | 114 | _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .monotonic); |
| 96 | self.activate(); | | |
| 97 | } | 115 | } |
| 98 | | 116 | |
| 99 | /// Finish a started `Node`. Thread-safe. | 117 | /// Finish a started `Node`. Thread-safe. |
| 100 | pub fn end(self: *Node) void { | 118 | pub fn end(child: *Node) void { |
| 101 | if (self.parent) |parent| { | 119 | if (child.parent()) |p| { |
| 102 | parent.completeOne(); | 120 | // Make sure the other thread doesn't access this memory that is |
| | 121 | // about to be released. |
| | 122 | child.mutex.lock(); |
| | 123 | |
| | 124 | const other = if (child.sibling_list_node.next) |n| n else child.sibling_list_node.prev; |
| | 125 | _ = @cmpxchgStrong(std.DoublyLinkedList(void).Node, &p.parent_list_node.next, child, other, .seq_cst, .seq_cst); |
| | 126 | p.completeOne(); |
| 103 | } else { | 127 | } else { |
| 104 | { | 128 | @atomicStore(bool, &global_progress.done, true, .seq_cst); |
| 105 | global_progress.mutex.lock(); | | |
| 106 | defer global_progress.mutex.unlock(); | | |
| 107 | global_progress.done = true; | | |
| 108 | } | | |
| 109 | global_progress.redraw_event.set(); | 129 | global_progress.redraw_event.set(); |
| 110 | if (global_progress.update_thread) |thread| thread.join(); | 130 | if (global_progress.update_thread) |thread| thread.join(); |
| 111 | } | 131 | } |
| 112 | } | 132 | } |
| 113 | | 133 | |
| 114 | /// Tell the parent node that this node is actively being worked on. Thread-safe. | | |
| 115 | pub fn activate(self: *Node) void { | | |
| 116 | var parent = self.parent; | | |
| 117 | var child = self; | | |
| 118 | while (parent) |p| { | | |
| 119 | @atomicStore(?*Node, &p.recently_updated_child, child, .release); | | |
| 120 | child = p; | | |
| 121 | parent = p.parent; | | |
| 122 | } | | |
| 123 | } | | |
| 124 | | | |
| 125 | /// Thread-safe. 0 means unknown. | 134 | /// Thread-safe. 0 means unknown. |
| 126 | pub fn setEstimatedTotalItems(self: *Node, count: usize) void { | 135 | pub fn setEstimatedTotalItems(self: *Node, count: usize) void { |
| 127 | @atomicStore(usize, &self.unprotected_estimated_total_items, count, .monotonic); | 136 | @atomicStore(usize, &self.unprotected_estimated_total_items, count, .monotonic); |
| ... | @@ -131,6 +140,11 @@ pub const Node = struct { | ... | @@ -131,6 +140,11 @@ pub const Node = struct { |
| 131 | pub fn setCompletedItems(self: *Node, completed_items: usize) void { | 140 | pub fn setCompletedItems(self: *Node, completed_items: usize) void { |
| 132 | @atomicStore(usize, &self.unprotected_completed_items, completed_items, .monotonic); | 141 | @atomicStore(usize, &self.unprotected_completed_items, completed_items, .monotonic); |
| 133 | } | 142 | } |
| | 143 | |
| | 144 | fn parent(child: *Node) ?*Node { |
| | 145 | const parent_node = child.parent_list_node.prev orelse return null; |
| | 146 | return @fieldParentPtr("parent_list_node", parent_node); |
| | 147 | } |
| 134 | }; | 148 | }; |
| 135 | | 149 | |
| 136 | var global_progress: Progress = .{ | 150 | var global_progress: Progress = .{ |
| ... | @@ -138,7 +152,6 @@ var global_progress: Progress = .{ | ... | @@ -138,7 +152,6 @@ var global_progress: Progress = .{ |
| 138 | .is_windows_terminal = false, | 152 | .is_windows_terminal = false, |
| 139 | .supports_ansi_escape_codes = false, | 153 | .supports_ansi_escape_codes = false, |
| 140 | .root = undefined, | 154 | .root = undefined, |
| 141 | .mutex = .{}, | | |
| 142 | .update_thread = null, | 155 | .update_thread = null, |
| 143 | .redraw_event = .{}, | 156 | .redraw_event = .{}, |
| 144 | .initialized = false, | 157 | .initialized = false, |
| ... | @@ -169,7 +182,9 @@ pub fn start(options: Options) *Node { | ... | @@ -169,7 +182,9 @@ pub fn start(options: Options) *Node { |
| 169 | global_progress.terminal = stderr; | 182 | global_progress.terminal = stderr; |
| 170 | } | 183 | } |
| 171 | global_progress.root = .{ | 184 | global_progress.root = .{ |
| 172 | .parent = null, | 185 | .mutex = .{}, |
| | 186 | .parent_list_node = .{ .data = {} }, |
| | 187 | .sibling_list_node = .{ .data = {} }, |
| 173 | .name = options.root_name, | 188 | .name = options.root_name, |
| 174 | .unprotected_estimated_total_items = options.estimated_total_items, | 189 | .unprotected_estimated_total_items = options.estimated_total_items, |
| 175 | .unprotected_completed_items = 0, | 190 | .unprotected_completed_items = 0, |
| ... | @@ -220,10 +235,8 @@ fn updateThreadRun() void { | ... | @@ -220,10 +235,8 @@ fn updateThreadRun() void { |
| 220 | maybeUpdateSize(resize_flag); | 235 | maybeUpdateSize(resize_flag); |
| 221 | | 236 | |
| 222 | const buffer = b: { | 237 | const buffer = b: { |
| 223 | global_progress.mutex.lock(); | 238 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 224 | defer global_progress.mutex.unlock(); | 239 | return clearTerminal(); |
| 225 | | | |
| 226 | if (global_progress.done) return clearTerminal(); | | |
| 227 | | 240 | |
| 228 | break :b computeRedraw(); | 241 | break :b computeRedraw(); |
| 229 | }; | 242 | }; |
| ... | @@ -235,10 +248,8 @@ fn updateThreadRun() void { | ... | @@ -235,10 +248,8 @@ fn updateThreadRun() void { |
| 235 | maybeUpdateSize(resize_flag); | 248 | maybeUpdateSize(resize_flag); |
| 236 | | 249 | |
| 237 | const buffer = b: { | 250 | const buffer = b: { |
| 238 | global_progress.mutex.lock(); | 251 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 239 | defer global_progress.mutex.unlock(); | 252 | return clearTerminal(); |
| 240 | | | |
| 241 | if (global_progress.done) return clearTerminal(); | | |
| 242 | | 253 | |
| 243 | break :b computeRedraw(); | 254 | break :b computeRedraw(); |
| 244 | }; | 255 | }; |
| ... | @@ -270,7 +281,6 @@ fn computeRedraw() []u8 { | ... | @@ -270,7 +281,6 @@ fn computeRedraw() []u8 { |
| 270 | i = prefix.len; | 281 | i = prefix.len; |
| 271 | | 282 | |
| 272 | // Walk the tree and write the progress output to the buffer. | 283 | // Walk the tree and write the progress output to the buffer. |
| 273 | | | |
| 274 | var node: *Node = &global_progress.root; | 284 | var node: *Node = &global_progress.root; |
| 275 | while (true) { | 285 | while (true) { |
| 276 | const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic); | 286 | const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic); |