| ... | @@ -69,6 +69,19 @@ random_file: RandomFile = .{}, | ... | @@ -69,6 +69,19 @@ random_file: RandomFile = .{}, |
| 69 | | 69 | |
| 70 | csprng: Csprng = .{}, | 70 | csprng: Csprng = .{}, |
| 71 | | 71 | |
| | 72 | /// Tracks open file handles for debugging purposes. |
| | 73 | open_file_map: OpenFileMap = if (OpenFileMap != void) .{} else {}, |
| | 74 | |
| | 75 | pub const OpenFileMap = switch (builtin.mode) { |
| | 76 | .Debug => struct { |
| | 77 | map: std.AutoArrayHashMapUnmanaged(File.Handle, StackTrace) = .empty, |
| | 78 | oom: bool = false, |
| | 79 | }, |
| | 80 | else => void, |
| | 81 | }; |
| | 82 | |
| | 83 | pub const StackTrace = [6]usize; |
| | 84 | |
| 72 | pub const Csprng = struct { | 85 | pub const Csprng = struct { |
| 73 | rng: std.Random.DefaultCsprng = .{ | 86 | rng: std.Random.DefaultCsprng = .{ |
| 74 | .state = undefined, | 87 | .state = undefined, |
| ... | @@ -1315,6 +1328,7 @@ pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { | ... | @@ -1315,6 +1328,7 @@ pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { |
| 1315 | } | 1328 | } |
| 1316 | | 1329 | |
| 1317 | pub fn deinit(t: *Threaded) void { | 1330 | pub fn deinit(t: *Threaded) void { |
| | 1331 | const gpa = t.allocator; |
| 1318 | t.join(); | 1332 | t.join(); |
| 1319 | if (is_windows and t.wsa.status == .initialized) { | 1333 | if (is_windows and t.wsa.status == .initialized) { |
| 1320 | if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); | 1334 | if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); |
| ... | @@ -1325,6 +1339,22 @@ pub fn deinit(t: *Threaded) void { | ... | @@ -1325,6 +1339,22 @@ pub fn deinit(t: *Threaded) void { |
| 1325 | } | 1339 | } |
| 1326 | t.null_file.deinit(); | 1340 | t.null_file.deinit(); |
| 1327 | t.random_file.deinit(); | 1341 | t.random_file.deinit(); |
| | 1342 | if (OpenFileMap != void) { |
| | 1343 | for (t.open_file_map.map.keys(), t.open_file_map.map.values()) |handle, *value| { |
| | 1344 | const st: std.builtin.StackTrace = .{ |
| | 1345 | .instruction_addresses = value, |
| | 1346 | .index = value.len, |
| | 1347 | }; |
| | 1348 | std.log.err("file handle {any} leaked: {f}", .{ |
| | 1349 | handle, |
| | 1350 | std.debug.FormatStackTrace{ |
| | 1351 | .stack_trace = st, |
| | 1352 | .terminal_mode = std.log.terminalMode(), |
| | 1353 | }, |
| | 1354 | }); |
| | 1355 | } |
| | 1356 | t.open_file_map.map.deinit(gpa); |
| | 1357 | } |
| 1328 | t.* = undefined; | 1358 | t.* = undefined; |
| 1329 | } | 1359 | } |
| 1330 | | 1360 | |
| ... | @@ -3863,9 +3893,35 @@ fn dirOpenFilePosix( | ... | @@ -3863,9 +3893,35 @@ fn dirOpenFilePosix( |
| 3863 | } | 3893 | } |
| 3864 | } | 3894 | } |
| 3865 | | 3895 | |
| | 3896 | trackOpenFile(t, fd, @returnAddress()); |
| 3866 | return .{ .handle = fd }; | 3897 | return .{ .handle = fd }; |
| 3867 | } | 3898 | } |
| 3868 | | 3899 | |
| | 3900 | fn trackOpenFile(t: *Threaded, handle: File.Handle, ra: usize) void { |
| | 3901 | if (OpenFileMap == void) return; |
| | 3902 | t.mutex.lock(); |
| | 3903 | defer t.mutex.unlock(); |
| | 3904 | if (t.open_file_map.oom) return; |
| | 3905 | const gop = t.open_file_map.map.getOrPut(t.allocator, handle) catch |err| switch (err) { |
| | 3906 | error.OutOfMemory => { |
| | 3907 | t.open_file_map.oom = true; |
| | 3908 | t.open_file_map.map.clearAndFree(t.allocator); |
| | 3909 | return; |
| | 3910 | }, |
| | 3911 | }; |
| | 3912 | assert(!gop.found_existing); |
| | 3913 | const st = std.debug.captureCurrentStackTrace(.{ .first_address = ra }, gop.value_ptr); |
| | 3914 | @memset(gop.value_ptr[@min(st.index, gop.value_ptr.len)..], 0); |
| | 3915 | } |
| | 3916 | |
| | 3917 | fn trackCloseFile(t: *Threaded, handle: File.Handle) void { |
| | 3918 | if (OpenFileMap == void) return; |
| | 3919 | t.mutex.lock(); |
| | 3920 | defer t.mutex.unlock(); |
| | 3921 | if (t.open_file_map.oom) return; |
| | 3922 | assert(t.open_file_map.map.swapRemove(handle)); |
| | 3923 | } |
| | 3924 | |
| 3869 | fn dirOpenFileWindows( | 3925 | fn dirOpenFileWindows( |
| 3870 | userdata: ?*anyopaque, | 3926 | userdata: ?*anyopaque, |
| 3871 | dir: Dir, | 3927 | dir: Dir, |
| ... | @@ -3873,14 +3929,14 @@ fn dirOpenFileWindows( | ... | @@ -3873,14 +3929,14 @@ fn dirOpenFileWindows( |
| 3873 | flags: File.OpenFlags, | 3929 | flags: File.OpenFlags, |
| 3874 | ) File.OpenError!File { | 3930 | ) File.OpenError!File { |
| 3875 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 3931 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3876 | _ = t; | | |
| 3877 | const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); | 3932 | const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 3878 | const sub_path_w = sub_path_w_array.span(); | 3933 | const sub_path_w = sub_path_w_array.span(); |
| 3879 | const dir_handle = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; | 3934 | const dir_handle = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; |
| 3880 | return dirOpenFileWtf16(dir_handle, sub_path_w, flags); | 3935 | return dirOpenFileWtf16(t, dir_handle, sub_path_w, flags); |
| 3881 | } | 3936 | } |
| 3882 | | 3937 | |
| 3883 | pub fn dirOpenFileWtf16( | 3938 | pub fn dirOpenFileWtf16( |
| | 3939 | t: *Threaded, |
| 3884 | dir_handle: ?windows.HANDLE, | 3940 | dir_handle: ?windows.HANDLE, |
| 3885 | sub_path_w: [:0]const u16, | 3941 | sub_path_w: [:0]const u16, |
| 3886 | flags: File.OpenFlags, | 3942 | flags: File.OpenFlags, |
| ... | @@ -4022,6 +4078,7 @@ pub fn dirOpenFileWtf16( | ... | @@ -4022,6 +4078,7 @@ pub fn dirOpenFileWtf16( |
| 4022 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer | 4078 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 4023 | else => |status| return syscall.unexpectedNtstatus(status), | 4079 | else => |status| return syscall.unexpectedNtstatus(status), |
| 4024 | }; | 4080 | }; |
| | 4081 | trackOpenFile(t, handle, @returnAddress()); |
| 4025 | return .{ .handle = handle }; | 4082 | return .{ .handle = handle }; |
| 4026 | } | 4083 | } |
| 4027 | | 4084 | |
| ... | @@ -4114,6 +4171,7 @@ fn dirOpenFileWasi( | ... | @@ -4114,6 +4171,7 @@ fn dirOpenFileWasi( |
| 4114 | if (is_dir) return error.IsDir; | 4171 | if (is_dir) return error.IsDir; |
| 4115 | } | 4172 | } |
| 4116 | | 4173 | |
| | 4174 | trackOpenFile(t, fd, @returnAddress()); |
| 4117 | return .{ .handle = fd }; | 4175 | return .{ .handle = fd }; |
| 4118 | } | 4176 | } |
| 4119 | | 4177 | |
| ... | @@ -7856,8 +7914,10 @@ fn dirHardLink( | ... | @@ -7856,8 +7914,10 @@ fn dirHardLink( |
| 7856 | | 7914 | |
| 7857 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { | 7915 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { |
| 7858 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 7916 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7859 | _ = t; | 7917 | for (files) |file| { |
| 7860 | for (files) |file| posix.close(file.handle); | 7918 | trackCloseFile(t, file.handle); |
| | 7919 | posix.close(file.handle); |
| | 7920 | } |
| 7861 | } | 7921 | } |
| 7862 | | 7922 | |
| 7863 | const fileReadStreaming = switch (native_os) { | 7923 | const fileReadStreaming = switch (native_os) { |