authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-22 12:37:17-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-27 20:56:48-07:00
loge1e4de2776901a0acb7a28454c0fe080c5c13a5e
tree73c5d9e47e0e2400ab87a5cee95ed4d0e1d2bc57
parentd6e8ba3f97b778676bdb3c79b37afc8003b883ea

progress progress

Move the mutex into the nodes Track the whole tree instead of only recently activated node

1 files changed, 48 insertions(+), 38 deletions(-)

lib/std/Progress.zig+48-38
......@@ -24,8 +24,6 @@ supports_ansi_escape_codes: bool,
2424
2525root: Node,
2626
27/// Protects all the state shared between the update thread and the public API calls.
28mutex: std.Thread.Mutex,
2927update_thread: ?std.Thread,
3028
3129/// Atomically set by SIGWINCH as well as the root done() function.
......@@ -33,6 +31,7 @@ redraw_event: std.Thread.ResetEvent,
3331/// Ensure there is only 1 global Progress object.
3432initialized: bool,
3533/// Indicates a request to shut down and reset global state.
34/// Accessed atomically.
3635done: bool,
3736
3837refresh_rate_ns: u64,
......@@ -65,10 +64,13 @@ pub const Options = struct {
6564/// Represents one unit of progress. Each node can have children nodes, or
6665/// one can use integers with `update`.
6766pub 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
6973 name: []const u8,
70 /// Must be handled atomically to be thread-safe.
71 recently_updated_child: ?*Node = null,
7274 /// Must be handled atomically to be thread-safe. 0 means null.
7375 unprotected_estimated_total_items: usize,
7476 /// Must be handled atomically to be thread-safe.
......@@ -78,50 +80,57 @@ pub const Node = struct {
7880
7981 /// Create a new child progress node. Thread-safe.
8082 ///
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.
8286 ///
8387 /// Passing 0 for `estimated_total_items` means unknown.
8488 pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node {
8589 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 = {} },
8797 .name = name,
8898 .unprotected_estimated_total_items = estimated_total_items,
8999 .unprotected_completed_items = 0,
90100 };
91101 }
92102
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
93112 /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe.
94113 pub fn completeOne(self: *Node) void {
95114 _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .monotonic);
96 self.activate();
97115 }
98116
99117 /// Finish a started `Node`. Thread-safe.
100 pub fn end(self: *Node) void {
101 if (self.parent) |parent| {
102 parent.completeOne();
118 pub fn end(child: *Node) void {
119 if (child.parent()) |p| {
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();
103127 } else {
104 {
105 global_progress.mutex.lock();
106 defer global_progress.mutex.unlock();
107 global_progress.done = true;
108 }
128 @atomicStore(bool, &global_progress.done, true, .seq_cst);
109129 global_progress.redraw_event.set();
110130 if (global_progress.update_thread) |thread| thread.join();
111131 }
112132 }
113133
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
125134 /// Thread-safe. 0 means unknown.
126135 pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
127136 @atomicStore(usize, &self.unprotected_estimated_total_items, count, .monotonic);
......@@ -131,6 +140,11 @@ pub const Node = struct {
131140 pub fn setCompletedItems(self: *Node, completed_items: usize) void {
132141 @atomicStore(usize, &self.unprotected_completed_items, completed_items, .monotonic);
133142 }
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 }
134148};
135149
136150var global_progress: Progress = .{
......@@ -138,7 +152,6 @@ var global_progress: Progress = .{
138152 .is_windows_terminal = false,
139153 .supports_ansi_escape_codes = false,
140154 .root = undefined,
141 .mutex = .{},
142155 .update_thread = null,
143156 .redraw_event = .{},
144157 .initialized = false,
......@@ -169,7 +182,9 @@ pub fn start(options: Options) *Node {
169182 global_progress.terminal = stderr;
170183 }
171184 global_progress.root = .{
172 .parent = null,
185 .mutex = .{},
186 .parent_list_node = .{ .data = {} },
187 .sibling_list_node = .{ .data = {} },
173188 .name = options.root_name,
174189 .unprotected_estimated_total_items = options.estimated_total_items,
175190 .unprotected_completed_items = 0,
......@@ -220,10 +235,8 @@ fn updateThreadRun() void {
220235 maybeUpdateSize(resize_flag);
221236
222237 const buffer = b: {
223 global_progress.mutex.lock();
224 defer global_progress.mutex.unlock();
225
226 if (global_progress.done) return clearTerminal();
238 if (@atomicLoad(bool, &global_progress.done, .seq_cst))
239 return clearTerminal();
227240
228241 break :b computeRedraw();
229242 };
......@@ -235,10 +248,8 @@ fn updateThreadRun() void {
235248 maybeUpdateSize(resize_flag);
236249
237250 const buffer = b: {
238 global_progress.mutex.lock();
239 defer global_progress.mutex.unlock();
240
241 if (global_progress.done) return clearTerminal();
251 if (@atomicLoad(bool, &global_progress.done, .seq_cst))
252 return clearTerminal();
242253
243254 break :b computeRedraw();
244255 };
......@@ -270,7 +281,6 @@ fn computeRedraw() []u8 {
270281 i = prefix.len;
271282
272283 // Walk the tree and write the progress output to the buffer.
273
274284 var node: *Node = &global_progress.root;
275285 while (true) {
276286 const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic);