authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-12 09:40:53+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-12 09:40:53+00:00
logfa74b80ab6c4932d10fffd267d88da4a3c01b4a4
tree7e626536bf633ac1ab851eaa253cb345ee3a3e3b
parent545982c0298812869712a58b9f16772f9b09abcd
signaturelock-open Commit is signed but in an unrecognized format.

std: introduce Io.Debug proof of concept


3 files changed, 675 insertions(+), 1 deletions(-)

lib/std/Io.zig+1
...@@ -474,6 +474,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {...@@ -474,6 +474,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {
474userdata: ?*anyopaque,474userdata: ?*anyopaque,
475vtable: *const VTable,475vtable: *const VTable,
476476
477pub const Debug = @import("Io/Debug.zig");
477pub const Threaded = @import("Io/Threaded.zig");478pub const Threaded = @import("Io/Threaded.zig");
478pub const Evented = switch (builtin.os.tag) {479pub const Evented = switch (builtin.os.tag) {
479 .linux => switch (builtin.cpu.arch) {480 .linux => switch (builtin.cpu.arch) {
lib/std/Io/Debug.zig created+659
...@@ -0,0 +1,659 @@
1const Debug = @This();
2
3impl: Io,
4
5/// It is safe for this to be a `std.Thread.Mutex` because it does not guard any
6/// `Io` operations, only data structure accesses. Using an `Io.Mutex` would be
7/// undesirable as it may significantly change the scheduling behavior of the
8/// application.
9mutex: std.Thread.Mutex,
10
11gpa: Allocator,
12oom_count: u32,
13open_files: std.AutoArrayHashMapUnmanaged(Io.File.Handle, StackTrace) = .empty,
14open_dirs: std.AutoArrayHashMapUnmanaged(Io.Dir.Handle, StackTrace) = .empty,
15
16const StackTrace = struct { index: usize, buf: [6]usize };
17
18pub fn init(impl: Io, gpa: Allocator) Debug {
19 return .{
20 .impl = impl,
21 .mutex = .{},
22 .gpa = gpa,
23 .oom_count = 0,
24 .open_files = .empty,
25 .open_dirs = .empty,
26 };
27}
28
29pub fn deinit(dbg: *Debug) void {
30 if (dbg.oom_count > 0) {
31 std.log.warn("ran out of memory to track {d} handles; some leaks may not be detected", .{dbg.oom_count});
32 }
33 for (dbg.open_files.keys(), dbg.open_files.values()) |handle, *st| {
34 std.log.err("file handle '{any}' leaked: {f}", .{
35 handle,
36 @as(std.debug.FormatStackTrace, .{
37 .stack_trace = .{ .instruction_addresses = &st.buf, .index = st.index },
38 .terminal_mode = std.log.terminalMode(),
39 }),
40 });
41 }
42 for (dbg.open_dirs.keys(), dbg.open_dirs.values()) |handle, *st| {
43 std.log.err("dir handle '{any}' leaked: {f}", .{
44 handle,
45 @as(std.debug.FormatStackTrace, .{
46 .stack_trace = .{ .instruction_addresses = &st.buf, .index = st.index },
47 .terminal_mode = std.log.terminalMode(),
48 }),
49 });
50 }
51 dbg.open_files.deinit(dbg.gpa);
52 dbg.open_dirs.deinit(dbg.gpa);
53}
54
55pub fn io(dbg: *Debug) Io {
56 return .{
57 .userdata = dbg,
58 .vtable = &.{
59 .async = &async,
60 .concurrent = &concurrent,
61 .await = &await,
62 .cancel = &cancel,
63 .select = &select,
64
65 .groupAsync = &groupAsync,
66 .groupConcurrent = &groupConcurrent,
67 .groupAwait = &groupAwait,
68 .groupCancel = &groupCancel,
69
70 .recancel = &recancel,
71 .swapCancelProtection = &swapCancelProtection,
72 .checkCancel = &checkCancel,
73
74 .futexWait = &futexWait,
75 .futexWaitUncancelable = &futexWaitUncancelable,
76 .futexWake = &futexWake,
77
78 .dirCreateDir = &dirCreateDir,
79 .dirCreateDirPath = &dirCreateDirPath,
80 .dirCreateDirPathOpen = &dirCreateDirPathOpen,
81 .dirOpenDir = &dirOpenDir,
82 .dirStat = &dirStat,
83 .dirStatFile = &dirStatFile,
84 .dirAccess = &dirAccess,
85 .dirCreateFile = &dirCreateFile,
86 .dirCreateFileAtomic = &dirCreateFileAtomic,
87 .dirOpenFile = &dirOpenFile,
88 .dirClose = &dirClose,
89 .dirRead = &dirRead,
90 .dirRealPath = &dirRealPath,
91 .dirRealPathFile = &dirRealPathFile,
92 .dirDeleteFile = &dirDeleteFile,
93 .dirDeleteDir = &dirDeleteDir,
94 .dirRename = &dirRename,
95 .dirRenamePreserve = &dirRenamePreserve,
96 .dirSymLink = &dirSymLink,
97 .dirReadLink = &dirReadLink,
98 .dirSetOwner = &dirSetOwner,
99 .dirSetFileOwner = &dirSetFileOwner,
100 .dirSetPermissions = &dirSetPermissions,
101 .dirSetFilePermissions = &dirSetFilePermissions,
102 .dirSetTimestamps = &dirSetTimestamps,
103 .dirHardLink = &dirHardLink,
104
105 .fileStat = &fileStat,
106 .fileLength = &fileLength,
107 .fileClose = &fileClose,
108 .fileWriteStreaming = &fileWriteStreaming,
109 .fileWritePositional = &fileWritePositional,
110 .fileWriteFileStreaming = &fileWriteFileStreaming,
111 .fileWriteFilePositional = &fileWriteFilePositional,
112 .fileReadStreaming = &fileReadStreaming,
113 .fileReadPositional = &fileReadPositional,
114 .fileSeekBy = &fileSeekBy,
115 .fileSeekTo = &fileSeekTo,
116 .fileSync = &fileSync,
117 .fileIsTty = &fileIsTty,
118 .fileEnableAnsiEscapeCodes = &fileEnableAnsiEscapeCodes,
119 .fileSupportsAnsiEscapeCodes = &fileSupportsAnsiEscapeCodes,
120 .fileSetLength = &fileSetLength,
121 .fileSetOwner = &fileSetOwner,
122 .fileSetPermissions = &fileSetPermissions,
123 .fileSetTimestamps = &fileSetTimestamps,
124 .fileLock = &fileLock,
125 .fileTryLock = &fileTryLock,
126 .fileUnlock = &fileUnlock,
127 .fileDowngradeLock = &fileDowngradeLock,
128 .fileRealPath = &fileRealPath,
129 .fileHardLink = &fileHardLink,
130
131 .processExecutableOpen = &processExecutableOpen,
132 .processExecutablePath = &processExecutablePath,
133 .lockStderr = &lockStderr,
134 .tryLockStderr = &tryLockStderr,
135 .unlockStderr = &unlockStderr,
136 .processSetCurrentDir = &processSetCurrentDir,
137 .processReplace = &processReplace,
138 .processReplacePath = &processReplacePath,
139 .processSpawn = &processSpawn,
140 .processSpawnPath = &processSpawnPath,
141 .childWait = &childWait,
142 .childKill = &childKill,
143
144 .progressParentFile = &progressParentFile,
145
146 .now = &now,
147 .sleep = &sleep,
148
149 .random = &random,
150 .randomSecure = &randomSecure,
151
152 .netListenIp = &netListenIp,
153 .netAccept = &netAccept,
154 .netBindIp = &netBindIp,
155 .netConnectIp = &netConnectIp,
156 .netListenUnix = &netListenUnix,
157 .netConnectUnix = &netConnectUnix,
158 .netSend = &netSend,
159 .netReceive = &netReceive,
160
161 .netRead = &netRead,
162 .netWrite = &netWrite,
163 .netWriteFile = &netWriteFile,
164 .netClose = &netClose,
165 .netShutdown = &netShutdown,
166 .netInterfaceNameResolve = &netInterfaceNameResolve,
167 .netInterfaceName = &netInterfaceName,
168 .netLookup = &netLookup,
169 },
170 };
171}
172
173fn trackOpenFile(dbg: *Debug, file: Io.File, ra: usize) void {
174 dbg.mutex.lock();
175 defer dbg.mutex.unlock();
176 const gop = dbg.open_files.getOrPut(dbg.gpa, file.handle) catch |err| switch (err) {
177 error.OutOfMemory => {
178 dbg.oom_count += 1;
179 return;
180 },
181 };
182 assert(!gop.found_existing); // underlying implementation returned duplicate handle
183 const st = std.debug.captureCurrentStackTrace(.{ .first_address = ra }, &gop.value_ptr.buf);
184 gop.value_ptr.index = st.index;
185}
186
187fn trackCloseFile(dbg: *Debug, file: Io.File, ra: usize) void {
188 dbg.mutex.lock();
189 defer dbg.mutex.unlock();
190 if (!dbg.open_files.swapRemove(file.handle)) {
191 // If there was an OOM we might have failed to track the handle, but otherwise this is
192 // definitely incorrect usage.
193 if (dbg.oom_count == 0) {
194 std.debug.panicExtra(ra, "attempted to close file handle '{any}' which is not open", .{file.handle});
195 }
196 }
197}
198
199fn trackOpenDir(dbg: *Debug, dir: Io.Dir, ra: usize) void {
200 dbg.mutex.lock();
201 defer dbg.mutex.unlock();
202 const gop = dbg.open_dirs.getOrPut(dbg.gpa, dir.handle) catch |err| switch (err) {
203 error.OutOfMemory => {
204 dbg.oom_count += 1;
205 return;
206 },
207 };
208 assert(!gop.found_existing); // underlying implementation returned duplicate handle
209 const st = std.debug.captureCurrentStackTrace(.{ .first_address = ra }, &gop.value_ptr.buf);
210 gop.value_ptr.index = st.index;
211}
212
213fn trackCloseDir(dbg: *Debug, dir: Io.Dir, ra: usize) void {
214 dbg.mutex.lock();
215 defer dbg.mutex.unlock();
216 if (!dbg.open_dirs.swapRemove(dir.handle)) {
217 // If there was an OOM we might have failed to track the handle, but otherwise this is
218 // definitely incorrect usage.
219 if (dbg.oom_count == 0) {
220 std.debug.panicExtra(ra, "attempted to close dir handle '{any}' which is not open", .{dir.handle});
221 }
222 }
223}
224
225fn async(userdata: ?*anyopaque, result: []u8, result_alignment: std.mem.Alignment, context: []const u8, context_alignment: std.mem.Alignment, start: *const fn (context: *const anyopaque, result: *anyopaque) void) ?*Io.AnyFuture {
226 const dbg: *Debug = @ptrCast(@alignCast(userdata));
227 return dbg.impl.vtable.async(dbg.impl.userdata, result, result_alignment, context, context_alignment, start);
228}
229
230fn concurrent(userdata: ?*anyopaque, result_len: usize, result_alignment: std.mem.Alignment, context: []const u8, context_alignment: std.mem.Alignment, start: *const fn (context: *const anyopaque, result: *anyopaque) void) Io.ConcurrentError!*Io.AnyFuture {
231 const dbg: *Debug = @ptrCast(@alignCast(userdata));
232 return dbg.impl.vtable.concurrent(dbg.impl.userdata, result_len, result_alignment, context, context_alignment, start);
233}
234
235fn await(userdata: ?*anyopaque, any_future: *Io.AnyFuture, result: []u8, result_alignment: std.mem.Alignment) void {
236 const dbg: *Debug = @ptrCast(@alignCast(userdata));
237 return dbg.impl.vtable.await(dbg.impl.userdata, any_future, result, result_alignment);
238}
239
240fn cancel(userdata: ?*anyopaque, any_future: *Io.AnyFuture, result: []u8, result_alignment: std.mem.Alignment) void {
241 const dbg: *Debug = @ptrCast(@alignCast(userdata));
242 return dbg.impl.vtable.cancel(dbg.impl.userdata, any_future, result, result_alignment);
243}
244
245fn groupAsync(userdata: ?*anyopaque, group: *Io.Group, context: []const u8, context_alignment: std.mem.Alignment, start: *const fn (context: *const anyopaque) Cancelable!void) void {
246 const dbg: *Debug = @ptrCast(@alignCast(userdata));
247 return dbg.impl.vtable.groupAsync(dbg.impl.userdata, group, context, context_alignment, start);
248}
249
250fn groupConcurrent(userdata: ?*anyopaque, group: *Io.Group, context: []const u8, context_alignment: std.mem.Alignment, start: *const fn (context: *const anyopaque) Cancelable!void) Io.ConcurrentError!void {
251 const dbg: *Debug = @ptrCast(@alignCast(userdata));
252 return dbg.impl.vtable.groupConcurrent(dbg.impl.userdata, group, context, context_alignment, start);
253}
254fn groupAwait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) Cancelable!void {
255 const dbg: *Debug = @ptrCast(@alignCast(userdata));
256 return dbg.impl.vtable.groupAwait(dbg.impl.userdata, group, token);
257}
258fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
259 const dbg: *Debug = @ptrCast(@alignCast(userdata));
260 return dbg.impl.vtable.groupCancel(dbg.impl.userdata, group, token);
261}
262
263fn recancel(userdata: ?*anyopaque) void {
264 const dbg: *Debug = @ptrCast(@alignCast(userdata));
265 return dbg.impl.vtable.recancel(dbg.impl.userdata);
266}
267fn swapCancelProtection(userdata: ?*anyopaque, new: Io.CancelProtection) Io.CancelProtection {
268 const dbg: *Debug = @ptrCast(@alignCast(userdata));
269 return dbg.impl.vtable.swapCancelProtection(dbg.impl.userdata, new);
270}
271fn checkCancel(userdata: ?*anyopaque) Cancelable!void {
272 const dbg: *Debug = @ptrCast(@alignCast(userdata));
273 return dbg.impl.vtable.checkCancel(dbg.impl.userdata);
274}
275
276fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Cancelable!usize {
277 const dbg: *Debug = @ptrCast(@alignCast(userdata));
278 return dbg.impl.vtable.select(dbg.impl.userdata, futures);
279}
280
281fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Cancelable!void {
282 const dbg: *Debug = @ptrCast(@alignCast(userdata));
283 return dbg.impl.vtable.futexWait(dbg.impl.userdata, ptr, expected, timeout);
284}
285fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
286 const dbg: *Debug = @ptrCast(@alignCast(userdata));
287 return dbg.impl.vtable.futexWaitUncancelable(dbg.impl.userdata, ptr, expected);
288}
289fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
290 const dbg: *Debug = @ptrCast(@alignCast(userdata));
291 return dbg.impl.vtable.futexWake(dbg.impl.userdata, ptr, max_waiters);
292}
293
294fn dirCreateDir(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.Dir.Permissions) Io.Dir.CreateDirError!void {
295 const dbg: *Debug = @ptrCast(@alignCast(userdata));
296 return dbg.impl.vtable.dirCreateDir(dbg.impl.userdata, dir, sub_path, permissions);
297}
298fn dirCreateDirPath(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.Dir.Permissions) Io.Dir.CreateDirPathError!Io.Dir.CreatePathStatus {
299 const dbg: *Debug = @ptrCast(@alignCast(userdata));
300 return dbg.impl.vtable.dirCreateDirPath(dbg.impl.userdata, dir, sub_path, permissions);
301}
302fn dirCreateDirPathOpen(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.Dir.Permissions, options: Io.Dir.OpenOptions) Io.Dir.CreateDirPathOpenError!Io.Dir {
303 const dbg: *Debug = @ptrCast(@alignCast(userdata));
304 const new_dir = try dbg.impl.vtable.dirCreateDirPathOpen(dbg.impl.userdata, dir, sub_path, permissions, options);
305 dbg.trackOpenDir(new_dir, @returnAddress());
306 return new_dir;
307}
308fn dirOpenDir(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.OpenOptions) Io.Dir.OpenError!Io.Dir {
309 const dbg: *Debug = @ptrCast(@alignCast(userdata));
310 const new_dir = try dbg.impl.vtable.dirOpenDir(dbg.impl.userdata, dir, sub_path, options);
311 dbg.trackOpenDir(new_dir, @returnAddress());
312 return new_dir;
313}
314fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
315 const dbg: *Debug = @ptrCast(@alignCast(userdata));
316 return dbg.impl.vtable.dirStat(dbg.impl.userdata, dir);
317}
318fn dirStatFile(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.StatFileOptions) Io.Dir.StatFileError!Io.File.Stat {
319 const dbg: *Debug = @ptrCast(@alignCast(userdata));
320 return dbg.impl.vtable.dirStatFile(dbg.impl.userdata, dir, sub_path, options);
321}
322fn dirAccess(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) Io.Dir.AccessError!void {
323 const dbg: *Debug = @ptrCast(@alignCast(userdata));
324 return dbg.impl.vtable.dirAccess(dbg.impl.userdata, dir, sub_path, options);
325}
326fn dirCreateFile(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, flags: Io.File.CreateFlags) Io.File.OpenError!Io.File {
327 const dbg: *Debug = @ptrCast(@alignCast(userdata));
328 const file = try dbg.impl.vtable.dirCreateFile(dbg.impl.userdata, dir, sub_path, flags);
329 dbg.trackOpenFile(file, @returnAddress());
330 return file;
331}
332fn dirCreateFileAtomic(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.CreateFileAtomicOptions) Io.Dir.CreateFileAtomicError!Io.File.Atomic {
333 const dbg: *Debug = @ptrCast(@alignCast(userdata));
334 const af = try dbg.impl.vtable.dirCreateFileAtomic(dbg.impl.userdata, dir, sub_path, options);
335 if (af.file_open) dbg.trackOpenFile(af.file, @returnAddress());
336 return af;
337}
338fn dirOpenFile(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, flags: Io.File.OpenFlags) Io.File.OpenError!Io.File {
339 const dbg: *Debug = @ptrCast(@alignCast(userdata));
340 const file = try dbg.impl.vtable.dirOpenFile(dbg.impl.userdata, dir, sub_path, flags);
341 dbg.trackOpenFile(file, @returnAddress());
342 return file;
343}
344fn dirClose(userdata: ?*anyopaque, dirs: []const Io.Dir) void {
345 const dbg: *Debug = @ptrCast(@alignCast(userdata));
346 for (dirs) |dir| dbg.trackCloseDir(dir, @returnAddress());
347 return dbg.impl.vtable.dirClose(dbg.impl.userdata, dirs);
348}
349fn dirRead(userdata: ?*anyopaque, dr: *Io.Dir.Reader, buffer: []Io.Dir.Entry) Io.Dir.Reader.Error!usize {
350 const dbg: *Debug = @ptrCast(@alignCast(userdata));
351 return dbg.impl.vtable.dirRead(dbg.impl.userdata, dr, buffer);
352}
353fn dirRealPath(userdata: ?*anyopaque, dir: Io.Dir, out_buffer: []u8) Io.Dir.RealPathError!usize {
354 const dbg: *Debug = @ptrCast(@alignCast(userdata));
355 return dbg.impl.vtable.dirRealPath(dbg.impl.userdata, dir, out_buffer);
356}
357fn dirRealPathFile(userdata: ?*anyopaque, dir: Io.Dir, path_name: []const u8, out_buffer: []u8) Io.Dir.RealPathFileError!usize {
358 const dbg: *Debug = @ptrCast(@alignCast(userdata));
359 return dbg.impl.vtable.dirRealPathFile(dbg.impl.userdata, dir, path_name, out_buffer);
360}
361fn dirDeleteFile(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void {
362 const dbg: *Debug = @ptrCast(@alignCast(userdata));
363 return dbg.impl.vtable.dirDeleteFile(dbg.impl.userdata, dir, sub_path);
364}
365fn dirDeleteDir(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void {
366 const dbg: *Debug = @ptrCast(@alignCast(userdata));
367 return dbg.impl.vtable.dirDeleteDir(dbg.impl.userdata, dir, sub_path);
368}
369fn dirRename(userdata: ?*anyopaque, old_dir: Io.Dir, old_sub_path: []const u8, new_dir: Io.Dir, new_sub_path: []const u8) Io.Dir.RenameError!void {
370 const dbg: *Debug = @ptrCast(@alignCast(userdata));
371 return dbg.impl.vtable.dirRename(dbg.impl.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
372}
373fn dirRenamePreserve(userdata: ?*anyopaque, old_dir: Io.Dir, old_sub_path: []const u8, new_dir: Io.Dir, new_sub_path: []const u8) Io.Dir.RenamePreserveError!void {
374 const dbg: *Debug = @ptrCast(@alignCast(userdata));
375 return dbg.impl.vtable.dirRenamePreserve(dbg.impl.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
376}
377fn dirSymLink(userdata: ?*anyopaque, dir: Io.Dir, target_path: []const u8, sym_link_path: []const u8, flags: Io.Dir.SymLinkFlags) Io.Dir.SymLinkError!void {
378 const dbg: *Debug = @ptrCast(@alignCast(userdata));
379 return dbg.impl.vtable.dirSymLink(dbg.impl.userdata, dir, target_path, sym_link_path, flags);
380}
381fn dirReadLink(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize {
382 const dbg: *Debug = @ptrCast(@alignCast(userdata));
383 return dbg.impl.vtable.dirReadLink(dbg.impl.userdata, dir, sub_path, buffer);
384}
385fn dirSetOwner(userdata: ?*anyopaque, dir: Io.Dir, uid: ?Io.File.Uid, gid: ?Io.File.Gid) Io.Dir.SetOwnerError!void {
386 const dbg: *Debug = @ptrCast(@alignCast(userdata));
387 return dbg.impl.vtable.dirSetOwner(dbg.impl.userdata, dir, uid, gid);
388}
389fn dirSetFileOwner(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, uid: ?Io.File.Uid, gid: ?Io.File.Gid, options: Io.Dir.SetFileOwnerOptions) Io.Dir.SetFileOwnerError!void {
390 const dbg: *Debug = @ptrCast(@alignCast(userdata));
391 return dbg.impl.vtable.dirSetFileOwner(dbg.impl.userdata, dir, sub_path, uid, gid, options);
392}
393fn dirSetPermissions(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io.Dir.Permissions) Io.Dir.SetPermissionsError!void {
394 const dbg: *Debug = @ptrCast(@alignCast(userdata));
395 return dbg.impl.vtable.dirSetPermissions(dbg.impl.userdata, dir, permissions);
396}
397fn dirSetFilePermissions(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.File.Permissions, options: Io.Dir.SetFilePermissionsOptions) Io.Dir.SetFilePermissionsError!void {
398 const dbg: *Debug = @ptrCast(@alignCast(userdata));
399 return dbg.impl.vtable.dirSetFilePermissions(dbg.impl.userdata, dir, sub_path, permissions, options);
400}
401fn dirSetTimestamps(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.SetTimestampsOptions) Io.Dir.SetTimestampsError!void {
402 const dbg: *Debug = @ptrCast(@alignCast(userdata));
403 return dbg.impl.vtable.dirSetTimestamps(dbg.impl.userdata, dir, sub_path, options);
404}
405fn dirHardLink(userdata: ?*anyopaque, old_dir: Io.Dir, old_sub_path: []const u8, new_dir: Io.Dir, new_sub_path: []const u8, options: Io.Dir.HardLinkOptions) Io.Dir.HardLinkError!void {
406 const dbg: *Debug = @ptrCast(@alignCast(userdata));
407 return dbg.impl.vtable.dirHardLink(dbg.impl.userdata, old_dir, old_sub_path, new_dir, new_sub_path, options);
408}
409
410fn fileStat(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
411 const dbg: *Debug = @ptrCast(@alignCast(userdata));
412 return dbg.impl.vtable.fileStat(dbg.impl.userdata, file);
413}
414fn fileLength(userdata: ?*anyopaque, file: Io.File) Io.File.LengthError!u64 {
415 const dbg: *Debug = @ptrCast(@alignCast(userdata));
416 return dbg.impl.vtable.fileLength(dbg.impl.userdata, file);
417}
418fn fileClose(userdata: ?*anyopaque, files: []const Io.File) void {
419 const dbg: *Debug = @ptrCast(@alignCast(userdata));
420 for (files) |file| dbg.trackCloseFile(file, @returnAddress());
421 return dbg.impl.vtable.fileClose(dbg.impl.userdata, files);
422}
423fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, header: []const u8, data: []const []const u8, splat: usize) Io.File.Writer.Error!usize {
424 const dbg: *Debug = @ptrCast(@alignCast(userdata));
425 return dbg.impl.vtable.fileWriteStreaming(dbg.impl.userdata, file, header, data, splat);
426}
427fn fileWritePositional(userdata: ?*anyopaque, file: Io.File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) Io.File.WritePositionalError!usize {
428 const dbg: *Debug = @ptrCast(@alignCast(userdata));
429 return dbg.impl.vtable.fileWritePositional(dbg.impl.userdata, file, header, data, splat, offset);
430}
431fn fileWriteFileStreaming(userdata: ?*anyopaque, file: Io.File, header: []const u8, fr: *Io.File.Reader, limit: Io.Limit) Io.File.Writer.WriteFileError!usize {
432 const dbg: *Debug = @ptrCast(@alignCast(userdata));
433 return dbg.impl.vtable.fileWriteFileStreaming(dbg.impl.userdata, file, header, fr, limit);
434}
435fn fileWriteFilePositional(userdata: ?*anyopaque, file: Io.File, header: []const u8, fr: *Io.File.Reader, limit: Io.Limit, offset: u64) Io.File.WriteFilePositionalError!usize {
436 const dbg: *Debug = @ptrCast(@alignCast(userdata));
437 return dbg.impl.vtable.fileWriteFilePositional(dbg.impl.userdata, file, header, fr, limit, offset);
438}
439
440fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: []const []u8) Io.File.Reader.Error!usize {
441 const dbg: *Debug = @ptrCast(@alignCast(userdata));
442 return dbg.impl.vtable.fileReadStreaming(dbg.impl.userdata, file, data);
443}
444
445fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: []const []u8, offset: u64) Io.File.ReadPositionalError!usize {
446 const dbg: *Debug = @ptrCast(@alignCast(userdata));
447 return dbg.impl.vtable.fileReadPositional(dbg.impl.userdata, file, data, offset);
448}
449fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, relative_offset: i64) Io.File.SeekError!void {
450 const dbg: *Debug = @ptrCast(@alignCast(userdata));
451 return dbg.impl.vtable.fileSeekBy(dbg.impl.userdata, file, relative_offset);
452}
453fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, absolute_offset: u64) Io.File.SeekError!void {
454 const dbg: *Debug = @ptrCast(@alignCast(userdata));
455 return dbg.impl.vtable.fileSeekTo(dbg.impl.userdata, file, absolute_offset);
456}
457fn fileSync(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void {
458 const dbg: *Debug = @ptrCast(@alignCast(userdata));
459 return dbg.impl.vtable.fileSync(dbg.impl.userdata, file);
460}
461fn fileIsTty(userdata: ?*anyopaque, file: Io.File) Cancelable!bool {
462 const dbg: *Debug = @ptrCast(@alignCast(userdata));
463 return dbg.impl.vtable.fileIsTty(dbg.impl.userdata, file);
464}
465fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.File.EnableAnsiEscapeCodesError!void {
466 const dbg: *Debug = @ptrCast(@alignCast(userdata));
467 return dbg.impl.vtable.fileEnableAnsiEscapeCodes(dbg.impl.userdata, file);
468}
469fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Cancelable!bool {
470 const dbg: *Debug = @ptrCast(@alignCast(userdata));
471 return dbg.impl.vtable.fileSupportsAnsiEscapeCodes(dbg.impl.userdata, file);
472}
473fn fileSetLength(userdata: ?*anyopaque, file: Io.File, length: u64) Io.File.SetLengthError!void {
474 const dbg: *Debug = @ptrCast(@alignCast(userdata));
475 return dbg.impl.vtable.fileSetLength(dbg.impl.userdata, file, length);
476}
477fn fileSetOwner(userdata: ?*anyopaque, file: Io.File, uid: ?Io.File.Uid, gid: ?Io.File.Gid) Io.File.SetOwnerError!void {
478 const dbg: *Debug = @ptrCast(@alignCast(userdata));
479 return dbg.impl.vtable.fileSetOwner(dbg.impl.userdata, file, uid, gid);
480}
481fn fileSetPermissions(userdata: ?*anyopaque, file: Io.File, permissions: Io.File.Permissions) Io.File.SetPermissionsError!void {
482 const dbg: *Debug = @ptrCast(@alignCast(userdata));
483 return dbg.impl.vtable.fileSetPermissions(dbg.impl.userdata, file, permissions);
484}
485fn fileSetTimestamps(userdata: ?*anyopaque, file: Io.File, options: Io.File.SetTimestampsOptions) Io.File.SetTimestampsError!void {
486 const dbg: *Debug = @ptrCast(@alignCast(userdata));
487 return dbg.impl.vtable.fileSetTimestamps(dbg.impl.userdata, file, options);
488}
489fn fileLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!void {
490 const dbg: *Debug = @ptrCast(@alignCast(userdata));
491 return dbg.impl.vtable.fileLock(dbg.impl.userdata, file, lock);
492}
493fn fileTryLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!bool {
494 const dbg: *Debug = @ptrCast(@alignCast(userdata));
495 return dbg.impl.vtable.fileTryLock(dbg.impl.userdata, file, lock);
496}
497fn fileUnlock(userdata: ?*anyopaque, file: Io.File) void {
498 const dbg: *Debug = @ptrCast(@alignCast(userdata));
499 return dbg.impl.vtable.fileUnlock(dbg.impl.userdata, file);
500}
501fn fileDowngradeLock(userdata: ?*anyopaque, file: Io.File) Io.File.DowngradeLockError!void {
502 const dbg: *Debug = @ptrCast(@alignCast(userdata));
503 return dbg.impl.vtable.fileDowngradeLock(dbg.impl.userdata, file);
504}
505fn fileRealPath(userdata: ?*anyopaque, file: Io.File, out_buffer: []u8) Io.File.RealPathError!usize {
506 const dbg: *Debug = @ptrCast(@alignCast(userdata));
507 return dbg.impl.vtable.fileRealPath(dbg.impl.userdata, file, out_buffer);
508}
509fn fileHardLink(userdata: ?*anyopaque, file: Io.File, dir: Io.Dir, sub_path: []const u8, options: Io.File.HardLinkOptions) Io.File.HardLinkError!void {
510 const dbg: *Debug = @ptrCast(@alignCast(userdata));
511 return dbg.impl.vtable.fileHardLink(dbg.impl.userdata, file, dir, sub_path, options);
512}
513
514fn processExecutableOpen(userdata: ?*anyopaque, flags: Io.File.OpenFlags) std.process.OpenExecutableError!Io.File {
515 const dbg: *Debug = @ptrCast(@alignCast(userdata));
516 const file = try dbg.impl.vtable.processExecutableOpen(dbg.impl.userdata, flags);
517 dbg.trackOpenFile(file, @returnAddress());
518 return file;
519}
520fn processExecutablePath(userdata: ?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize {
521 const dbg: *Debug = @ptrCast(@alignCast(userdata));
522 return dbg.impl.vtable.processExecutablePath(dbg.impl.userdata, buffer);
523}
524fn lockStderr(userdata: ?*anyopaque, mode: ?Io.Terminal.Mode) Cancelable!Io.LockedStderr {
525 const dbg: *Debug = @ptrCast(@alignCast(userdata));
526 return dbg.impl.vtable.lockStderr(dbg.impl.userdata, mode);
527}
528fn tryLockStderr(userdata: ?*anyopaque, mode: ?Io.Terminal.Mode) Cancelable!?Io.LockedStderr {
529 const dbg: *Debug = @ptrCast(@alignCast(userdata));
530 return dbg.impl.vtable.tryLockStderr(dbg.impl.userdata, mode);
531}
532fn unlockStderr(userdata: ?*anyopaque) void {
533 const dbg: *Debug = @ptrCast(@alignCast(userdata));
534 return dbg.impl.vtable.unlockStderr(dbg.impl.userdata);
535}
536fn processSetCurrentDir(userdata: ?*anyopaque, dir: Io.Dir) std.process.SetCurrentDirError!void {
537 const dbg: *Debug = @ptrCast(@alignCast(userdata));
538 return dbg.impl.vtable.processSetCurrentDir(dbg.impl.userdata, dir);
539}
540fn processReplace(userdata: ?*anyopaque, options: std.process.ReplaceOptions) std.process.ReplaceError {
541 const dbg: *Debug = @ptrCast(@alignCast(userdata));
542 return dbg.impl.vtable.processReplace(dbg.impl.userdata, options);
543}
544fn processReplacePath(userdata: ?*anyopaque, dir: Io.Dir, options: std.process.ReplaceOptions) std.process.ReplaceError {
545 const dbg: *Debug = @ptrCast(@alignCast(userdata));
546 return dbg.impl.vtable.processReplacePath(dbg.impl.userdata, dir, options);
547}
548fn processSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
549 const dbg: *Debug = @ptrCast(@alignCast(userdata));
550 return dbg.impl.vtable.processSpawn(dbg.impl.userdata, options);
551}
552fn processSpawnPath(userdata: ?*anyopaque, dir: Io.Dir, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
553 const dbg: *Debug = @ptrCast(@alignCast(userdata));
554 return dbg.impl.vtable.processSpawnPath(dbg.impl.userdata, dir, options);
555}
556fn childWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term {
557 const dbg: *Debug = @ptrCast(@alignCast(userdata));
558 return dbg.impl.vtable.childWait(dbg.impl.userdata, child);
559}
560fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void {
561 const dbg: *Debug = @ptrCast(@alignCast(userdata));
562 return dbg.impl.vtable.childKill(dbg.impl.userdata, child);
563}
564
565fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!Io.File {
566 const dbg: *Debug = @ptrCast(@alignCast(userdata));
567 return dbg.impl.vtable.progressParentFile(dbg.impl.userdata);
568}
569
570fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
571 const dbg: *Debug = @ptrCast(@alignCast(userdata));
572 return dbg.impl.vtable.now(dbg.impl.userdata, clock);
573}
574fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
575 const dbg: *Debug = @ptrCast(@alignCast(userdata));
576 return dbg.impl.vtable.sleep(dbg.impl.userdata, timeout);
577}
578
579fn random(userdata: ?*anyopaque, buffer: []u8) void {
580 const dbg: *Debug = @ptrCast(@alignCast(userdata));
581 return dbg.impl.vtable.random(dbg.impl.userdata, buffer);
582}
583fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
584 const dbg: *Debug = @ptrCast(@alignCast(userdata));
585 return dbg.impl.vtable.randomSecure(dbg.impl.userdata, buffer);
586}
587
588fn netListenIp(userdata: ?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server {
589 const dbg: *Debug = @ptrCast(@alignCast(userdata));
590 return dbg.impl.vtable.netListenIp(dbg.impl.userdata, address, options);
591}
592fn netAccept(userdata: ?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream {
593 const dbg: *Debug = @ptrCast(@alignCast(userdata));
594 return dbg.impl.vtable.netAccept(dbg.impl.userdata, server);
595}
596fn netBindIp(userdata: ?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket {
597 const dbg: *Debug = @ptrCast(@alignCast(userdata));
598 return dbg.impl.vtable.netBindIp(dbg.impl.userdata, address, options);
599}
600fn netConnectIp(userdata: ?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream {
601 const dbg: *Debug = @ptrCast(@alignCast(userdata));
602 return dbg.impl.vtable.netConnectIp(dbg.impl.userdata, address, options);
603}
604fn netListenUnix(userdata: ?*anyopaque, address: *const net.UnixAddress, options: net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle {
605 const dbg: *Debug = @ptrCast(@alignCast(userdata));
606 return dbg.impl.vtable.netListenUnix(dbg.impl.userdata, address, options);
607}
608fn netConnectUnix(userdata: ?*anyopaque, address: *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle {
609 const dbg: *Debug = @ptrCast(@alignCast(userdata));
610 return dbg.impl.vtable.netConnectUnix(dbg.impl.userdata, address);
611}
612fn netSend(userdata: ?*anyopaque, handle: net.Socket.Handle, messages: []net.OutgoingMessage, flags: net.SendFlags) struct { ?net.Socket.SendError, usize } {
613 const dbg: *Debug = @ptrCast(@alignCast(userdata));
614 return dbg.impl.vtable.netSend(dbg.impl.userdata, handle, messages, flags);
615}
616fn netReceive(userdata: ?*anyopaque, handle: net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, flags: net.ReceiveFlags, timeout: Io.Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize } {
617 const dbg: *Debug = @ptrCast(@alignCast(userdata));
618 return dbg.impl.vtable.netReceive(dbg.impl.userdata, handle, message_buffer, data_buffer, flags, timeout);
619}
620
621fn netRead(userdata: ?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
622 const dbg: *Debug = @ptrCast(@alignCast(userdata));
623 return dbg.impl.vtable.netRead(dbg.impl.userdata, src, data);
624}
625fn netWrite(userdata: ?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize {
626 const dbg: *Debug = @ptrCast(@alignCast(userdata));
627 return dbg.impl.vtable.netWrite(dbg.impl.userdata, dest, header, data, splat);
628}
629fn netWriteFile(userdata: ?*anyopaque, dest: net.Socket.Handle, header: []const u8, fr: *Io.File.Reader, limit: Io.Limit) net.Stream.Writer.WriteFileError!usize {
630 const dbg: *Debug = @ptrCast(@alignCast(userdata));
631 return dbg.impl.vtable.netWriteFile(dbg.impl.userdata, dest, header, fr, limit);
632}
633fn netClose(userdata: ?*anyopaque, handle: []const net.Socket.Handle) void {
634 const dbg: *Debug = @ptrCast(@alignCast(userdata));
635 return dbg.impl.vtable.netClose(dbg.impl.userdata, handle);
636}
637fn netShutdown(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
638 const dbg: *Debug = @ptrCast(@alignCast(userdata));
639 return dbg.impl.vtable.netShutdown(dbg.impl.userdata, handle, how);
640}
641fn netInterfaceNameResolve(userdata: ?*anyopaque, name: *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface {
642 const dbg: *Debug = @ptrCast(@alignCast(userdata));
643 return dbg.impl.vtable.netInterfaceNameResolve(dbg.impl.userdata, name);
644}
645fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name {
646 const dbg: *Debug = @ptrCast(@alignCast(userdata));
647 return dbg.impl.vtable.netInterfaceName(dbg.impl.userdata, interface);
648}
649fn netLookup(userdata: ?*anyopaque, host_name: net.HostName, resolved: *Io.Queue(net.HostName.LookupResult), options: net.HostName.LookupOptions) net.HostName.LookupError!void {
650 const dbg: *Debug = @ptrCast(@alignCast(userdata));
651 return dbg.impl.vtable.netLookup(dbg.impl.userdata, host_name, resolved, options);
652}
653
654const std = @import("../std.zig");
655const Allocator = std.mem.Allocator;
656const Cancelable = Io.Cancelable;
657const Io = std.Io;
658const assert = std.debug.assert;
659const net = Io.net;
lib/std/start.zig+15-1
...@@ -669,6 +669,11 @@ const use_debug_allocator = !is_wasm and switch (builtin.mode) {...@@ -669,6 +669,11 @@ const use_debug_allocator = !is_wasm and switch (builtin.mode) {
669 .ReleaseFast, .ReleaseSmall => !builtin.link_libc and builtin.single_threaded, // Also not ideal.669 .ReleaseFast, .ReleaseSmall => !builtin.link_libc and builtin.single_threaded, // Also not ideal.
670};670};
671var debug_allocator: std.heap.DebugAllocator(.{}) = .init;671var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
672const use_debug_io = switch (builtin.mode) {
673 .Debug, .ReleaseSafe => true,
674 .ReleaseFast, .ReleaseSmall => false,
675};
676var debug_io: std.Io.Debug = undefined;
672677
673inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 {678inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 {
674 const fn_info = @typeInfo(@TypeOf(root.main)).@"fn";679 const fn_info = @typeInfo(@TypeOf(root.main)).@"fn";
...@@ -704,6 +709,15 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B...@@ -704,6 +709,15 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B
704 });709 });
705 defer threaded.deinit();710 defer threaded.deinit();
706711
712 if (use_debug_io) {
713 debug_io = .init(threaded.io(), gpa);
714 }
715 defer if (use_debug_io) {
716 debug_io.deinit();
717 };
718
719 const io = if (use_debug_io) debug_io.io() else threaded.io();
720
707 var environ_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err|721 var environ_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err|
708 std.process.fatal("failed to parse environment variables: {t}", .{err});722 std.process.fatal("failed to parse environment variables: {t}", .{err});
709 defer environ_map.deinit();723 defer environ_map.deinit();
...@@ -718,7 +732,7 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B...@@ -718,7 +732,7 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B
718 },732 },
719 .arena = &arena_allocator,733 .arena = &arena_allocator,
720 .gpa = gpa,734 .gpa = gpa,
721 .io = threaded.io(),735 .io = io,
722 .environ_map = &environ_map,736 .environ_map = &environ_map,
723 .preopens = preopens,737 .preopens = preopens,
724 }));738 }));