Skip to content

Commit 5f88afe

Browse files
committed
std.Progress: fix race condition with IPC nodes
It stored some metadata into the canonical node storage data but that is a race condition because another thread recycles those nodes. Also, keep the parent name for empty child root node names.
1 parent 85fe1cf commit 5f88afe

File tree

1 file changed

+49
-40
lines changed

1 file changed

+49
-40
lines changed

lib/std/Progress.zig

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,7 @@ pub const Node = struct {
8686
name: [max_name_len]u8,
8787

8888
fn getIpcFd(s: Storage) ?posix.fd_t {
89-
if (s.estimated_total_count != std.math.maxInt(u32))
90-
return null;
91-
92-
const low: u16 = @truncate(s.completed_count);
93-
return low;
94-
}
95-
96-
fn getMainStorageIndex(s: Storage) Node.Index {
97-
assert(s.estimated_total_count == std.math.maxInt(u32));
98-
const i: u16 = @truncate(s.completed_count >> 16);
99-
return @enumFromInt(i);
89+
return if (s.estimated_total_count != std.math.maxInt(u32)) null else @bitCast(s.completed_count);
10090
}
10191

10292
fn setIpcFd(s: *Storage, fd: posix.fd_t) void {
@@ -538,14 +528,9 @@ fn serialize() Serialized {
538528
@memcpy(&dest_storage.name, &storage_ptr.name);
539529
dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic);
540530
dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .monotonic);
541-
542-
if (dest_storage.getIpcFd() != null) {
543-
any_ipc = true;
544-
dest_storage.completed_count |= @as(u32, @intCast(i)) << 16;
545-
}
546-
547531
const end_parent = @atomicLoad(Node.Parent, parent_ptr, .seq_cst);
548532
if (begin_parent == end_parent) {
533+
any_ipc = any_ipc or (dest_storage.getIpcFd() != null);
549534
serialized_node_parents_buffer[serialized_len] = begin_parent;
550535
serialized_node_map_buffer[i] = @enumFromInt(serialized_len);
551536
serialized_len += 1;
@@ -577,23 +562,25 @@ fn serialize() Serialized {
577562

578563
var parents_copy: [default_node_storage_buffer_len]Node.Parent = undefined;
579564
var storage_copy: [default_node_storage_buffer_len]Node.Storage = undefined;
565+
var ipc_metadata_copy: [default_node_storage_buffer_len]SavedMetadata = undefined;
580566

581-
const SavedMetadata = extern struct {
567+
var ipc_metadata: [default_node_storage_buffer_len]SavedMetadata = undefined;
568+
var ipc_metadata_len: u16 = 0;
569+
570+
const SavedMetadata = struct {
571+
ipc_fd: u16,
572+
main_index: u16,
582573
start_index: u16,
583574
nodes_len: u16,
584-
main_index: u16,
585-
flags: Flags,
586-
587-
const Flags = enum(u16) {
588-
saved = std.math.maxInt(u16),
589-
_,
590-
};
591575
};
592576

593577
fn serializeIpc(start_serialized_len: usize) usize {
594578
var serialized_len = start_serialized_len;
595579
var pipe_buf: [2 * 4096]u8 align(4) = undefined;
596580

581+
const old_ipc_metadata = ipc_metadata_copy[0..ipc_metadata_len];
582+
ipc_metadata_len = 0;
583+
597584
main_loop: for (
598585
serialized_node_parents_buffer[0..serialized_len],
599586
serialized_node_storage_buffer[0..serialized_len],
@@ -618,23 +605,23 @@ fn serializeIpc(start_serialized_len: usize) usize {
618605
// Ignore all but the last message on the pipe.
619606
var input: []align(2) u8 = pipe_buf[0..bytes_read];
620607
if (input.len == 0) {
621-
serialized_len = useSavedIpcData(serialized_len, main_storage, main_index);
608+
serialized_len = useSavedIpcData(serialized_len, main_storage, main_index, old_ipc_metadata);
622609
continue;
623610
}
624611

625612
const storage, const parents = while (true) {
626613
if (input.len < 4) {
627614
std.log.warn("short read: {d} out of 4 header bytes", .{input.len});
628615
// TODO keep track of the short read to trash odd bytes with the next read
629-
serialized_len = useSavedIpcData(serialized_len, main_storage, main_index);
616+
serialized_len = useSavedIpcData(serialized_len, main_storage, main_index, old_ipc_metadata);
630617
continue :main_loop;
631618
}
632619
const subtree_len = std.mem.readInt(u32, input[0..4], .little);
633620
const expected_bytes = 4 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));
634621
if (input.len < expected_bytes) {
635622
std.log.warn("short read: {d} out of {d} ({d} nodes)", .{ input.len, expected_bytes, subtree_len });
636623
// TODO keep track of the short read to trash odd bytes with the next read
637-
serialized_len = useSavedIpcData(serialized_len, main_storage, main_index);
624+
serialized_len = useSavedIpcData(serialized_len, main_storage, main_index, old_ipc_metadata);
638625
continue :main_loop;
639626
}
640627
if (input.len > expected_bytes) {
@@ -650,16 +637,16 @@ fn serializeIpc(start_serialized_len: usize) usize {
650637
};
651638

652639
// Remember in case the pipe is empty on next update.
653-
const real_storage: *Node.Storage = Node.storageByIndex(main_storage.getMainStorageIndex());
654-
@as(*SavedMetadata, @ptrCast(&real_storage.name)).* = .{
640+
ipc_metadata[ipc_metadata_len] = .{
641+
.ipc_fd = @intCast(fd),
655642
.start_index = @intCast(serialized_len),
656643
.nodes_len = @intCast(parents.len),
657644
.main_index = @intCast(main_index),
658-
.flags = .saved,
659645
};
646+
ipc_metadata_len += 1;
660647

661648
// Mount the root here.
662-
main_storage.* = storage[0];
649+
copyRoot(main_storage, &storage[0]);
663650

664651
// Copy the rest of the tree to the end.
665652
@memcpy(serialized_node_storage_buffer[serialized_len..][0 .. storage.len - 1], storage[1..]);
@@ -685,34 +672,56 @@ fn serializeIpc(start_serialized_len: usize) usize {
685672
// Save a copy in case any pipes are empty on the next update.
686673
@memcpy(parents_copy[0..serialized_len], serialized_node_parents_buffer[0..serialized_len]);
687674
@memcpy(storage_copy[0..serialized_len], serialized_node_storage_buffer[0..serialized_len]);
675+
@memcpy(ipc_metadata_copy[0..ipc_metadata_len], ipc_metadata[0..ipc_metadata_len]);
688676

689677
return serialized_len;
690678
}
691679

692-
fn useSavedIpcData(start_serialized_len: usize, main_storage: *Node.Storage, main_index: usize) usize {
693-
const saved_metadata: *SavedMetadata = @ptrCast(&main_storage.name);
694-
if (saved_metadata.flags != .saved) {
680+
fn copyRoot(dest: *Node.Storage, src: *align(2) Node.Storage) void {
681+
dest.* = .{
682+
.completed_count = src.completed_count,
683+
.estimated_total_count = src.estimated_total_count,
684+
.name = if (src.name[0] == 0) dest.name else src.name,
685+
};
686+
}
687+
688+
fn findOld(ipc_fd: posix.fd_t, old_metadata: []const SavedMetadata) ?*const SavedMetadata {
689+
for (old_metadata) |*m| {
690+
if (m.ipc_fd == ipc_fd)
691+
return m;
692+
}
693+
return null;
694+
}
695+
696+
fn useSavedIpcData(
697+
start_serialized_len: usize,
698+
main_storage: *Node.Storage,
699+
main_index: usize,
700+
old_metadata: []const SavedMetadata,
701+
) usize {
702+
const ipc_fd = main_storage.getIpcFd().?;
703+
const saved_metadata = findOld(ipc_fd, old_metadata) orelse {
695704
main_storage.completed_count = 0;
696705
main_storage.estimated_total_count = 0;
697706
return start_serialized_len;
698-
}
707+
};
699708

700709
const start_index = saved_metadata.start_index;
701710
const nodes_len = saved_metadata.nodes_len;
702711
const old_main_index = saved_metadata.main_index;
703712

704-
const real_storage: *Node.Storage = Node.storageByIndex(main_storage.getMainStorageIndex());
705-
@as(*SavedMetadata, @ptrCast(&real_storage.name)).* = .{
713+
ipc_metadata[ipc_metadata_len] = .{
714+
.ipc_fd = @intCast(ipc_fd),
706715
.start_index = @intCast(start_serialized_len),
707716
.nodes_len = nodes_len,
708717
.main_index = @intCast(main_index),
709-
.flags = .saved,
710718
};
719+
ipc_metadata_len += 1;
711720

712721
const parents = parents_copy[start_index..][0 .. nodes_len - 1];
713722
const storage = storage_copy[start_index..][0 .. nodes_len - 1];
714723

715-
main_storage.* = storage_copy[old_main_index];
724+
copyRoot(main_storage, &storage_copy[old_main_index]);
716725

717726
@memcpy(serialized_node_storage_buffer[start_serialized_len..][0..storage.len], storage);
718727

0 commit comments

Comments
 (0)