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,...@@ -24,8 +24,6 @@ supports_ansi_escape_codes: bool,
2424
25root: Node,25root: Node,
2626
27/// Protects all the state shared between the update thread and the public API calls.
28mutex: std.Thread.Mutex,
29update_thread: ?std.Thread,27update_thread: ?std.Thread,
3028
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.
34initialized: bool,32initialized: 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.
36done: bool,35done: bool,
3736
38refresh_rate_ns: u64,37refresh_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, or64/// 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`.
67pub const Node = struct {66pub 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 {
7880
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 }
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
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 }
98116
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 }
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
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};
135149
136var global_progress: Progress = .{150var 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);
221236
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();
227240
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);
236249
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();
242253
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;
271282
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);