authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-06 17:56:40-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-06 18:05:50-05:00
log0b5bcd2f56a84e66d5c700744ec1838381893667
tree5b640a57055e50636fe7a9f782915b2126395ef3
parent704cd977bdcdfa8cff4e70aaad93857d9b622fc7
signaturelock-open Commit is signed but in an unrecognized format.

more std lib async I/O integration

* `zig test` gainst `--test-evented-io` parameter and gains the ability to seamlessly run async tests. * `std.ChildProcess` opens its child process pipe with O_NONBLOCK when using evented I/O * `std.io.getStdErr()` gives a File that is blocking even in evented I/O mode. * Delete `std.event.fs`. The functionality is now merged into `std.fs` and async file system access (using a dedicated thread) is automatically handled. * `std.fs.File` can be configured to specify whether its handle is expected to block, and whether that is OK to block even when in async I/O mode. This makes async I/O work correctly for e.g. the file system as well as network. * `std.fs.File` has some deprecated functions removed. * Missing readv,writev,pread,pwrite,preadv,pwritev functions are added to `std.os` and `std.fs.File`. They are all integrated with async I/O. * `std.fs.Watch` is still bit rotted and needs to be audited in light of the new async/await syntax. * `std.io.OutStream` integrates with async I/O * linked list nodes in the std lib have default `null` values for `prev` and `next`. * Windows async I/O integration is enabled for reading/writing file handles. * Added `std.os.mode_t`. Integer sizes need to be audited. * Fixed #4403 which was causing compiler to crash. This is working towards: ./zig test ../test/stage1/behavior.zig --test-evented-io Which does not successfully build yet. I'd like to enable behavioral tests and std lib tests with --test-evented-io in the test matrix in the future, to prevent regressions.

24 files changed, 1572 insertions(+), 1691 deletions(-)

lib/std/builtin.zig+1
......@@ -458,6 +458,7 @@ pub const ExportOptions = struct {
458458pub const TestFn = struct {
459459 name: []const u8,
460460 func: fn () anyerror!void,
461 async_frame_size: ?usize,
461462};
462463
463464/// This function type is used by the Zig language code generation and
lib/std/child_process.zig+44-15
......@@ -329,17 +329,18 @@ pub const ChildProcess = struct {
329329 }
330330
331331 fn spawnPosix(self: *ChildProcess) SpawnError!void {
332 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined;
332 const pipe_flags = if (io.is_async) os.O_NONBLOCK else 0;
333 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
333334 errdefer if (self.stdin_behavior == StdIo.Pipe) {
334335 destroyPipe(stdin_pipe);
335336 };
336337
337 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe() else undefined;
338 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
338339 errdefer if (self.stdout_behavior == StdIo.Pipe) {
339340 destroyPipe(stdout_pipe);
340341 };
341342
342 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe() else undefined;
343 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
343344 errdefer if (self.stderr_behavior == StdIo.Pipe) {
344345 destroyPipe(stderr_pipe);
345346 };
......@@ -426,17 +427,26 @@ pub const ChildProcess = struct {
426427 // we are the parent
427428 const pid = @intCast(i32, pid_result);
428429 if (self.stdin_behavior == StdIo.Pipe) {
429 self.stdin = File.openHandle(stdin_pipe[1]);
430 self.stdin = File{
431 .handle = stdin_pipe[1],
432 .io_mode = std.io.mode,
433 };
430434 } else {
431435 self.stdin = null;
432436 }
433437 if (self.stdout_behavior == StdIo.Pipe) {
434 self.stdout = File.openHandle(stdout_pipe[0]);
438 self.stdout = File{
439 .handle = stdout_pipe[0],
440 .io_mode = std.io.mode,
441 };
435442 } else {
436443 self.stdout = null;
437444 }
438445 if (self.stderr_behavior == StdIo.Pipe) {
439 self.stderr = File.openHandle(stderr_pipe[0]);
446 self.stderr = File{
447 .handle = stderr_pipe[0],
448 .io_mode = std.io.mode,
449 };
440450 } else {
441451 self.stderr = null;
442452 }
......@@ -661,17 +671,26 @@ pub const ChildProcess = struct {
661671 };
662672
663673 if (g_hChildStd_IN_Wr) |h| {
664 self.stdin = File.openHandle(h);
674 self.stdin = File{
675 .handle = h,
676 .io_mode = io.mode,
677 };
665678 } else {
666679 self.stdin = null;
667680 }
668681 if (g_hChildStd_OUT_Rd) |h| {
669 self.stdout = File.openHandle(h);
682 self.stdout = File{
683 .handle = h,
684 .io_mode = io.mode,
685 };
670686 } else {
671687 self.stdout = null;
672688 }
673689 if (g_hChildStd_ERR_Rd) |h| {
674 self.stderr = File.openHandle(h);
690 self.stderr = File{
691 .handle = h,
692 .io_mode = io.mode,
693 };
675694 } else {
676695 self.stderr = null;
677696 }
......@@ -693,10 +712,10 @@ pub const ChildProcess = struct {
693712
694713 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
695714 switch (stdio) {
696 StdIo.Pipe => try os.dup2(pipe_fd, std_fileno),
697 StdIo.Close => os.close(std_fileno),
698 StdIo.Inherit => {},
699 StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno),
715 .Pipe => try os.dup2(pipe_fd, std_fileno),
716 .Close => os.close(std_fileno),
717 .Inherit => {},
718 .Ignore => try os.dup2(dev_null_fd, std_fileno),
700719 }
701720 }
702721};
......@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
811830const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);
812831
813832fn writeIntFd(fd: i32, value: ErrInt) !void {
814 const stream = &File.openHandle(fd).outStream().stream;
833 const file = File{
834 .handle = fd,
835 .io_mode = .blocking,
836 .async_block_allowed = File.async_block_allowed_yes,
837 };
838 const stream = &file.outStream().stream;
815839 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;
816840}
817841
818842fn readIntFd(fd: i32) !ErrInt {
819 const stream = &File.openHandle(fd).inStream().stream;
843 const file = File{
844 .handle = fd,
845 .io_mode = .blocking,
846 .async_block_allowed = File.async_block_allowed_yes,
847 };
848 const stream = &file.inStream().stream;
820849 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);
821850}
822851
lib/std/debug.zig+24-24
......@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {
5050 const held = stderr_mutex.acquire();
5151 defer held.release();
5252 const stderr = getStderrStream();
53 stderr.print(fmt, args) catch return;
53 noasync stderr.print(fmt, args) catch return;
5454}
5555
5656pub fn getStderrStream() *io.OutStream(File.WriteError) {
......@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {
102102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
103103 const stderr = getStderrStream();
104104 if (builtin.strip_debug_info) {
105 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
105 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
106106 return;
107107 }
108108 const debug_info = getSelfDebugInfo() catch |err| {
109 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
109 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
110110 return;
111111 };
112112 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| {
113 stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
113 noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
114114 return;
115115 };
116116}
......@@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
121121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
122122 const stderr = getStderrStream();
123123 if (builtin.strip_debug_info) {
124 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
124 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
125125 return;
126126 }
127127 const debug_info = getSelfDebugInfo() catch |err| {
128 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
128 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
129129 return;
130130 };
131131 const tty_config = detectTTYConfig();
......@@ -195,15 +195,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace
195195pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {
196196 const stderr = getStderrStream();
197197 if (builtin.strip_debug_info) {
198 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
198 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
199199 return;
200200 }
201201 const debug_info = getSelfDebugInfo() catch |err| {
202 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
202 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
203203 return;
204204 };
205205 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| {
206 stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
206 noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
207207 return;
208208 };
209209}
......@@ -244,7 +244,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
244244 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {
245245 0 => {
246246 const stderr = getStderrStream();
247 stderr.print(format ++ "\n", args) catch os.abort();
247 noasync stderr.print(format ++ "\n", args) catch os.abort();
248248 if (trace) |t| {
249249 dumpStackTrace(t.*);
250250 }
......@@ -556,12 +556,12 @@ pub const TTY = struct {
556556 switch (conf) {
557557 .no_color => return,
558558 .escape_codes => switch (color) {
559 .Red => out_stream.write(RED) catch return,
560 .Green => out_stream.write(GREEN) catch return,
561 .Cyan => out_stream.write(CYAN) catch return,
562 .White, .Bold => out_stream.write(WHITE) catch return,
563 .Dim => out_stream.write(DIM) catch return,
564 .Reset => out_stream.write(RESET) catch return,
559 .Red => noasync out_stream.write(RED) catch return,
560 .Green => noasync out_stream.write(GREEN) catch return,
561 .Cyan => noasync out_stream.write(CYAN) catch return,
562 .White, .Bold => noasync out_stream.write(WHITE) catch return,
563 .Dim => noasync out_stream.write(DIM) catch return,
564 .Reset => noasync out_stream.write(RESET) catch return,
565565 },
566566 .windows_api => if (builtin.os == .windows) {
567567 const S = struct {
......@@ -717,17 +717,17 @@ fn printLineInfo(
717717 tty_config.setColor(out_stream, .White);
718718
719719 if (line_info) |*li| {
720 try out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });
720 try noasync out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });
721721 } else {
722 try out_stream.print("???:?:?", .{});
722 try noasync out_stream.write("???:?:?");
723723 }
724724
725725 tty_config.setColor(out_stream, .Reset);
726 try out_stream.write(": ");
726 try noasync out_stream.write(": ");
727727 tty_config.setColor(out_stream, .Dim);
728 try out_stream.print("0x{x} in {} ({})", .{ address, symbol_name, compile_unit_name });
728 try noasync out_stream.print("0x{x} in {} ({})", .{ address, symbol_name, compile_unit_name });
729729 tty_config.setColor(out_stream, .Reset);
730 try out_stream.write("\n");
730 try noasync out_stream.write("\n");
731731
732732 // Show the matching source code line if possible
733733 if (line_info) |li| {
......@@ -736,12 +736,12 @@ fn printLineInfo(
736736 // The caret already takes one char
737737 const space_needed = @intCast(usize, li.column - 1);
738738
739 try out_stream.writeByteNTimes(' ', space_needed);
739 try noasync out_stream.writeByteNTimes(' ', space_needed);
740740 tty_config.setColor(out_stream, .Green);
741 try out_stream.write("^");
741 try noasync out_stream.write("^");
742742 tty_config.setColor(out_stream, .Reset);
743743 }
744 try out_stream.write("\n");
744 try noasync out_stream.write("\n");
745745 } else |err| switch (err) {
746746 error.EndOfFile, error.FileNotFound => {},
747747 error.BadPathName => {},
lib/std/event.zig-2
......@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;
66pub const RwLock = @import("event/rwlock.zig").RwLock;
77pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
88pub const Loop = @import("event/loop.zig").Loop;
9pub const fs = @import("event/fs.zig");
109
1110test "import event tests" {
1211 _ = @import("event/channel.zig");
13 _ = @import("event/fs.zig");
1412 _ = @import("event/future.zig");
1513 _ = @import("event/group.zig");
1614 _ = @import("event/lock.zig");
lib/std/event/fs.zig deleted-1418
......@@ -1,1418 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14//! TODO mege this with `std.fs`
15
16const global_event_loop = Loop.instance orelse
17 @compileError("std.event.fs currently only works with event-based I/O");
18
19pub const RequestNode = std.atomic.Queue(Request).Node;
20
21pub const Request = struct {
22 msg: Msg,
23 finish: Finish,
24
25 pub const Finish = union(enum) {
26 TickNode: Loop.NextTickNode,
27 DeallocCloseOperation: *CloseOperation,
28 NoAction,
29 };
30
31 pub const Msg = union(enum) {
32 WriteV: WriteV,
33 PWriteV: PWriteV,
34 PReadV: PReadV,
35 Open: Open,
36 Close: Close,
37 WriteFile: WriteFile,
38 End, // special - means the fs thread should exit
39
40 pub const WriteV = struct {
41 fd: fd_t,
42 iov: []const os.iovec_const,
43 result: Error!void,
44
45 pub const Error = os.WriteError;
46 };
47
48 pub const PWriteV = struct {
49 fd: fd_t,
50 iov: []const os.iovec_const,
51 offset: usize,
52 result: Error!void,
53
54 pub const Error = os.WriteError;
55 };
56
57 pub const PReadV = struct {
58 fd: fd_t,
59 iov: []const os.iovec,
60 offset: usize,
61 result: Error!usize,
62
63 pub const Error = os.ReadError;
64 };
65
66 pub const Open = struct {
67 path: [:0]const u8,
68 flags: u32,
69 mode: File.Mode,
70 result: Error!fd_t,
71
72 pub const Error = File.OpenError;
73 };
74
75 pub const WriteFile = struct {
76 path: [:0]const u8,
77 contents: []const u8,
78 mode: File.Mode,
79 result: Error!void,
80
81 pub const Error = File.OpenError || File.WriteError;
82 };
83
84 pub const Close = struct {
85 fd: fd_t,
86 };
87 };
88};
89
90pub const PWriteVError = error{OutOfMemory} || File.WriteError;
91
92/// data - just the inner references - must live until pwritev frame completes.
93pub fn pwritev(allocator: *Allocator, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void {
94 switch (builtin.os) {
95 .macosx,
96 .linux,
97 .freebsd,
98 .netbsd,
99 .dragonfly,
100 => {
101 const iovecs = try allocator.alloc(os.iovec_const, data.len);
102 defer allocator.free(iovecs);
103
104 for (data) |buf, i| {
105 iovecs[i] = os.iovec_const{
106 .iov_base = buf.ptr,
107 .iov_len = buf.len,
108 };
109 }
110
111 return pwritevPosix(fd, iovecs, offset);
112 },
113 .windows => {
114 const data_copy = try std.mem.dupe(allocator, []const u8, data);
115 defer allocator.free(data_copy);
116 return pwritevWindows(fd, data, offset);
117 },
118 else => @compileError("Unsupported OS"),
119 }
120}
121
122/// data must outlive the returned frame
123pub fn pwritevWindows(fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void {
124 if (data.len == 0) return;
125 if (data.len == 1) return pwriteWindows(fd, data[0], offset);
126
127 // TODO do these in parallel
128 var off = offset;
129 for (data) |buf| {
130 try pwriteWindows(fd, buf, off);
131 off += buf.len;
132 }
133}
134
135pub fn pwriteWindows(fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void {
136 var resume_node = Loop.ResumeNode.Basic{
137 .base = Loop.ResumeNode{
138 .id = Loop.ResumeNode.Id.Basic,
139 .handle = @frame(),
140 .overlapped = windows.OVERLAPPED{
141 .Internal = 0,
142 .InternalHigh = 0,
143 .Offset = @truncate(u32, offset),
144 .OffsetHigh = @truncate(u32, offset >> 32),
145 .hEvent = null,
146 },
147 },
148 };
149 // TODO only call create io completion port once per fd
150 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined);
151 global_event_loop.beginOneEvent();
152 errdefer global_event_loop.finishOneEvent();
153
154 errdefer {
155 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
156 }
157 suspend {
158 _ = windows.kernel32.WriteFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
159 }
160 var bytes_transferred: windows.DWORD = undefined;
161 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
162 switch (windows.kernel32.GetLastError()) {
163 .IO_PENDING => unreachable,
164 .INVALID_USER_BUFFER => return error.SystemResources,
165 .NOT_ENOUGH_MEMORY => return error.SystemResources,
166 .OPERATION_ABORTED => return error.OperationAborted,
167 .NOT_ENOUGH_QUOTA => return error.SystemResources,
168 .BROKEN_PIPE => return error.BrokenPipe,
169 else => |err| return windows.unexpectedError(err),
170 }
171 }
172}
173
174/// iovecs must live until pwritev frame completes.
175pub fn pwritevPosix(fd: fd_t, iovecs: []const os.iovec_const, offset: usize) os.WriteError!void {
176 var req_node = RequestNode{
177 .prev = null,
178 .next = null,
179 .data = Request{
180 .msg = Request.Msg{
181 .PWriteV = Request.Msg.PWriteV{
182 .fd = fd,
183 .iov = iovecs,
184 .offset = offset,
185 .result = undefined,
186 },
187 },
188 .finish = Request.Finish{
189 .TickNode = Loop.NextTickNode{
190 .prev = null,
191 .next = null,
192 .data = @frame(),
193 },
194 },
195 },
196 };
197
198 errdefer global_event_loop.posixFsCancel(&req_node);
199
200 suspend {
201 global_event_loop.posixFsRequest(&req_node);
202 }
203
204 return req_node.data.msg.PWriteV.result;
205}
206
207/// iovecs must live until pwritev frame completes.
208pub fn writevPosix(fd: fd_t, iovecs: []const os.iovec_const) os.WriteError!void {
209 var req_node = RequestNode{
210 .prev = null,
211 .next = null,
212 .data = Request{
213 .msg = Request.Msg{
214 .WriteV = Request.Msg.WriteV{
215 .fd = fd,
216 .iov = iovecs,
217 .result = undefined,
218 },
219 },
220 .finish = Request.Finish{
221 .TickNode = Loop.NextTickNode{
222 .prev = null,
223 .next = null,
224 .data = @frame(),
225 },
226 },
227 },
228 };
229
230 suspend {
231 global_event_loop.posixFsRequest(&req_node);
232 }
233
234 return req_node.data.msg.WriteV.result;
235}
236
237pub const PReadVError = error{OutOfMemory} || File.ReadError;
238
239/// data - just the inner references - must live until preadv frame completes.
240pub fn preadv(allocator: *Allocator, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize {
241 assert(data.len != 0);
242 switch (builtin.os) {
243 .macosx,
244 .linux,
245 .freebsd,
246 .netbsd,
247 .dragonfly,
248 => {
249 const iovecs = try allocator.alloc(os.iovec, data.len);
250 defer allocator.free(iovecs);
251
252 for (data) |buf, i| {
253 iovecs[i] = os.iovec{
254 .iov_base = buf.ptr,
255 .iov_len = buf.len,
256 };
257 }
258
259 return preadvPosix(fd, iovecs, offset);
260 },
261 .windows => {
262 const data_copy = try std.mem.dupe(allocator, []u8, data);
263 defer allocator.free(data_copy);
264 return preadvWindows(fd, data_copy, offset);
265 },
266 else => @compileError("Unsupported OS"),
267 }
268}
269
270/// data must outlive the returned frame
271pub fn preadvWindows(fd: fd_t, data: []const []u8, offset: u64) !usize {
272 assert(data.len != 0);
273 if (data.len == 1) return preadWindows(fd, data[0], offset);
274
275 // TODO do these in parallel?
276 var off: usize = 0;
277 var iov_i: usize = 0;
278 var inner_off: usize = 0;
279 while (true) {
280 const v = data[iov_i];
281 const amt_read = try preadWindows(fd, v[inner_off .. v.len - inner_off], offset + off);
282 off += amt_read;
283 inner_off += amt_read;
284 if (inner_off == v.len) {
285 iov_i += 1;
286 inner_off = 0;
287 if (iov_i == data.len) {
288 return off;
289 }
290 }
291 if (amt_read == 0) return off; // EOF
292 }
293}
294
295pub fn preadWindows(fd: fd_t, data: []u8, offset: u64) !usize {
296 var resume_node = Loop.ResumeNode.Basic{
297 .base = Loop.ResumeNode{
298 .id = Loop.ResumeNode.Id.Basic,
299 .handle = @frame(),
300 .overlapped = windows.OVERLAPPED{
301 .Internal = 0,
302 .InternalHigh = 0,
303 .Offset = @truncate(u32, offset),
304 .OffsetHigh = @truncate(u32, offset >> 32),
305 .hEvent = null,
306 },
307 },
308 };
309 // TODO only call create io completion port once per fd
310 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined) catch undefined;
311 global_event_loop.beginOneEvent();
312 errdefer global_event_loop.finishOneEvent();
313
314 errdefer {
315 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
316 }
317 suspend {
318 _ = windows.kernel32.ReadFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
319 }
320 var bytes_transferred: windows.DWORD = undefined;
321 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
322 switch (windows.kernel32.GetLastError()) {
323 .IO_PENDING => unreachable,
324 .OPERATION_ABORTED => return error.OperationAborted,
325 .BROKEN_PIPE => return error.BrokenPipe,
326 .HANDLE_EOF => return @as(usize, bytes_transferred),
327 else => |err| return windows.unexpectedError(err),
328 }
329 }
330 return @as(usize, bytes_transferred);
331}
332
333/// iovecs must live until preadv frame completes
334pub fn preadvPosix(fd: fd_t, iovecs: []const os.iovec, offset: usize) os.ReadError!usize {
335 var req_node = RequestNode{
336 .prev = null,
337 .next = null,
338 .data = Request{
339 .msg = Request.Msg{
340 .PReadV = Request.Msg.PReadV{
341 .fd = fd,
342 .iov = iovecs,
343 .offset = offset,
344 .result = undefined,
345 },
346 },
347 .finish = Request.Finish{
348 .TickNode = Loop.NextTickNode{
349 .prev = null,
350 .next = null,
351 .data = @frame(),
352 },
353 },
354 },
355 };
356
357 errdefer global_event_loop.posixFsCancel(&req_node);
358
359 suspend {
360 global_event_loop.posixFsRequest(&req_node);
361 }
362
363 return req_node.data.msg.PReadV.result;
364}
365
366pub fn openPosix(path: []const u8, flags: u32, mode: File.Mode) File.OpenError!fd_t {
367 const path_c = try std.os.toPosixPath(path);
368
369 var req_node = RequestNode{
370 .prev = null,
371 .next = null,
372 .data = Request{
373 .msg = Request.Msg{
374 .Open = Request.Msg.Open{
375 .path = path_c[0..path.len],
376 .flags = flags,
377 .mode = mode,
378 .result = undefined,
379 },
380 },
381 .finish = Request.Finish{
382 .TickNode = Loop.NextTickNode{
383 .prev = null,
384 .next = null,
385 .data = @frame(),
386 },
387 },
388 },
389 };
390
391 errdefer global_event_loop.posixFsCancel(&req_node);
392
393 suspend {
394 global_event_loop.posixFsRequest(&req_node);
395 }
396
397 return req_node.data.msg.Open.result;
398}
399
400pub fn openRead(path: []const u8) File.OpenError!fd_t {
401 switch (builtin.os) {
402 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
403 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
404 const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
405 return openPosix(path, flags, File.default_mode);
406 },
407
408 .windows => return windows.CreateFile(
409 path,
410 windows.GENERIC_READ,
411 windows.FILE_SHARE_READ,
412 null,
413 windows.OPEN_EXISTING,
414 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
415 null,
416 ),
417
418 else => @compileError("Unsupported OS"),
419 }
420}
421
422/// Creates if does not exist. Truncates the file if it exists.
423/// Uses the default mode.
424pub fn openWrite(path: []const u8) File.OpenError!fd_t {
425 return openWriteMode(path, File.default_mode);
426}
427
428/// Creates if does not exist. Truncates the file if it exists.
429pub fn openWriteMode(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
430 switch (builtin.os) {
431 .macosx,
432 .linux,
433 .freebsd,
434 .netbsd,
435 .dragonfly,
436 => {
437 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
438 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC;
439 return openPosix(path, flags, File.default_mode);
440 },
441 .windows => return windows.CreateFile(
442 path,
443 windows.GENERIC_WRITE,
444 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
445 null,
446 windows.CREATE_ALWAYS,
447 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
448 null,
449 ),
450 else => @compileError("Unsupported OS"),
451 }
452}
453
454/// Creates if does not exist. Does not truncate.
455pub fn openReadWrite(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
456 switch (builtin.os) {
457 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
458 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
459 const flags = O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC;
460 return openPosix(path, flags, mode);
461 },
462
463 .windows => return windows.CreateFile(
464 path,
465 windows.GENERIC_WRITE | windows.GENERIC_READ,
466 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
467 null,
468 windows.OPEN_ALWAYS,
469 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
470 null,
471 ),
472
473 else => @compileError("Unsupported OS"),
474 }
475}
476
477/// This abstraction helps to close file handles in defer expressions
478/// without the possibility of failure and without the use of suspend points.
479/// Start a `CloseOperation` before opening a file, so that you can defer
480/// `CloseOperation.finish`.
481/// If you call `setHandle` then finishing will close the fd; otherwise finishing
482/// will deallocate the `CloseOperation`.
483pub const CloseOperation = struct {
484 allocator: *Allocator,
485 os_data: OsData,
486
487 const OsData = switch (builtin.os) {
488 .linux, .macosx, .freebsd, .netbsd, .dragonfly => OsDataPosix,
489
490 .windows => struct {
491 handle: ?fd_t,
492 },
493
494 else => @compileError("Unsupported OS"),
495 };
496
497 const OsDataPosix = struct {
498 have_fd: bool,
499 close_req_node: RequestNode,
500 };
501
502 pub fn start(allocator: *Allocator) (error{OutOfMemory}!*CloseOperation) {
503 const self = try allocator.create(CloseOperation);
504 self.* = CloseOperation{
505 .allocator = allocator,
506 .os_data = switch (builtin.os) {
507 .linux, .macosx, .freebsd, .netbsd, .dragonfly => initOsDataPosix(self),
508 .windows => OsData{ .handle = null },
509 else => @compileError("Unsupported OS"),
510 },
511 };
512 return self;
513 }
514
515 fn initOsDataPosix(self: *CloseOperation) OsData {
516 return OsData{
517 .have_fd = false,
518 .close_req_node = RequestNode{
519 .prev = null,
520 .next = null,
521 .data = Request{
522 .msg = Request.Msg{
523 .Close = Request.Msg.Close{ .fd = undefined },
524 },
525 .finish = Request.Finish{ .DeallocCloseOperation = self },
526 },
527 },
528 };
529 }
530
531 /// Defer this after creating.
532 pub fn finish(self: *CloseOperation) void {
533 switch (builtin.os) {
534 .linux,
535 .macosx,
536 .freebsd,
537 .netbsd,
538 .dragonfly,
539 => {
540 if (self.os_data.have_fd) {
541 global_event_loop.posixFsRequest(&self.os_data.close_req_node);
542 } else {
543 self.allocator.destroy(self);
544 }
545 },
546 .windows => {
547 if (self.os_data.handle) |handle| {
548 os.close(handle);
549 }
550 self.allocator.destroy(self);
551 },
552 else => @compileError("Unsupported OS"),
553 }
554 }
555
556 pub fn setHandle(self: *CloseOperation, handle: fd_t) void {
557 switch (builtin.os) {
558 .linux,
559 .macosx,
560 .freebsd,
561 .netbsd,
562 .dragonfly,
563 => {
564 self.os_data.close_req_node.data.msg.Close.fd = handle;
565 self.os_data.have_fd = true;
566 },
567 .windows => {
568 self.os_data.handle = handle;
569 },
570 else => @compileError("Unsupported OS"),
571 }
572 }
573
574 /// Undo a `setHandle`.
575 pub fn clearHandle(self: *CloseOperation) void {
576 switch (builtin.os) {
577 .linux,
578 .macosx,
579 .freebsd,
580 .netbsd,
581 .dragonfly,
582 => {
583 self.os_data.have_fd = false;
584 },
585 .windows => {
586 self.os_data.handle = null;
587 },
588 else => @compileError("Unsupported OS"),
589 }
590 }
591
592 pub fn getHandle(self: *CloseOperation) fd_t {
593 switch (builtin.os) {
594 .linux,
595 .macosx,
596 .freebsd,
597 .netbsd,
598 .dragonfly,
599 => {
600 assert(self.os_data.have_fd);
601 return self.os_data.close_req_node.data.msg.Close.fd;
602 },
603 .windows => {
604 return self.os_data.handle.?;
605 },
606 else => @compileError("Unsupported OS"),
607 }
608 }
609};
610
611/// contents must remain alive until writeFile completes.
612/// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate
613pub fn writeFile(allocator: *Allocator, path: []const u8, contents: []const u8) !void {
614 return writeFileMode(allocator, path, contents, File.default_mode);
615}
616
617/// contents must remain alive until writeFile completes.
618pub fn writeFileMode(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
619 switch (builtin.os) {
620 .linux,
621 .macosx,
622 .freebsd,
623 .netbsd,
624 .dragonfly,
625 => return writeFileModeThread(allocator, path, contents, mode),
626 .windows => return writeFileWindows(path, contents),
627 else => @compileError("Unsupported OS"),
628 }
629}
630
631fn writeFileWindows(path: []const u8, contents: []const u8) !void {
632 const handle = try windows.CreateFile(
633 path,
634 windows.GENERIC_WRITE,
635 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
636 null,
637 windows.CREATE_ALWAYS,
638 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
639 null,
640 );
641 defer os.close(handle);
642
643 try pwriteWindows(handle, contents, 0);
644}
645
646fn writeFileModeThread(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
647 const path_with_null = try std.cstr.addNullByte(allocator, path);
648 defer allocator.free(path_with_null);
649
650 var req_node = RequestNode{
651 .prev = null,
652 .next = null,
653 .data = Request{
654 .msg = Request.Msg{
655 .WriteFile = Request.Msg.WriteFile{
656 .path = path_with_null[0..path.len],
657 .contents = contents,
658 .mode = mode,
659 .result = undefined,
660 },
661 },
662 .finish = Request.Finish{
663 .TickNode = Loop.NextTickNode{
664 .prev = null,
665 .next = null,
666 .data = @frame(),
667 },
668 },
669 },
670 };
671
672 errdefer global_event_loop.posixFsCancel(&req_node);
673
674 suspend {
675 global_event_loop.posixFsRequest(&req_node);
676 }
677
678 return req_node.data.msg.WriteFile.result;
679}
680
681/// The frame resumes when the last data has been confirmed written, but before the file handle
682/// is closed.
683/// Caller owns returned memory.
684pub fn readFile(allocator: *Allocator, file_path: []const u8, max_size: usize) ![]u8 {
685 var close_op = try CloseOperation.start(allocator);
686 defer close_op.finish();
687
688 const fd = try openRead(file_path);
689 close_op.setHandle(fd);
690
691 var list = std.ArrayList(u8).init(allocator);
692 defer list.deinit();
693
694 while (true) {
695 try list.ensureCapacity(list.len + mem.page_size);
696 const buf = list.items[list.len..];
697 const buf_array = [_][]u8{buf};
698 const amt = try preadv(allocator, fd, &buf_array, list.len);
699 list.len += amt;
700 if (list.len > max_size) {
701 return error.FileTooBig;
702 }
703 if (amt < buf.len) {
704 return list.toOwnedSlice();
705 }
706 }
707}
708
709pub const WatchEventId = enum {
710 CloseWrite,
711 Delete,
712};
713
714fn eqlString(a: []const u16, b: []const u16) bool {
715 if (a.len != b.len) return false;
716 if (a.ptr == b.ptr) return true;
717 return mem.compare(u16, a, b) == .Equal;
718}
719
720fn hashString(s: []const u16) u32 {
721 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
722}
723
724pub const WatchEventError = error{
725 UserResourceLimitReached,
726 SystemResources,
727 AccessDenied,
728 Unexpected, // TODO remove this possibility
729};
730
731pub fn Watch(comptime V: type) type {
732 return struct {
733 channel: *event.Channel(Event.Error!Event),
734 os_data: OsData,
735 allocator: *Allocator,
736
737 const OsData = switch (builtin.os) {
738 // TODO https://github.com/ziglang/zig/issues/3778
739 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
740 .linux => LinuxOsData,
741 .windows => WindowsOsData,
742
743 else => @compileError("Unsupported OS"),
744 };
745
746 const KqOsData = struct {
747 file_table: FileTable,
748 table_lock: event.Lock,
749
750 const FileTable = std.StringHashMap(*Put);
751 const Put = struct {
752 putter_frame: @Frame(kqPutEvents),
753 cancelled: bool = false,
754 value: V,
755 };
756 };
757
758 const WindowsOsData = struct {
759 table_lock: event.Lock,
760 dir_table: DirTable,
761 all_putters: std.atomic.Queue(Put),
762 ref_count: std.atomic.Int(usize),
763
764 const Put = struct {
765 putter: anyframe,
766 cancelled: bool = false,
767 };
768
769 const DirTable = std.StringHashMap(*Dir);
770 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
771
772 const Dir = struct {
773 putter_frame: @Frame(windowsDirReader),
774 file_table: FileTable,
775 table_lock: event.Lock,
776 };
777 };
778
779 const LinuxOsData = struct {
780 putter_frame: @Frame(linuxEventPutter),
781 inotify_fd: i32,
782 wd_table: WdTable,
783 table_lock: event.Lock,
784 cancelled: bool = false,
785
786 const WdTable = std.AutoHashMap(i32, Dir);
787 const FileTable = std.StringHashMap(V);
788
789 const Dir = struct {
790 dirname: []const u8,
791 file_table: FileTable,
792 };
793 };
794
795 const Self = @This();
796
797 pub const Event = struct {
798 id: Id,
799 data: V,
800
801 pub const Id = WatchEventId;
802 pub const Error = WatchEventError;
803 };
804
805 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
806 const channel = try allocator.create(event.Channel(Event.Error!Event));
807 errdefer allocator.destroy(channel);
808 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
809 errdefer allocator.free(buf);
810 channel.init(buf);
811 errdefer channel.deinit();
812
813 const self = try allocator.create(Self);
814 errdefer allocator.destroy(self);
815
816 switch (builtin.os) {
817 .linux => {
818 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
819 errdefer os.close(inotify_fd);
820
821 self.* = Self{
822 .allocator = allocator,
823 .channel = channel,
824 .os_data = OsData{
825 .putter_frame = undefined,
826 .inotify_fd = inotify_fd,
827 .wd_table = OsData.WdTable.init(allocator),
828 .table_lock = event.Lock.init(),
829 },
830 };
831
832 self.os_data.putter_frame = async self.linuxEventPutter();
833 return self;
834 },
835
836 .windows => {
837 self.* = Self{
838 .allocator = allocator,
839 .channel = channel,
840 .os_data = OsData{
841 .table_lock = event.Lock.init(),
842 .dir_table = OsData.DirTable.init(allocator),
843 .ref_count = std.atomic.Int(usize).init(1),
844 .all_putters = std.atomic.Queue(anyframe).init(),
845 },
846 };
847 return self;
848 },
849
850 .macosx, .freebsd, .netbsd, .dragonfly => {
851 self.* = Self{
852 .allocator = allocator,
853 .channel = channel,
854 .os_data = OsData{
855 .table_lock = event.Lock.init(),
856 .file_table = OsData.FileTable.init(allocator),
857 },
858 };
859 return self;
860 },
861 else => @compileError("Unsupported OS"),
862 }
863 }
864
865 /// All addFile calls and removeFile calls must have completed.
866 pub fn deinit(self: *Self) void {
867 switch (builtin.os) {
868 .macosx, .freebsd, .netbsd, .dragonfly => {
869 // TODO we need to cancel the frames before destroying the lock
870 self.os_data.table_lock.deinit();
871 var it = self.os_data.file_table.iterator();
872 while (it.next()) |entry| {
873 entry.cancelled = true;
874 await entry.value.putter;
875 self.allocator.free(entry.key);
876 self.allocator.free(entry.value);
877 }
878 self.channel.deinit();
879 self.allocator.destroy(self.channel.buffer_nodes);
880 self.allocator.destroy(self);
881 },
882 .linux => {
883 self.os_data.cancelled = true;
884 await self.os_data.putter_frame;
885 self.allocator.destroy(self);
886 },
887 .windows => {
888 while (self.os_data.all_putters.get()) |putter_node| {
889 putter_node.cancelled = true;
890 await putter_node.frame;
891 }
892 self.deref();
893 },
894 else => @compileError("Unsupported OS"),
895 }
896 }
897
898 fn ref(self: *Self) void {
899 _ = self.os_data.ref_count.incr();
900 }
901
902 fn deref(self: *Self) void {
903 if (self.os_data.ref_count.decr() == 1) {
904 self.os_data.table_lock.deinit();
905 var it = self.os_data.dir_table.iterator();
906 while (it.next()) |entry| {
907 self.allocator.free(entry.key);
908 self.allocator.destroy(entry.value);
909 }
910 self.os_data.dir_table.deinit();
911 self.channel.deinit();
912 self.allocator.destroy(self.channel.buffer_nodes);
913 self.allocator.destroy(self);
914 }
915 }
916
917 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
918 switch (builtin.os) {
919 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
920 .linux => return addFileLinux(self, file_path, value),
921 .windows => return addFileWindows(self, file_path, value),
922 else => @compileError("Unsupported OS"),
923 }
924 }
925
926 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
927 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
928 var resolved_path_consumed = false;
929 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
930
931 var close_op = try CloseOperation.start(self.allocator);
932 var close_op_consumed = false;
933 defer if (!close_op_consumed) close_op.finish();
934
935 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
936 const mode = 0;
937 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
938 close_op.setHandle(fd);
939
940 var put = try self.allocator.create(OsData.Put);
941 errdefer self.allocator.destroy(put);
942 put.* = OsData.Put{
943 .value = value,
944 .putter_frame = undefined,
945 };
946 put.putter_frame = async self.kqPutEvents(close_op, put);
947 close_op_consumed = true;
948 errdefer {
949 put.cancelled = true;
950 await put.putter_frame;
951 }
952
953 const result = blk: {
954 const held = self.os_data.table_lock.acquire();
955 defer held.release();
956
957 const gop = try self.os_data.file_table.getOrPut(resolved_path);
958 if (gop.found_existing) {
959 const prev_value = gop.kv.value.value;
960 await gop.kv.value.putter_frame;
961 gop.kv.value = put;
962 break :blk prev_value;
963 } else {
964 resolved_path_consumed = true;
965 gop.kv.value = put;
966 break :blk null;
967 }
968 };
969
970 return result;
971 }
972
973 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
974 global_event_loop.beginOneEvent();
975
976 defer {
977 close_op.finish();
978 global_event_loop.finishOneEvent();
979 }
980
981 while (!put.cancelled) {
982 if (global_event_loop.bsdWaitKev(
983 @intCast(usize, close_op.getHandle()),
984 os.EVFILT_VNODE,
985 os.NOTE_WRITE | os.NOTE_DELETE,
986 )) |kev| {
987 // TODO handle EV_ERROR
988 if (kev.fflags & os.NOTE_DELETE != 0) {
989 self.channel.put(Self.Event{
990 .id = Event.Id.Delete,
991 .data = put.value,
992 });
993 } else if (kev.fflags & os.NOTE_WRITE != 0) {
994 self.channel.put(Self.Event{
995 .id = Event.Id.CloseWrite,
996 .data = put.value,
997 });
998 }
999 } else |err| switch (err) {
1000 error.EventNotFound => unreachable,
1001 error.ProcessNotFound => unreachable,
1002 error.Overflow => unreachable,
1003 error.AccessDenied, error.SystemResources => |casted_err| {
1004 self.channel.put(casted_err);
1005 },
1006 }
1007 }
1008 }
1009
1010 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
1011 const dirname = std.fs.path.dirname(file_path) orelse ".";
1012 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
1013 var dirname_with_null_consumed = false;
1014 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
1015
1016 const basename = std.fs.path.basename(file_path);
1017 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
1018 var basename_with_null_consumed = false;
1019 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
1020
1021 const wd = try os.inotify_add_watchC(
1022 self.os_data.inotify_fd,
1023 dirname_with_null.ptr,
1024 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
1025 );
1026 // wd is either a newly created watch or an existing one.
1027
1028 const held = self.os_data.table_lock.acquire();
1029 defer held.release();
1030
1031 const gop = try self.os_data.wd_table.getOrPut(wd);
1032 if (!gop.found_existing) {
1033 gop.kv.value = OsData.Dir{
1034 .dirname = dirname_with_null,
1035 .file_table = OsData.FileTable.init(self.allocator),
1036 };
1037 dirname_with_null_consumed = true;
1038 }
1039 const dir = &gop.kv.value;
1040
1041 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1042 if (file_table_gop.found_existing) {
1043 const prev_value = file_table_gop.kv.value;
1044 file_table_gop.kv.value = value;
1045 return prev_value;
1046 } else {
1047 file_table_gop.kv.value = value;
1048 basename_with_null_consumed = true;
1049 return null;
1050 }
1051 }
1052
1053 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1054 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1055 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1056 var dirname_consumed = false;
1057 defer if (!dirname_consumed) self.allocator.free(dirname);
1058
1059 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
1060 defer self.allocator.free(dirname_utf16le);
1061
1062 // TODO https://github.com/ziglang/zig/issues/265
1063 const basename = std.fs.path.basename(file_path);
1064 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
1065 var basename_utf16le_null_consumed = false;
1066 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
1067 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1068
1069 const dir_handle = try windows.CreateFileW(
1070 dirname_utf16le.ptr,
1071 windows.FILE_LIST_DIRECTORY,
1072 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1073 null,
1074 windows.OPEN_EXISTING,
1075 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1076 null,
1077 );
1078 var dir_handle_consumed = false;
1079 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1080
1081 const held = self.os_data.table_lock.acquire();
1082 defer held.release();
1083
1084 const gop = try self.os_data.dir_table.getOrPut(dirname);
1085 if (gop.found_existing) {
1086 const dir = gop.kv.value;
1087 const held_dir_lock = dir.table_lock.acquire();
1088 defer held_dir_lock.release();
1089
1090 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1091 if (file_gop.found_existing) {
1092 const prev_value = file_gop.kv.value;
1093 file_gop.kv.value = value;
1094 return prev_value;
1095 } else {
1096 file_gop.kv.value = value;
1097 basename_utf16le_null_consumed = true;
1098 return null;
1099 }
1100 } else {
1101 errdefer _ = self.os_data.dir_table.remove(dirname);
1102 const dir = try self.allocator.create(OsData.Dir);
1103 errdefer self.allocator.destroy(dir);
1104
1105 dir.* = OsData.Dir{
1106 .file_table = OsData.FileTable.init(self.allocator),
1107 .table_lock = event.Lock.init(),
1108 .putter_frame = undefined,
1109 };
1110 gop.kv.value = dir;
1111 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
1112 basename_utf16le_null_consumed = true;
1113
1114 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
1115 dir_handle_consumed = true;
1116
1117 dirname_consumed = true;
1118
1119 return null;
1120 }
1121 }
1122
1123 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1124 self.ref();
1125 defer self.deref();
1126
1127 defer os.close(dir_handle);
1128
1129 var putter_node = std.atomic.Queue(anyframe).Node{
1130 .data = .{ .putter = @frame() },
1131 .prev = null,
1132 .next = null,
1133 };
1134 self.os_data.all_putters.put(&putter_node);
1135 defer _ = self.os_data.all_putters.remove(&putter_node);
1136
1137 var resume_node = Loop.ResumeNode.Basic{
1138 .base = Loop.ResumeNode{
1139 .id = Loop.ResumeNode.Id.Basic,
1140 .handle = @frame(),
1141 .overlapped = windows.OVERLAPPED{
1142 .Internal = 0,
1143 .InternalHigh = 0,
1144 .Offset = 0,
1145 .OffsetHigh = 0,
1146 .hEvent = null,
1147 },
1148 },
1149 };
1150 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1151
1152 // TODO handle this error not in the channel but in the setup
1153 _ = windows.CreateIoCompletionPort(
1154 dir_handle,
1155 global_event_loop.os_data.io_port,
1156 undefined,
1157 undefined,
1158 ) catch |err| {
1159 self.channel.put(err);
1160 return;
1161 };
1162
1163 while (!putter_node.data.cancelled) {
1164 {
1165 // TODO only 1 beginOneEvent for the whole function
1166 global_event_loop.beginOneEvent();
1167 errdefer global_event_loop.finishOneEvent();
1168 errdefer {
1169 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1170 }
1171 suspend {
1172 _ = windows.kernel32.ReadDirectoryChangesW(
1173 dir_handle,
1174 &event_buf,
1175 @intCast(windows.DWORD, event_buf.len),
1176 windows.FALSE, // watch subtree
1177 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1178 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1179 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1180 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1181 null, // number of bytes transferred (unused for async)
1182 &resume_node.base.overlapped,
1183 null, // completion routine - unused because we use IOCP
1184 );
1185 }
1186 }
1187 var bytes_transferred: windows.DWORD = undefined;
1188 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1189 const err = switch (windows.kernel32.GetLastError()) {
1190 else => |err| windows.unexpectedError(err),
1191 };
1192 self.channel.put(err);
1193 } else {
1194 // can't use @bytesToSlice because of the special variable length name field
1195 var ptr = event_buf[0..].ptr;
1196 const end_ptr = ptr + bytes_transferred;
1197 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1198 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1199 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1200 const emit = switch (ev.Action) {
1201 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1202 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1203 else => null,
1204 };
1205 if (emit) |id| {
1206 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1207 const user_value = blk: {
1208 const held = dir.table_lock.acquire();
1209 defer held.release();
1210
1211 if (dir.file_table.get(basename_utf16le)) |entry| {
1212 break :blk entry.value;
1213 } else {
1214 break :blk null;
1215 }
1216 };
1217 if (user_value) |v| {
1218 self.channel.put(Event{
1219 .id = id,
1220 .data = v,
1221 });
1222 }
1223 }
1224 if (ev.NextEntryOffset == 0) break;
1225 }
1226 }
1227 }
1228 }
1229
1230 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
1231 @panic("TODO");
1232 }
1233
1234 fn linuxEventPutter(self: *Self) void {
1235 global_event_loop.beginOneEvent();
1236
1237 defer {
1238 self.os_data.table_lock.deinit();
1239 var wd_it = self.os_data.wd_table.iterator();
1240 while (wd_it.next()) |wd_entry| {
1241 var file_it = wd_entry.value.file_table.iterator();
1242 while (file_it.next()) |file_entry| {
1243 self.allocator.free(file_entry.key);
1244 }
1245 self.allocator.free(wd_entry.value.dirname);
1246 wd_entry.value.file_table.deinit();
1247 }
1248 self.os_data.wd_table.deinit();
1249 global_event_loop.finishOneEvent();
1250 os.close(self.os_data.inotify_fd);
1251 self.channel.deinit();
1252 self.allocator.free(self.channel.buffer_nodes);
1253 }
1254
1255 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1256
1257 while (!self.os_data.cancelled) {
1258 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
1259 const errno = os.linux.getErrno(rc);
1260 switch (errno) {
1261 0 => {
1262 // can't use @bytesToSlice because of the special variable length name field
1263 var ptr = event_buf[0..].ptr;
1264 const end_ptr = ptr + event_buf.len;
1265 var ev: *os.linux.inotify_event = undefined;
1266 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
1267 ev = @ptrCast(*os.linux.inotify_event, ptr);
1268 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1269 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1270 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
1271 const basename_with_null = basename_ptr[0..ev.len];
1272 const user_value = blk: {
1273 const held = self.os_data.table_lock.acquire();
1274 defer held.release();
1275
1276 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
1277 if (dir.file_table.get(basename_with_null)) |entry| {
1278 break :blk entry.value;
1279 } else {
1280 break :blk null;
1281 }
1282 };
1283 if (user_value) |v| {
1284 self.channel.put(Event{
1285 .id = WatchEventId.CloseWrite,
1286 .data = v,
1287 });
1288 }
1289 }
1290
1291 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
1292 }
1293 },
1294 os.linux.EINTR => continue,
1295 os.linux.EINVAL => unreachable,
1296 os.linux.EFAULT => unreachable,
1297 os.linux.EAGAIN => {
1298 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
1299 },
1300 else => unreachable,
1301 }
1302 }
1303 }
1304 };
1305}
1306
1307const test_tmp_dir = "std_event_fs_test";
1308
1309test "write a file, watch it, write it again" {
1310 // TODO provide a way to run tests in evented I/O mode
1311 if (!std.io.is_async) return error.SkipZigTest;
1312
1313 const allocator = std.heap.page_allocator;
1314
1315 // TODO move this into event loop too
1316 try os.makePath(allocator, test_tmp_dir);
1317 defer os.deleteTree(test_tmp_dir) catch {};
1318
1319 return testFsWatch(&allocator);
1320}
1321
1322fn testFsWatch(allocator: *Allocator) !void {
1323 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
1324 defer allocator.free(file_path);
1325
1326 const contents =
1327 \\line 1
1328 \\line 2
1329 ;
1330 const line2_offset = 7;
1331
1332 // first just write then read the file
1333 try writeFile(allocator, file_path, contents);
1334
1335 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
1336 testing.expectEqualSlices(u8, contents, read_contents);
1337
1338 // now watch the file
1339 var watch = try Watch(void).init(allocator, 0);
1340 defer watch.deinit();
1341
1342 testing.expect((try watch.addFile(file_path, {})) == null);
1343
1344 const ev = watch.channel.get();
1345 var ev_consumed = false;
1346 defer if (!ev_consumed) await ev;
1347
1348 // overwrite line 2
1349 const fd = try await openReadWrite(file_path, File.default_mode);
1350 {
1351 defer os.close(fd);
1352
1353 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
1354 }
1355
1356 ev_consumed = true;
1357 switch ((try await ev).id) {
1358 WatchEventId.CloseWrite => {},
1359 WatchEventId.Delete => @panic("wrong event"),
1360 }
1361 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
1362 testing.expectEqualSlices(u8,
1363 \\line 1
1364 \\lorem ipsum
1365 , contents_updated);
1366
1367 // TODO test deleting the file and then re-adding it. we should get events for both
1368}
1369
1370pub const OutStream = struct {
1371 fd: fd_t,
1372 stream: Stream,
1373 allocator: *Allocator,
1374 offset: usize,
1375
1376 pub const Error = File.WriteError;
1377 pub const Stream = event.io.OutStream(Error);
1378
1379 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) OutStream {
1380 return OutStream{
1381 .fd = fd,
1382 .offset = offset,
1383 .stream = Stream{ .writeFn = writeFn },
1384 };
1385 }
1386
1387 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
1388 const self = @fieldParentPtr(OutStream, "stream", out_stream);
1389 const offset = self.offset;
1390 self.offset += bytes.len;
1391 return pwritev(self.allocator, self.fd, [_][]const u8{bytes}, offset);
1392 }
1393};
1394
1395pub const InStream = struct {
1396 fd: fd_t,
1397 stream: Stream,
1398 allocator: *Allocator,
1399 offset: usize,
1400
1401 pub const Error = PReadVError; // TODO make this not have OutOfMemory
1402 pub const Stream = event.io.InStream(Error);
1403
1404 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) InStream {
1405 return InStream{
1406 .fd = fd,
1407 .offset = offset,
1408 .stream = Stream{ .readFn = readFn },
1409 };
1410 }
1411
1412 fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
1413 const self = @fieldParentPtr(InStream, "stream", in_stream);
1414 const amt = try preadv(self.allocator, self.fd, [_][]u8{bytes}, self.offset);
1415 self.offset += amt;
1416 return amt;
1417 }
1418};
lib/std/event/loop.zig+317-50
......@@ -6,7 +6,6 @@ const testing = std.testing;
66const mem = std.mem;
77const AtomicRmwOp = builtin.AtomicRmwOp;
88const AtomicOrder = builtin.AtomicOrder;
9const fs = std.event.fs;
109const os = std.os;
1110const windows = os.windows;
1211const maxInt = std.math.maxInt;
......@@ -174,21 +173,19 @@ pub const Loop = struct {
174173 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
175174 switch (builtin.os) {
176175 .linux => {
177 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
176 self.os_data.fs_queue = std.atomic.Queue(Request).init();
178177 self.os_data.fs_queue_item = 0;
179178 // we need another thread for the file system because Linux does not have an async
180179 // file system I/O API.
181 self.os_data.fs_end_request = fs.RequestNode{
182 .prev = undefined,
183 .next = undefined,
184 .data = fs.Request{
185 .msg = fs.Request.Msg.End,
186 .finish = fs.Request.Finish.NoAction,
180 self.os_data.fs_end_request = Request.Node{
181 .data = Request{
182 .msg = .end,
183 .finish = .NoAction,
187184 },
188185 };
189186
190187 errdefer {
191 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
188 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
192189 }
193190 for (self.eventfd_resume_nodes) |*eventfd_node| {
194191 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
......@@ -207,10 +204,10 @@ pub const Loop = struct {
207204 }
208205
209206 self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC);
210 errdefer os.close(self.os_data.epollfd);
207 errdefer noasync os.close(self.os_data.epollfd);
211208
212209 self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK);
213 errdefer os.close(self.os_data.final_eventfd);
210 errdefer noasync os.close(self.os_data.final_eventfd);
214211
215212 self.os_data.final_eventfd_event = os.epoll_event{
216213 .events = os.EPOLLIN,
......@@ -237,7 +234,7 @@ pub const Loop = struct {
237234 var extra_thread_index: usize = 0;
238235 errdefer {
239236 // writing 8 bytes to an eventfd cannot fail
240 os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
237 noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
241238 while (extra_thread_index != 0) {
242239 extra_thread_index -= 1;
243240 self.extra_threads[extra_thread_index].wait();
......@@ -249,20 +246,20 @@ pub const Loop = struct {
249246 },
250247 .macosx, .freebsd, .netbsd, .dragonfly => {
251248 self.os_data.kqfd = try os.kqueue();
252 errdefer os.close(self.os_data.kqfd);
249 errdefer noasync os.close(self.os_data.kqfd);
253250
254251 self.os_data.fs_kqfd = try os.kqueue();
255 errdefer os.close(self.os_data.fs_kqfd);
252 errdefer noasync os.close(self.os_data.fs_kqfd);
256253
257 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
254 self.os_data.fs_queue = std.atomic.Queue(Request).init();
258255 // we need another thread for the file system because Darwin does not have an async
259256 // file system I/O API.
260 self.os_data.fs_end_request = fs.RequestNode{
257 self.os_data.fs_end_request = Request.Node{
261258 .prev = undefined,
262259 .next = undefined,
263 .data = fs.Request{
264 .msg = fs.Request.Msg.End,
265 .finish = fs.Request.Finish.NoAction,
260 .data = Request{
261 .msg = .end,
262 .finish = .NoAction,
266263 },
267264 };
268265
......@@ -407,14 +404,14 @@ pub const Loop = struct {
407404 fn deinitOsData(self: *Loop) void {
408405 switch (builtin.os) {
409406 .linux => {
410 os.close(self.os_data.final_eventfd);
411 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
412 os.close(self.os_data.epollfd);
407 noasync os.close(self.os_data.final_eventfd);
408 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
409 noasync os.close(self.os_data.epollfd);
413410 self.allocator.free(self.eventfd_resume_nodes);
414411 },
415412 .macosx, .freebsd, .netbsd, .dragonfly => {
416 os.close(self.os_data.kqfd);
417 os.close(self.os_data.fs_kqfd);
413 noasync os.close(self.os_data.kqfd);
414 noasync os.close(self.os_data.fs_kqfd);
418415 },
419416 .windows => {
420417 windows.CloseHandle(self.os_data.io_port);
......@@ -711,6 +708,190 @@ pub const Loop = struct {
711708 }
712709 }
713710
711 /// Performs an async `os.open` using a separate thread.
712 pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
713 var req_node = Request.Node{
714 .data = .{
715 .msg = .{
716 .open = .{
717 .path = file_path,
718 .flags = flags,
719 .mode = mode,
720 .result = undefined,
721 },
722 },
723 .finish = .{ .TickNode = .{ .data = @frame() } },
724 },
725 };
726 suspend {
727 self.posixFsRequest(&req_node);
728 }
729 return req_node.data.msg.open.result;
730 }
731
732 /// Performs an async `os.opent` using a separate thread.
733 pub fn openatZ(self: *Loop, fd: os.fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
734 var req_node = Request.Node{
735 .data = .{
736 .msg = .{
737 .openat = .{
738 .fd = fd,
739 .path = file_path,
740 .flags = flags,
741 .mode = mode,
742 .result = undefined,
743 },
744 },
745 .finish = .{ .TickNode = .{ .data = @frame() } },
746 },
747 };
748 suspend {
749 self.posixFsRequest(&req_node);
750 }
751 return req_node.data.msg.openat.result;
752 }
753
754 /// Performs an async `os.close` using a separate thread.
755 pub fn close(self: *Loop, fd: os.fd_t) void {
756 var req_node = Request.Node{
757 .data = .{
758 .msg = .{ .close = .{ .fd = fd } },
759 .finish = .{ .TickNode = .{ .data = @frame() } },
760 },
761 };
762 suspend {
763 self.posixFsRequest(&req_node);
764 }
765 }
766
767 /// Performs an async `os.read` using a separate thread.
768 /// `fd` must block and not return EAGAIN.
769 pub fn read(self: *Loop, fd: os.fd_t, buf: []u8) os.ReadError!usize {
770 var req_node = Request.Node{
771 .data = .{
772 .msg = .{
773 .read = .{
774 .fd = fd,
775 .buf = buf,
776 .result = undefined,
777 },
778 },
779 .finish = .{ .TickNode = .{ .data = @frame() } },
780 },
781 };
782 suspend {
783 self.posixFsRequest(&req_node);
784 }
785 return req_node.data.msg.read.result;
786 }
787
788 /// Performs an async `os.readv` using a separate thread.
789 /// `fd` must block and not return EAGAIN.
790 pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec) os.ReadError!usize {
791 var req_node = Request.Node{
792 .data = .{
793 .msg = .{
794 .readv = .{
795 .fd = fd,
796 .iov = iov,
797 .result = undefined,
798 },
799 },
800 .finish = .{ .TickNode = .{ .data = @frame() } },
801 },
802 };
803 suspend {
804 self.posixFsRequest(&req_node);
805 }
806 return req_node.data.msg.readv.result;
807 }
808
809 /// Performs an async `os.preadv` using a separate thread.
810 /// `fd` must block and not return EAGAIN.
811 pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64) os.ReadError!usize {
812 var req_node = Request.Node{
813 .data = .{
814 .msg = .{
815 .preadv = .{
816 .fd = fd,
817 .iov = iov,
818 .offset = offset,
819 .result = undefined,
820 },
821 },
822 .finish = .{ .TickNode = .{ .data = @frame() } },
823 },
824 };
825 suspend {
826 self.posixFsRequest(&req_node);
827 }
828 return req_node.data.msg.preadv.result;
829 }
830
831 /// Performs an async `os.write` using a separate thread.
832 /// `fd` must block and not return EAGAIN.
833 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!void {
834 var req_node = Request.Node{
835 .data = .{
836 .msg = .{
837 .write = .{
838 .fd = fd,
839 .bytes = bytes,
840 .result = undefined,
841 },
842 },
843 .finish = .{ .TickNode = .{ .data = @frame() } },
844 },
845 };
846 suspend {
847 self.posixFsRequest(&req_node);
848 }
849 return req_node.data.msg.write.result;
850 }
851
852 /// Performs an async `os.writev` using a separate thread.
853 /// `fd` must block and not return EAGAIN.
854 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) WriteError!void {
855 var req_node = Request.Node{
856 .data = .{
857 .msg = .{
858 .writev = .{
859 .fd = fd,
860 .iov = iov,
861 .result = undefined,
862 },
863 },
864 .finish = .{ .TickNode = .{ .data = @frame() } },
865 },
866 };
867 suspend {
868 self.posixFsRequest(&req_node);
869 }
870 return req_node.data.msg.writev.result;
871 }
872
873 /// Performs an async `os.pwritev` using a separate thread.
874 /// `fd` must block and not return EAGAIN.
875 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) WriteError!void {
876 var req_node = Request.Node{
877 .data = .{
878 .msg = .{
879 .pwritev = .{
880 .fd = fd,
881 .iov = iov,
882 .offset = offset,
883 .result = undefined,
884 },
885 },
886 .finish = .{ .TickNode = .{ .data = @frame() } },
887 },
888 };
889 suspend {
890 self.posixFsRequest(&req_node);
891 }
892 return req_node.data.msg.pwritev.result;
893 }
894
714895 fn workerRun(self: *Loop) void {
715896 while (true) {
716897 while (true) {
......@@ -804,7 +985,7 @@ pub const Loop = struct {
804985 }
805986 }
806987
807 fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void {
988 fn posixFsRequest(self: *Loop, request_node: *Request.Node) void {
808989 self.beginOneEvent(); // finished in posixFsRun after processing the msg
809990 self.os_data.fs_queue.put(request_node);
810991 switch (builtin.os) {
......@@ -826,7 +1007,7 @@ pub const Loop = struct {
8261007 }
8271008 }
8281009
829 fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void {
1010 fn posixFsCancel(self: *Loop, request_node: *Request.Node) void {
8301011 if (self.os_data.fs_queue.remove(request_node)) {
8311012 self.finishOneEvent();
8321013 }
......@@ -841,37 +1022,32 @@ pub const Loop = struct {
8411022 }
8421023 while (self.os_data.fs_queue.get()) |node| {
8431024 switch (node.data.msg) {
844 .End => return,
845 .WriteV => |*msg| {
1025 .end => return,
1026 .read => |*msg| {
1027 msg.result = noasync os.read(msg.fd, msg.buf);
1028 },
1029 .write => |*msg| {
1030 msg.result = noasync os.write(msg.fd, msg.bytes);
1031 },
1032 .writev => |*msg| {
8461033 msg.result = noasync os.writev(msg.fd, msg.iov);
8471034 },
848 .PWriteV => |*msg| {
1035 .pwritev => |*msg| {
8491036 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);
8501037 },
851 .PReadV => |*msg| {
1038 .preadv => |*msg| {
8521039 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);
8531040 },
854 .Open => |*msg| {
855 msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode);
1041 .open => |*msg| {
1042 msg.result = noasync os.openC(msg.path, msg.flags, msg.mode);
8561043 },
857 .Close => |*msg| noasync os.close(msg.fd),
858 .WriteFile => |*msg| blk: {
859 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
860 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
861 os.O_CLOEXEC | os.O_TRUNC;
862 const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
863 msg.result = err;
864 break :blk;
865 };
866 defer noasync os.close(fd);
867 msg.result = noasync os.write(fd, msg.contents);
1044 .openat => |*msg| {
1045 msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode);
8681046 },
1047 .close => |*msg| noasync os.close(msg.fd),
8691048 }
8701049 switch (node.data.finish) {
8711050 .TickNode => |*tick_node| self.onNextTick(tick_node),
872 .DeallocCloseOperation => |close_op| {
873 self.allocator.destroy(close_op);
874 },
8751051 .NoAction => {},
8761052 }
8771053 self.finishOneEvent();
......@@ -911,8 +1087,8 @@ pub const Loop = struct {
9111087 fs_kevent_wait: os.Kevent,
9121088 fs_thread: *Thread,
9131089 fs_kqfd: i32,
914 fs_queue: std.atomic.Queue(fs.Request),
915 fs_end_request: fs.RequestNode,
1090 fs_queue: std.atomic.Queue(Request),
1091 fs_end_request: Request.Node,
9161092 };
9171093
9181094 const LinuxOsData = struct {
......@@ -921,8 +1097,99 @@ pub const Loop = struct {
9211097 final_eventfd_event: os.linux.epoll_event,
9221098 fs_thread: *Thread,
9231099 fs_queue_item: i32,
924 fs_queue: std.atomic.Queue(fs.Request),
925 fs_end_request: fs.RequestNode,
1100 fs_queue: std.atomic.Queue(Request),
1101 fs_end_request: Request.Node,
1102 };
1103
1104 pub const Request = struct {
1105 msg: Msg,
1106 finish: Finish,
1107
1108 pub const Node = std.atomic.Queue(Request).Node;
1109
1110 pub const Finish = union(enum) {
1111 TickNode: Loop.NextTickNode,
1112 NoAction,
1113 };
1114
1115 pub const Msg = union(enum) {
1116 read: Read,
1117 write: Write,
1118 writev: WriteV,
1119 pwritev: PWriteV,
1120 preadv: PReadV,
1121 open: Open,
1122 openat: OpenAt,
1123 close: Close,
1124
1125 /// special - means the fs thread should exit
1126 end,
1127
1128 pub const Read = struct {
1129 fd: os.fd_t,
1130 buf: []u8,
1131 result: Error!usize,
1132
1133 pub const Error = os.ReadError;
1134 };
1135
1136 pub const Write = struct {
1137 fd: os.fd_t,
1138 bytes: []const u8,
1139 result: Error!void,
1140
1141 pub const Error = os.WriteError;
1142 };
1143
1144 pub const WriteV = struct {
1145 fd: os.fd_t,
1146 iov: []const os.iovec_const,
1147 result: Error!void,
1148
1149 pub const Error = os.WriteError;
1150 };
1151
1152 pub const PWriteV = struct {
1153 fd: os.fd_t,
1154 iov: []const os.iovec_const,
1155 offset: usize,
1156 result: Error!void,
1157
1158 pub const Error = os.WriteError;
1159 };
1160
1161 pub const PReadV = struct {
1162 fd: os.fd_t,
1163 iov: []const os.iovec,
1164 offset: usize,
1165 result: Error!usize,
1166
1167 pub const Error = os.ReadError;
1168 };
1169
1170 pub const Open = struct {
1171 path: [*:0]const u8,
1172 flags: u32,
1173 mode: os.mode_t,
1174 result: Error!os.fd_t,
1175
1176 pub const Error = os.OpenError;
1177 };
1178
1179 pub const OpenAt = struct {
1180 fd: os.fd_t,
1181 path: [*:0]const u8,
1182 flags: u32,
1183 mode: os.mode_t,
1184 result: Error!os.fd_t,
1185
1186 pub const Error = os.OpenError;
1187 };
1188
1189 pub const Close = struct {
1190 fd: os.fd_t,
1191 };
1192 };
9261193 };
9271194};
9281195
lib/std/fs.zig+31-6
......@@ -23,6 +23,8 @@ pub const realpathW = os.realpathW;
2323pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
2424pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
2525
26pub const Watch = @import("fs/watch.zig").Watch;
27
2628/// This represents the maximum size of a UTF-8 encoded file path.
2729/// All file system operations which return a path are guaranteed to
2830/// fit into a UTF-8 encoded array of this length.
......@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(
4345 base64.standard_pad_char,
4446);
4547
48/// Whether or not async file system syscalls need a dedicated thread because the operating
49/// system does not support non-blocking I/O on the file system.
50pub const need_async_thread = std.io.is_async and switch (builtin.os) {
51 .windows, .other => false,
52 else => true,
53};
54
4655/// TODO remove the allocator requirement from this API
4756pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
4857 if (symLink(existing_path, new_path)) {
......@@ -688,7 +697,11 @@ pub const Dir = struct {
688697 }
689698
690699 pub fn close(self: *Dir) void {
691 os.close(self.fd);
700 if (need_async_thread) {
701 std.event.Loop.instance.?.close(self.fd);
702 } else {
703 os.close(self.fd);
704 }
692705 self.* = undefined;
693706 }
694707
......@@ -718,8 +731,11 @@ pub const Dir = struct {
718731 @as(u32, os.O_WRONLY)
719732 else
720733 @as(u32, os.O_RDONLY);
721 const fd = try os.openatC(self.fd, sub_path, os_flags, 0);
722 return File{ .handle = fd };
734 const fd = if (need_async_thread)
735 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
736 else
737 try os.openatC(self.fd, sub_path, os_flags, 0);
738 return File{ .handle = fd, .io_mode = .blocking };
723739 }
724740
725741 /// Same as `openFile` but Windows-only and the path parameter is
......@@ -756,8 +772,11 @@ pub const Dir = struct {
756772 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
757773 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
758774 (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);
759 const fd = try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);
760 return File{ .handle = fd };
775 const fd = if (need_async_thread)
776 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
777 else
778 try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);
779 return File{ .handle = fd, .io_mode = .blocking };
761780 }
762781
763782 /// Same as `createFile` but Windows-only and the path parameter is
......@@ -919,7 +938,12 @@ pub const Dir = struct {
919938 }
920939
921940 fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
922 const fd = os.openatC(self.fd, sub_path_c, flags | os.O_DIRECTORY, 0) catch |err| switch (err) {
941 const os_flags = flags | os.O_DIRECTORY;
942 const result = if (need_async_thread)
943 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, 0)
944 else
945 os.openatC(self.fd, sub_path_c, os_flags, 0);
946 const fd = result catch |err| switch (err) {
923947 error.FileTooBig => unreachable, // can't happen for directories
924948 error.IsDir => unreachable, // we're providing O_DIRECTORY
925949 error.NoSpaceLeft => unreachable, // not providing O_CREAT
......@@ -1588,4 +1612,5 @@ test "" {
15881612 _ = @import("fs/path.zig");
15891613 _ = @import("fs/file.zig");
15901614 _ = @import("fs/get_app_data_dir.zig");
1615 _ = @import("fs/watch.zig");
15911616}
lib/std/fs/file.zig+77-74
......@@ -8,18 +8,29 @@ const assert = std.debug.assert;
88const windows = os.windows;
99const Os = builtin.Os;
1010const maxInt = std.math.maxInt;
11const need_async_thread = std.fs.need_async_thread;
1112
1213pub const File = struct {
1314 /// The OS-specific file descriptor or file handle.
1415 handle: os.fd_t,
1516
16 pub const Mode = switch (builtin.os) {
17 Os.windows => void,
18 else => u32,
19 };
17 /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O.
18 /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking
19 /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor,
20 /// or, more specifically, whether the I/O is blocking.
21 io_mode: io.Mode,
22
23 /// Even when std.io.mode is async, it is still sometimes desirable to perform blocking I/O, although
24 /// not by default. For example, when printing a stack trace to stderr.
25 async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no,
26
27 pub const async_block_allowed_yes = if (io.is_async) true else {};
28 pub const async_block_allowed_no = if (io.is_async) false else {};
29
30 pub const Mode = os.mode_t;
2031
2132 pub const default_mode = switch (builtin.os) {
22 Os.windows => {},
33 .windows => 0,
2334 else => 0o666,
2435 };
2536
......@@ -49,87 +60,27 @@ pub const File = struct {
4960 mode: Mode = default_mode,
5061 };
5162
52 /// Deprecated; call `std.fs.Dir.openFile` directly.
53 pub fn openRead(path: []const u8) OpenError!File {
54 return std.fs.cwd().openFile(path, .{});
55 }
56
57 /// Deprecated; call `std.fs.Dir.openFileC` directly.
58 pub fn openReadC(path_c: [*:0]const u8) OpenError!File {
59 return std.fs.cwd().openFileC(path_c, .{});
60 }
61
62 /// Deprecated; call `std.fs.Dir.openFileW` directly.
63 pub fn openReadW(path_w: [*:0]const u16) OpenError!File {
64 return std.fs.cwd().openFileW(path_w, .{});
65 }
66
67 /// Deprecated; call `std.fs.Dir.createFile` directly.
68 pub fn openWrite(path: []const u8) OpenError!File {
69 return std.fs.cwd().createFile(path, .{});
70 }
71
72 /// Deprecated; call `std.fs.Dir.createFile` directly.
73 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
74 return std.fs.cwd().createFile(path, .{ .mode = file_mode });
75 }
76
77 /// Deprecated; call `std.fs.Dir.createFileC` directly.
78 pub fn openWriteModeC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
79 return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode });
80 }
81
82 /// Deprecated; call `std.fs.Dir.createFileW` directly.
83 pub fn openWriteModeW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
84 return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode });
85 }
86
87 /// Deprecated; call `std.fs.Dir.createFile` directly.
88 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
89 return std.fs.cwd().createFile(path, .{
90 .mode = file_mode,
91 .exclusive = true,
92 });
93 }
94
95 /// Deprecated; call `std.fs.Dir.createFileC` directly.
96 pub fn openWriteNoClobberC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
97 return std.fs.cwd().createFileC(path_c, .{
98 .mode = file_mode,
99 .exclusive = true,
100 });
101 }
102
103 /// Deprecated; call `std.fs.Dir.createFileW` directly.
104 pub fn openWriteNoClobberW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
105 return std.fs.cwd().createFileW(path_w, .{
106 .mode = file_mode,
107 .exclusive = true,
108 });
109 }
110
111 pub fn openHandle(handle: os.fd_t) File {
112 return File{ .handle = handle };
113 }
114
11563 /// Test for the existence of `path`.
11664 /// `path` is UTF8-encoded.
11765 /// In general it is recommended to avoid this function. For example,
11866 /// instead of testing if a file exists and then opening it, just
11967 /// open it and handle the error for file not found.
12068 /// TODO: deprecate this and move it to `std.fs.Dir`.
69 /// TODO: integrate with async I/O
12170 pub fn access(path: []const u8) !void {
12271 return os.access(path, os.F_OK);
12372 }
12473
12574 /// Same as `access` except the parameter is null-terminated.
12675 /// TODO: deprecate this and move it to `std.fs.Dir`.
76 /// TODO: integrate with async I/O
12777 pub fn accessC(path: [*:0]const u8) !void {
12878 return os.accessC(path, os.F_OK);
12979 }
13080
13181 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.
13282 /// TODO: deprecate this and move it to `std.fs.Dir`.
83 /// TODO: integrate with async I/O
13384 pub fn accessW(path: [*:0]const u16) !void {
13485 return os.accessW(path, os.F_OK);
13586 }
......@@ -137,7 +88,11 @@ pub const File = struct {
13788 /// Upon success, the stream is in an uninitialized state. To continue using it,
13889 /// you must use the open() function.
13990 pub fn close(self: File) void {
140 return os.close(self.handle);
91 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
92 std.event.Loop.instance.?.close(self.handle);
93 } else {
94 return os.close(self.handle);
95 }
14196 }
14297
14398 /// Test whether the file refers to a terminal.
......@@ -167,26 +122,31 @@ pub const File = struct {
167122 pub const SeekError = os.SeekError;
168123
169124 /// Repositions read/write file offset relative to the current offset.
125 /// TODO: integrate with async I/O
170126 pub fn seekBy(self: File, offset: i64) SeekError!void {
171127 return os.lseek_CUR(self.handle, offset);
172128 }
173129
174130 /// Repositions read/write file offset relative to the end.
131 /// TODO: integrate with async I/O
175132 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
176133 return os.lseek_END(self.handle, offset);
177134 }
178135
179136 /// Repositions read/write file offset relative to the beginning.
137 /// TODO: integrate with async I/O
180138 pub fn seekTo(self: File, offset: u64) SeekError!void {
181139 return os.lseek_SET(self.handle, offset);
182140 }
183141
184142 pub const GetPosError = os.SeekError || os.FStatError;
185143
144 /// TODO: integrate with async I/O
186145 pub fn getPos(self: File) GetPosError!u64 {
187146 return os.lseek_CUR_get(self.handle);
188147 }
189148
149 /// TODO: integrate with async I/O
190150 pub fn getEndPos(self: File) GetPosError!u64 {
191151 if (builtin.os == .windows) {
192152 return windows.GetFileSizeEx(self.handle);
......@@ -196,6 +156,7 @@ pub const File = struct {
196156
197157 pub const ModeError = os.FStatError;
198158
159 /// TODO: integrate with async I/O
199160 pub fn mode(self: File) ModeError!Mode {
200161 if (builtin.os == .windows) {
201162 return {};
......@@ -219,6 +180,7 @@ pub const File = struct {
219180
220181 pub const StatError = os.FStatError;
221182
183 /// TODO: integrate with async I/O
222184 pub fn stat(self: File) StatError!Stat {
223185 if (builtin.os == .windows) {
224186 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
......@@ -259,6 +221,7 @@ pub const File = struct {
259221 /// and therefore this function cannot guarantee any precision will be stored.
260222 /// Further, the maximum value is limited by the system ABI. When a value is provided
261223 /// that exceeds this range, the value is clamped to the maximum.
224 /// TODO: integrate with async I/O
262225 pub fn updateTimes(
263226 self: File,
264227 /// access timestamp in nanoseconds
......@@ -287,21 +250,61 @@ pub const File = struct {
287250 pub const ReadError = os.ReadError;
288251
289252 pub fn read(self: File, buffer: []u8) ReadError!usize {
253 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
254 return std.event.Loop.instance.?.read(self.handle, buffer);
255 }
290256 return os.read(self.handle, buffer);
291257 }
292258
259 pub fn pread(self: File, buffer: []u8, offset: u64) ReadError!usize {
260 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
261 return std.event.Loop.instance.?.pread(self.handle, buffer);
262 }
263 return os.pread(self.handle, buffer, offset);
264 }
265
266 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
267 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
268 return std.event.Loop.instance.?.readv(self.handle, iovecs);
269 }
270 return os.readv(self.handle, iovecs);
271 }
272
273 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) ReadError!usize {
274 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
275 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);
276 }
277 return os.preadv(self.handle, iovecs, offset);
278 }
279
293280 pub const WriteError = os.WriteError;
294281
295282 pub fn write(self: File, bytes: []const u8) WriteError!void {
283 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
284 return std.event.Loop.instance.?.write(self.handle, bytes);
285 }
296286 return os.write(self.handle, bytes);
297287 }
298288
299 pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void {
300 if (std.event.Loop.instance) |loop| {
301 return std.event.fs.writevPosix(loop, self.handle, iovecs);
302 } else {
303 return os.writev(self.handle, iovecs);
289 pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void {
290 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
291 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
292 }
293 return os.pwrite(self.handle, bytes, offset);
294 }
295
296 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void {
297 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
298 return std.event.Loop.instance.?.writev(self.handle, iovecs);
299 }
300 return os.writev(self.handle, iovecs);
301 }
302
303 pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void {
304 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
305 return std.event.Loop.instance.?.pwritev(self.handle, iovecs);
304306 }
307 return os.pwritev(self.handle, iovecs);
305308 }
306309
307310 pub fn inStream(file: File) InStream {
lib/std/fs/watch.zig created+673
......@@ -0,0 +1,673 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14const WatchEventId = enum {
15 CloseWrite,
16 Delete,
17};
18
19fn eqlString(a: []const u16, b: []const u16) bool {
20 if (a.len != b.len) return false;
21 if (a.ptr == b.ptr) return true;
22 return mem.compare(u16, a, b) == .Equal;
23}
24
25fn hashString(s: []const u16) u32 {
26 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
27}
28
29const WatchEventError = error{
30 UserResourceLimitReached,
31 SystemResources,
32 AccessDenied,
33 Unexpected, // TODO remove this possibility
34};
35
36pub fn Watch(comptime V: type) type {
37 return struct {
38 channel: *event.Channel(Event.Error!Event),
39 os_data: OsData,
40 allocator: *Allocator,
41
42 const OsData = switch (builtin.os) {
43 // TODO https://github.com/ziglang/zig/issues/3778
44 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
45 .linux => LinuxOsData,
46 .windows => WindowsOsData,
47
48 else => @compileError("Unsupported OS"),
49 };
50
51 const KqOsData = struct {
52 file_table: FileTable,
53 table_lock: event.Lock,
54
55 const FileTable = std.StringHashMap(*Put);
56 const Put = struct {
57 putter_frame: @Frame(kqPutEvents),
58 cancelled: bool = false,
59 value: V,
60 };
61 };
62
63 const WindowsOsData = struct {
64 table_lock: event.Lock,
65 dir_table: DirTable,
66 all_putters: std.atomic.Queue(Put),
67 ref_count: std.atomic.Int(usize),
68
69 const Put = struct {
70 putter: anyframe,
71 cancelled: bool = false,
72 };
73
74 const DirTable = std.StringHashMap(*Dir);
75 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
76
77 const Dir = struct {
78 putter_frame: @Frame(windowsDirReader),
79 file_table: FileTable,
80 table_lock: event.Lock,
81 };
82 };
83
84 const LinuxOsData = struct {
85 putter_frame: @Frame(linuxEventPutter),
86 inotify_fd: i32,
87 wd_table: WdTable,
88 table_lock: event.Lock,
89 cancelled: bool = false,
90
91 const WdTable = std.AutoHashMap(i32, Dir);
92 const FileTable = std.StringHashMap(V);
93
94 const Dir = struct {
95 dirname: []const u8,
96 file_table: FileTable,
97 };
98 };
99
100 const Self = @This();
101
102 pub const Event = struct {
103 id: Id,
104 data: V,
105
106 pub const Id = WatchEventId;
107 pub const Error = WatchEventError;
108 };
109
110 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
111 const channel = try allocator.create(event.Channel(Event.Error!Event));
112 errdefer allocator.destroy(channel);
113 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
114 errdefer allocator.free(buf);
115 channel.init(buf);
116 errdefer channel.deinit();
117
118 const self = try allocator.create(Self);
119 errdefer allocator.destroy(self);
120
121 switch (builtin.os) {
122 .linux => {
123 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
124 errdefer os.close(inotify_fd);
125
126 self.* = Self{
127 .allocator = allocator,
128 .channel = channel,
129 .os_data = OsData{
130 .putter_frame = undefined,
131 .inotify_fd = inotify_fd,
132 .wd_table = OsData.WdTable.init(allocator),
133 .table_lock = event.Lock.init(),
134 },
135 };
136
137 self.os_data.putter_frame = async self.linuxEventPutter();
138 return self;
139 },
140
141 .windows => {
142 self.* = Self{
143 .allocator = allocator,
144 .channel = channel,
145 .os_data = OsData{
146 .table_lock = event.Lock.init(),
147 .dir_table = OsData.DirTable.init(allocator),
148 .ref_count = std.atomic.Int(usize).init(1),
149 .all_putters = std.atomic.Queue(anyframe).init(),
150 },
151 };
152 return self;
153 },
154
155 .macosx, .freebsd, .netbsd, .dragonfly => {
156 self.* = Self{
157 .allocator = allocator,
158 .channel = channel,
159 .os_data = OsData{
160 .table_lock = event.Lock.init(),
161 .file_table = OsData.FileTable.init(allocator),
162 },
163 };
164 return self;
165 },
166 else => @compileError("Unsupported OS"),
167 }
168 }
169
170 /// All addFile calls and removeFile calls must have completed.
171 pub fn deinit(self: *Self) void {
172 switch (builtin.os) {
173 .macosx, .freebsd, .netbsd, .dragonfly => {
174 // TODO we need to cancel the frames before destroying the lock
175 self.os_data.table_lock.deinit();
176 var it = self.os_data.file_table.iterator();
177 while (it.next()) |entry| {
178 entry.cancelled = true;
179 await entry.value.putter;
180 self.allocator.free(entry.key);
181 self.allocator.free(entry.value);
182 }
183 self.channel.deinit();
184 self.allocator.destroy(self.channel.buffer_nodes);
185 self.allocator.destroy(self);
186 },
187 .linux => {
188 self.os_data.cancelled = true;
189 await self.os_data.putter_frame;
190 self.allocator.destroy(self);
191 },
192 .windows => {
193 while (self.os_data.all_putters.get()) |putter_node| {
194 putter_node.cancelled = true;
195 await putter_node.frame;
196 }
197 self.deref();
198 },
199 else => @compileError("Unsupported OS"),
200 }
201 }
202
203 fn ref(self: *Self) void {
204 _ = self.os_data.ref_count.incr();
205 }
206
207 fn deref(self: *Self) void {
208 if (self.os_data.ref_count.decr() == 1) {
209 self.os_data.table_lock.deinit();
210 var it = self.os_data.dir_table.iterator();
211 while (it.next()) |entry| {
212 self.allocator.free(entry.key);
213 self.allocator.destroy(entry.value);
214 }
215 self.os_data.dir_table.deinit();
216 self.channel.deinit();
217 self.allocator.destroy(self.channel.buffer_nodes);
218 self.allocator.destroy(self);
219 }
220 }
221
222 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
223 switch (builtin.os) {
224 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
225 .linux => return addFileLinux(self, file_path, value),
226 .windows => return addFileWindows(self, file_path, value),
227 else => @compileError("Unsupported OS"),
228 }
229 }
230
231 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
232 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
233 var resolved_path_consumed = false;
234 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
235
236 var close_op = try CloseOperation.start(self.allocator);
237 var close_op_consumed = false;
238 defer if (!close_op_consumed) close_op.finish();
239
240 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
241 const mode = 0;
242 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
243 close_op.setHandle(fd);
244
245 var put = try self.allocator.create(OsData.Put);
246 errdefer self.allocator.destroy(put);
247 put.* = OsData.Put{
248 .value = value,
249 .putter_frame = undefined,
250 };
251 put.putter_frame = async self.kqPutEvents(close_op, put);
252 close_op_consumed = true;
253 errdefer {
254 put.cancelled = true;
255 await put.putter_frame;
256 }
257
258 const result = blk: {
259 const held = self.os_data.table_lock.acquire();
260 defer held.release();
261
262 const gop = try self.os_data.file_table.getOrPut(resolved_path);
263 if (gop.found_existing) {
264 const prev_value = gop.kv.value.value;
265 await gop.kv.value.putter_frame;
266 gop.kv.value = put;
267 break :blk prev_value;
268 } else {
269 resolved_path_consumed = true;
270 gop.kv.value = put;
271 break :blk null;
272 }
273 };
274
275 return result;
276 }
277
278 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
279 global_event_loop.beginOneEvent();
280
281 defer {
282 close_op.finish();
283 global_event_loop.finishOneEvent();
284 }
285
286 while (!put.cancelled) {
287 if (global_event_loop.bsdWaitKev(
288 @intCast(usize, close_op.getHandle()),
289 os.EVFILT_VNODE,
290 os.NOTE_WRITE | os.NOTE_DELETE,
291 )) |kev| {
292 // TODO handle EV_ERROR
293 if (kev.fflags & os.NOTE_DELETE != 0) {
294 self.channel.put(Self.Event{
295 .id = Event.Id.Delete,
296 .data = put.value,
297 });
298 } else if (kev.fflags & os.NOTE_WRITE != 0) {
299 self.channel.put(Self.Event{
300 .id = Event.Id.CloseWrite,
301 .data = put.value,
302 });
303 }
304 } else |err| switch (err) {
305 error.EventNotFound => unreachable,
306 error.ProcessNotFound => unreachable,
307 error.Overflow => unreachable,
308 error.AccessDenied, error.SystemResources => |casted_err| {
309 self.channel.put(casted_err);
310 },
311 }
312 }
313 }
314
315 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
316 const dirname = std.fs.path.dirname(file_path) orelse ".";
317 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
318 var dirname_with_null_consumed = false;
319 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
320
321 const basename = std.fs.path.basename(file_path);
322 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
323 var basename_with_null_consumed = false;
324 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
325
326 const wd = try os.inotify_add_watchC(
327 self.os_data.inotify_fd,
328 dirname_with_null.ptr,
329 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
330 );
331 // wd is either a newly created watch or an existing one.
332
333 const held = self.os_data.table_lock.acquire();
334 defer held.release();
335
336 const gop = try self.os_data.wd_table.getOrPut(wd);
337 if (!gop.found_existing) {
338 gop.kv.value = OsData.Dir{
339 .dirname = dirname_with_null,
340 .file_table = OsData.FileTable.init(self.allocator),
341 };
342 dirname_with_null_consumed = true;
343 }
344 const dir = &gop.kv.value;
345
346 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
347 if (file_table_gop.found_existing) {
348 const prev_value = file_table_gop.kv.value;
349 file_table_gop.kv.value = value;
350 return prev_value;
351 } else {
352 file_table_gop.kv.value = value;
353 basename_with_null_consumed = true;
354 return null;
355 }
356 }
357
358 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
359 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
360 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
361 var dirname_consumed = false;
362 defer if (!dirname_consumed) self.allocator.free(dirname);
363
364 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
365 defer self.allocator.free(dirname_utf16le);
366
367 // TODO https://github.com/ziglang/zig/issues/265
368 const basename = std.fs.path.basename(file_path);
369 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
370 var basename_utf16le_null_consumed = false;
371 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
372 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
373
374 const dir_handle = try windows.CreateFileW(
375 dirname_utf16le.ptr,
376 windows.FILE_LIST_DIRECTORY,
377 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
378 null,
379 windows.OPEN_EXISTING,
380 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
381 null,
382 );
383 var dir_handle_consumed = false;
384 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
385
386 const held = self.os_data.table_lock.acquire();
387 defer held.release();
388
389 const gop = try self.os_data.dir_table.getOrPut(dirname);
390 if (gop.found_existing) {
391 const dir = gop.kv.value;
392 const held_dir_lock = dir.table_lock.acquire();
393 defer held_dir_lock.release();
394
395 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
396 if (file_gop.found_existing) {
397 const prev_value = file_gop.kv.value;
398 file_gop.kv.value = value;
399 return prev_value;
400 } else {
401 file_gop.kv.value = value;
402 basename_utf16le_null_consumed = true;
403 return null;
404 }
405 } else {
406 errdefer _ = self.os_data.dir_table.remove(dirname);
407 const dir = try self.allocator.create(OsData.Dir);
408 errdefer self.allocator.destroy(dir);
409
410 dir.* = OsData.Dir{
411 .file_table = OsData.FileTable.init(self.allocator),
412 .table_lock = event.Lock.init(),
413 .putter_frame = undefined,
414 };
415 gop.kv.value = dir;
416 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
417 basename_utf16le_null_consumed = true;
418
419 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
420 dir_handle_consumed = true;
421
422 dirname_consumed = true;
423
424 return null;
425 }
426 }
427
428 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
429 self.ref();
430 defer self.deref();
431
432 defer os.close(dir_handle);
433
434 var putter_node = std.atomic.Queue(anyframe).Node{
435 .data = .{ .putter = @frame() },
436 .prev = null,
437 .next = null,
438 };
439 self.os_data.all_putters.put(&putter_node);
440 defer _ = self.os_data.all_putters.remove(&putter_node);
441
442 var resume_node = Loop.ResumeNode.Basic{
443 .base = Loop.ResumeNode{
444 .id = Loop.ResumeNode.Id.Basic,
445 .handle = @frame(),
446 .overlapped = windows.OVERLAPPED{
447 .Internal = 0,
448 .InternalHigh = 0,
449 .Offset = 0,
450 .OffsetHigh = 0,
451 .hEvent = null,
452 },
453 },
454 };
455 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
456
457 // TODO handle this error not in the channel but in the setup
458 _ = windows.CreateIoCompletionPort(
459 dir_handle,
460 global_event_loop.os_data.io_port,
461 undefined,
462 undefined,
463 ) catch |err| {
464 self.channel.put(err);
465 return;
466 };
467
468 while (!putter_node.data.cancelled) {
469 {
470 // TODO only 1 beginOneEvent for the whole function
471 global_event_loop.beginOneEvent();
472 errdefer global_event_loop.finishOneEvent();
473 errdefer {
474 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
475 }
476 suspend {
477 _ = windows.kernel32.ReadDirectoryChangesW(
478 dir_handle,
479 &event_buf,
480 @intCast(windows.DWORD, event_buf.len),
481 windows.FALSE, // watch subtree
482 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
483 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
484 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
485 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
486 null, // number of bytes transferred (unused for async)
487 &resume_node.base.overlapped,
488 null, // completion routine - unused because we use IOCP
489 );
490 }
491 }
492 var bytes_transferred: windows.DWORD = undefined;
493 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
494 const err = switch (windows.kernel32.GetLastError()) {
495 else => |err| windows.unexpectedError(err),
496 };
497 self.channel.put(err);
498 } else {
499 // can't use @bytesToSlice because of the special variable length name field
500 var ptr = event_buf[0..].ptr;
501 const end_ptr = ptr + bytes_transferred;
502 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
503 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
504 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
505 const emit = switch (ev.Action) {
506 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
507 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
508 else => null,
509 };
510 if (emit) |id| {
511 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
512 const user_value = blk: {
513 const held = dir.table_lock.acquire();
514 defer held.release();
515
516 if (dir.file_table.get(basename_utf16le)) |entry| {
517 break :blk entry.value;
518 } else {
519 break :blk null;
520 }
521 };
522 if (user_value) |v| {
523 self.channel.put(Event{
524 .id = id,
525 .data = v,
526 });
527 }
528 }
529 if (ev.NextEntryOffset == 0) break;
530 }
531 }
532 }
533 }
534
535 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
536 @panic("TODO");
537 }
538
539 fn linuxEventPutter(self: *Self) void {
540 global_event_loop.beginOneEvent();
541
542 defer {
543 self.os_data.table_lock.deinit();
544 var wd_it = self.os_data.wd_table.iterator();
545 while (wd_it.next()) |wd_entry| {
546 var file_it = wd_entry.value.file_table.iterator();
547 while (file_it.next()) |file_entry| {
548 self.allocator.free(file_entry.key);
549 }
550 self.allocator.free(wd_entry.value.dirname);
551 wd_entry.value.file_table.deinit();
552 }
553 self.os_data.wd_table.deinit();
554 global_event_loop.finishOneEvent();
555 os.close(self.os_data.inotify_fd);
556 self.channel.deinit();
557 self.allocator.free(self.channel.buffer_nodes);
558 }
559
560 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
561
562 while (!self.os_data.cancelled) {
563 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
564 const errno = os.linux.getErrno(rc);
565 switch (errno) {
566 0 => {
567 // can't use @bytesToSlice because of the special variable length name field
568 var ptr = event_buf[0..].ptr;
569 const end_ptr = ptr + event_buf.len;
570 var ev: *os.linux.inotify_event = undefined;
571 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
572 ev = @ptrCast(*os.linux.inotify_event, ptr);
573 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
574 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
575 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
576 const basename_with_null = basename_ptr[0..ev.len];
577 const user_value = blk: {
578 const held = self.os_data.table_lock.acquire();
579 defer held.release();
580
581 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
582 if (dir.file_table.get(basename_with_null)) |entry| {
583 break :blk entry.value;
584 } else {
585 break :blk null;
586 }
587 };
588 if (user_value) |v| {
589 self.channel.put(Event{
590 .id = WatchEventId.CloseWrite,
591 .data = v,
592 });
593 }
594 }
595
596 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
597 }
598 },
599 os.linux.EINTR => continue,
600 os.linux.EINVAL => unreachable,
601 os.linux.EFAULT => unreachable,
602 os.linux.EAGAIN => {
603 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
604 },
605 else => unreachable,
606 }
607 }
608 }
609 };
610}
611
612const test_tmp_dir = "std_event_fs_test";
613
614test "write a file, watch it, write it again" {
615 // TODO provide a way to run tests in evented I/O mode
616 if (!std.io.is_async) return error.SkipZigTest;
617
618 const allocator = std.heap.page_allocator;
619
620 // TODO move this into event loop too
621 try os.makePath(allocator, test_tmp_dir);
622 defer os.deleteTree(test_tmp_dir) catch {};
623
624 return testFsWatch(&allocator);
625}
626
627fn testFsWatch(allocator: *Allocator) !void {
628 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
629 defer allocator.free(file_path);
630
631 const contents =
632 \\line 1
633 \\line 2
634 ;
635 const line2_offset = 7;
636
637 // first just write then read the file
638 try writeFile(allocator, file_path, contents);
639
640 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
641 testing.expectEqualSlices(u8, contents, read_contents);
642
643 // now watch the file
644 var watch = try Watch(void).init(allocator, 0);
645 defer watch.deinit();
646
647 testing.expect((try watch.addFile(file_path, {})) == null);
648
649 const ev = watch.channel.get();
650 var ev_consumed = false;
651 defer if (!ev_consumed) await ev;
652
653 // overwrite line 2
654 const fd = try await openReadWrite(file_path, File.default_mode);
655 {
656 defer os.close(fd);
657
658 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
659 }
660
661 ev_consumed = true;
662 switch ((try await ev).id) {
663 WatchEventId.CloseWrite => {},
664 WatchEventId.Delete => @panic("wrong event"),
665 }
666 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
667 testing.expectEqualSlices(u8,
668 \\line 1
669 \\lorem ipsum
670 , contents_updated);
671
672 // TODO test deleting the file and then re-adding it. we should get events for both
673}
lib/std/io.zig+13-3
......@@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t {
4747}
4848
4949pub fn getStdOut() File {
50 return File.openHandle(getStdOutHandle());
50 return File{
51 .handle = getStdOutHandle(),
52 .io_mode = .blocking,
53 };
5154}
5255
5356fn getStdErrHandle() os.fd_t {
......@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {
6366}
6467
6568pub fn getStdErr() File {
66 return File.openHandle(getStdErrHandle());
69 return File{
70 .handle = getStdErrHandle(),
71 .io_mode = .blocking,
72 .async_block_allowed = File.async_block_allowed_yes,
73 };
6774}
6875
6976fn getStdInHandle() os.fd_t {
......@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {
7986}
8087
8188pub fn getStdIn() File {
82 return File.openHandle(getStdInHandle());
89 return File{
90 .handle = getStdInHandle(),
91 .io_mode = .blocking,
92 };
8393}
8494
8595pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
lib/std/io/out_stream.zig+10-14
......@@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))
99else
1010 default_stack_size;
1111
12/// TODO this is not integrated with evented I/O yet.
13/// https://github.com/ziglang/zig/issues/3557
1412pub fn OutStream(comptime WriteError: type) type {
1513 return struct {
1614 const Self = @This();
1715 pub const Error = WriteError;
18 // TODO https://github.com/ziglang/zig/issues/3557
19 pub const WriteFn = if (std.io.is_async and false)
16 pub const WriteFn = if (std.io.is_async)
2017 async fn (self: *Self, bytes: []const u8) Error!void
2118 else
2219 fn (self: *Self, bytes: []const u8) Error!void;
......@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {
2421 writeFn: WriteFn,
2522
2623 pub fn write(self: *Self, bytes: []const u8) Error!void {
27 // TODO https://github.com/ziglang/zig/issues/3557
28 if (std.io.is_async and false) {
24 if (std.io.is_async) {
2925 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
3026 @setRuntimeSafety(false);
3127 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
......@@ -40,8 +36,8 @@ pub fn OutStream(comptime WriteError: type) type {
4036 }
4137
4238 pub fn writeByte(self: *Self, byte: u8) Error!void {
43 const slice = @as(*const [1]u8, &byte)[0..];
44 return self.writeFn(self, slice);
39 const array = [1]u8{byte};
40 return self.write(&array);
4541 }
4642
4743 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
......@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {
5147 var remaining: usize = n;
5248 while (remaining > 0) {
5349 const to_write = std.math.min(remaining, bytes.len);
54 try self.writeFn(self, bytes[0..to_write]);
50 try self.write(bytes[0..to_write]);
5551 remaining -= to_write;
5652 }
5753 }
......@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {
6056 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
6157 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
6258 mem.writeIntNative(T, &bytes, value);
63 return self.writeFn(self, &bytes);
59 return self.write(&bytes);
6460 }
6561
6662 /// Write a foreign-endian integer.
6763 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
6864 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
6965 mem.writeIntForeign(T, &bytes, value);
70 return self.writeFn(self, &bytes);
66 return self.write(&bytes);
7167 }
7268
7369 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
7470 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
7571 mem.writeIntLittle(T, &bytes, value);
76 return self.writeFn(self, &bytes);
72 return self.write(&bytes);
7773 }
7874
7975 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
8076 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
8177 mem.writeIntBig(T, &bytes, value);
82 return self.writeFn(self, &bytes);
78 return self.write(&bytes);
8379 }
8480
8581 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
8682 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
8783 mem.writeInt(T, &bytes, value, endian);
88 return self.writeFn(self, &bytes);
84 return self.write(&bytes);
8985 }
9086 };
9187}
lib/std/linked_list.zig+3-6
......@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {
1818
1919 /// Node inside the linked list wrapping the actual data.
2020 pub const Node = struct {
21 next: ?*Node,
21 next: ?*Node = null,
2222 data: T,
2323
2424 pub fn init(data: T) Node {
2525 return Node{
26 .next = null,
2726 .data = data,
2827 };
2928 }
......@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {
196195
197196 /// Node inside the linked list wrapping the actual data.
198197 pub const Node = struct {
199 prev: ?*Node,
200 next: ?*Node,
198 prev: ?*Node = null,
199 next: ?*Node = null,
201200 data: T,
202201
203202 pub fn init(data: T) Node {
204203 return Node{
205 .prev = null,
206 .next = null,
207204 .data = data,
208205 };
209206 }
lib/std/os.zig+179-26
......@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
169169 return error.NoDevice;
170170 }
171171
172 const stream = &std.fs.File.openHandle(fd).inStream().stream;
172 const file = std.fs.File{
173 .handle = fd,
174 .io_mode = .blocking,
175 .async_block_allowed = std.fs.File.async_block_allowed_yes,
176 };
177 const stream = &file.inStream().stream;
173178 stream.readNoEof(buf) catch return error.Unexpected;
174179}
175180
......@@ -293,7 +298,7 @@ pub const ReadError = error{
293298/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
294299pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
295300 if (builtin.os == .windows) {
296 return windows.ReadFile(fd, buf);
301 return windows.ReadFile(fd, buf, null);
297302 }
298303
299304 if (builtin.os == .wasi and !builtin.link_libc) {
......@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
335340}
336341
337342/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
338/// If the application has a global event loop enabled, EAGAIN is handled
339/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
343///
344/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
345/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
346/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
347/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
348///
349/// This operation is non-atomic on the following systems:
350/// * Windows
351/// On these systems, the read races with concurrent writes to the same file descriptor.
340352pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
353 if (builtin.os == .windows) {
354 // TODO batch these into parallel requests
355 var off: usize = 0;
356 var iov_i: usize = 0;
357 var inner_off: usize = 0;
358 while (true) {
359 const v = iov[iov_i];
360 const amt_read = try read(fd, v.iov_base[inner_off .. v.iov_len - inner_off]);
361 off += amt_read;
362 inner_off += amt_read;
363 if (inner_off == v.len) {
364 iov_i += 1;
365 inner_off = 0;
366 if (iov_i == iov.len) {
367 return off;
368 }
369 }
370 if (amt_read == 0) return off; // EOF
371 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
372 }
373
341374 while (true) {
342375 // TODO handle the case when iov_len is too large and get rid of this @intCast
343376 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));
......@@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
363396}
364397
365398/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
366/// If the application has a global event loop enabled, EAGAIN is handled
367/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
399///
400/// Retries when interrupted by a signal.
401///
402/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
403/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
404/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
405/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
406pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
407 if (builtin.os == .windows) {
408 return windows.ReadFile(fd, buf, offset);
409 }
410
411 while (true) {
412 const rc = system.pread(fd, buf.ptr, buf.len, offset);
413 switch (errno(rc)) {
414 0 => return @intCast(usize, rc),
415 EINTR => continue,
416 EINVAL => unreachable,
417 EFAULT => unreachable,
418 EAGAIN => if (std.event.Loop.instance) |loop| {
419 loop.waitUntilFdReadable(fd);
420 continue;
421 } else {
422 return error.WouldBlock;
423 },
424 EBADF => unreachable, // Always a race condition.
425 EIO => return error.InputOutput,
426 EISDIR => return error.IsDir,
427 ENOBUFS => return error.SystemResources,
428 ENOMEM => return error.SystemResources,
429 ECONNRESET => return error.ConnectionResetByPeer,
430 else => |err| return unexpectedErrno(err),
431 }
432 }
433 return index;
434}
435
436/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
437///
438/// Retries when interrupted by a signal.
439///
440/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
441/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
442/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
443/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
444///
445/// This operation is non-atomic on the following systems:
446/// * Darwin
447/// * Windows
448/// On these systems, the read races with concurrent writes to the same file descriptor.
368449pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
369450 if (comptime std.Target.current.isDarwin()) {
370451 // Darwin does not have preadv but it does have pread.
......@@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
409490 }
410491 }
411492 }
493
494 if (builtin.os == .windows) {
495 // TODO batch these into parallel requests
496 var off: usize = 0;
497 var iov_i: usize = 0;
498 var inner_off: usize = 0;
499 while (true) {
500 const v = iov[iov_i];
501 const amt_read = try pread(fd, v.iov_base[inner_off .. v.iov_len - inner_off], offset + off);
502 off += amt_read;
503 inner_off += amt_read;
504 if (inner_off == v.len) {
505 iov_i += 1;
506 inner_off = 0;
507 if (iov_i == iov.len) {
508 return off;
509 }
510 }
511 if (amt_read == 0) return off; // EOF
512 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
513 }
514
412515 while (true) {
413516 // TODO handle the case when iov_len is too large and get rid of this @intCast
414517 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
......@@ -451,11 +554,9 @@ pub const WriteError = error{
451554/// Write to a file descriptor. Keeps trying if it gets interrupted.
452555/// If the application has a global event loop enabled, EAGAIN is handled
453556/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
454/// TODO evented I/O integration is disabled until
455/// https://github.com/ziglang/zig/issues/3557 is solved.
456557pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
457558 if (builtin.os == .windows) {
458 return windows.WriteFile(fd, bytes);
559 return windows.WriteFile(fd, bytes, null);
459560 }
460561
461562 if (builtin.os == .wasi and !builtin.link_libc) {
......@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
488589 EINTR => continue,
489590 EINVAL => unreachable,
490591 EFAULT => unreachable,
491 // TODO https://github.com/ziglang/zig/issues/3557
492 EAGAIN => return error.WouldBlock,
493 //EAGAIN => if (std.event.Loop.instance) |loop| {
494 // loop.waitUntilFdWritable(fd);
495 // continue;
496 //} else {
497 // return error.WouldBlock;
498 //},
592 EAGAIN => if (std.event.Loop.instance) |loop| {
593 loop.waitUntilFdWritable(fd);
594 continue;
595 } else {
596 return error.WouldBlock;
597 },
499598 EBADF => unreachable, // Always a race condition.
500599 EDESTADDRREQ => unreachable, // `connect` was never called.
501600 EDQUOT => return error.DiskQuota,
......@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
540639 }
541640}
542641
642/// Write to a file descriptor, with a position offset.
643///
644/// Retries when interrupted by a signal.
645///
646/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
647/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
648/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
649/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
650pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {
651 if (comptime std.Target.current.isWindows()) {
652 return windows.WriteFile(fd, bytes, offset);
653 }
654
655 while (true) {
656 const rc = system.pwrite(fd, bytes.ptr, bytes.len, offset);
657 switch (errno(rc)) {
658 0 => return,
659 EINTR => continue,
660 EINVAL => unreachable,
661 EFAULT => unreachable,
662 EAGAIN => if (std.event.Loop.instance) |loop| {
663 loop.waitUntilFdWritable(fd);
664 continue;
665 } else {
666 return error.WouldBlock;
667 },
668 EBADF => unreachable, // Always a race condition.
669 EDESTADDRREQ => unreachable, // `connect` was never called.
670 EDQUOT => return error.DiskQuota,
671 EFBIG => return error.FileTooBig,
672 EIO => return error.InputOutput,
673 ENOSPC => return error.NoSpaceLeft,
674 EPERM => return error.AccessDenied,
675 EPIPE => return error.BrokenPipe,
676 else => |err| return unexpectedErrno(err),
677 }
678 }
679}
680
543681/// Write multiple buffers to a file descriptor, with a position offset.
544/// Keeps trying if it gets interrupted.
682///
683/// Retries when interrupted by a signal.
684///
685/// If the application has a global event loop enabled, EAGAIN is handled
686/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
687///
688/// This operation is non-atomic on the following systems:
689/// * Darwin
690/// * Windows
691/// On these systems, the write races with concurrent writes to the same file descriptor, and
692/// the file can be in a partially written state when an error occurs.
545693pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
546694 if (comptime std.Target.current.isDarwin()) {
547695 // Darwin does not have pwritev but it does have pwrite.
......@@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
589737 }
590738 }
591739
740 if (comptime std.Target.current.isWindows()) {
741 var off = offset;
742 for (iov) |item| {
743 try pwrite(fd, item.iov_base[0..item.iov_len], off);
744 off += buf.len;
745 }
746 return;
747 }
748
592749 while (true) {
593750 // TODO handle the case when iov_len is too large and get rid of this @intCast
594751 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);
......@@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
694851/// Open and possibly create a file. Keeps trying if it gets interrupted.
695852/// `file_path` is relative to the open directory handle `dir_fd`.
696853/// See also `openatC`.
697pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t {
854pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
698855 const file_path_c = try toPosixPath(file_path);
699856 return openatC(dir_fd, &file_path_c, flags, mode);
700857}
......@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open
702859/// Open and possibly create a file. Keeps trying if it gets interrupted.
703860/// `file_path` is relative to the open directory handle `dir_fd`.
704861/// See also `openat`.
705pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) OpenError!fd_t {
862pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
706863 while (true) {
707864 const rc = system.openat(dir_fd, file_path, flags, mode);
708865 switch (errno(rc)) {
......@@ -3328,9 +3485,7 @@ pub fn getrusage(who: i32) rusage {
33283485 }
33293486}
33303487
3331pub const TermiosGetError = error{
3332 NotATerminal,
3333} || UnexpectedError;
3488pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
33343489
33353490pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
33363491 var term: termios = undefined;
......@@ -3342,9 +3497,7 @@ pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
33423497 }
33433498}
33443499
3345pub const TermiosSetError = TermiosGetError || error{
3346 ProcessOrphaned,
3347};
3500pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned};
33483501
33493502pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void {
33503503 while (true) {
lib/std/os/bits/darwin.zig+1
......@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;
44
55pub const fd_t = c_int;
66pub const pid_t = c_int;
7pub const mode_t = c_uint;
78
89pub const in_port_t = u16;
910pub const sa_family_t = u8;
lib/std/os/bits/dragonfly.zig+1
......@@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool {
77pub const fd_t = c_int;
88pub const pid_t = c_int;
99pub const off_t = c_long;
10pub const mode_t = c_uint;
1011
1112pub const ENOTSUP = EOPNOTSUPP;
1213pub const EWOULDBLOCK = EAGAIN;
lib/std/os/bits/freebsd.zig+1
......@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
44pub const fd_t = c_int;
55pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
78pub const socklen_t = u32;
89
lib/std/os/bits/linux/x86_64.zig+2
......@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;
1212const iovec = linux.iovec;
1313const iovec_const = linux.iovec_const;
1414
15pub const mode_t = usize;
16
1517pub const SYS_read = 0;
1618pub const SYS_write = 1;
1719pub const SYS_open = 2;
lib/std/os/bits/netbsd.zig+1
......@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
44pub const fd_t = c_int;
55pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
78/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.
89pub const Kevent = extern struct {
lib/std/os/bits/wasi.zig+1
......@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;
130130pub const exitcode_t = u32;
131131
132132pub const fd_t = u32;
133pub const mode_t = u32;
133134
134135pub const fdflags_t = u16;
135136pub const FDFLAG_APPEND: fdflags_t = 0x0001;
lib/std/os/bits/windows.zig+1
......@@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig");
55
66pub const fd_t = HANDLE;
77pub const pid_t = HANDLE;
8pub const mode_t = u0;
89
910pub const PATH_MAX = 260;
1011
lib/std/os/windows.zig+128-29
......@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {
344344 assert(kernel32.FindClose(hFindFile) != 0);
345345}
346346
347pub const ReadFileError = error{Unexpected};
348
349pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize {
350 var index: usize = 0;
351 while (index < buffer.len) {
352 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));
353 var amt_read: DWORD = undefined;
354 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {
355 switch (kernel32.GetLastError()) {
356 .OPERATION_ABORTED => continue,
357 .BROKEN_PIPE => return index,
358 else => |err| return unexpectedError(err),
347pub const ReadFileError = error{
348 OperationAborted,
349 BrokenPipe,
350 Unexpected,
351};
352
353/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
354/// multiple non-atomic reads.
355pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
356 if (std.event.Loop.instance) |loop| {
357 // TODO support async ReadFile with no offset
358 const off = offset.?;
359 var resume_node = std.event.Loop.ResumeNode.Basic{
360 .base = .{
361 .id = .Basic,
362 .handle = @frame(),
363 .overlapped = OVERLAPPED{
364 .Internal = 0,
365 .InternalHigh = 0,
366 .Offset = @truncate(u32, off),
367 .OffsetHigh = @truncate(u32, off >> 32),
368 .hEvent = null,
369 },
370 },
371 };
372 // TODO only call create io completion port once per fd
373 _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined;
374 loop.beginOneEvent();
375 suspend {
376 // TODO handle buffer bigger than DWORD can hold
377 _ = windows.kernel32.ReadFile(fd, buffer.ptr, @intCast(windows.DWORD, buffer.len), null, &resume_node.base.overlapped);
378 }
379 var bytes_transferred: windows.DWORD = undefined;
380 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
381 switch (windows.kernel32.GetLastError()) {
382 .IO_PENDING => unreachable,
383 .OPERATION_ABORTED => return error.OperationAborted,
384 .BROKEN_PIPE => return error.BrokenPipe,
385 .HANDLE_EOF => return @as(usize, bytes_transferred),
386 else => |err| return windows.unexpectedError(err),
387 }
388 }
389 return @as(usize, bytes_transferred);
390 } else {
391 var index: usize = 0;
392 while (index < buffer.len) {
393 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));
394 var amt_read: DWORD = undefined;
395 var overlapped_data: OVERLAPPED = undefined;
396 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
397 overlapped_data = .{
398 .Internal = 0,
399 .InternalHigh = 0,
400 .Offset = @truncate(u32, off + index),
401 .OffsetHigh = @truncate(u32, (off + index) >> 32),
402 .hEvent = null,
403 };
404 break :blk &overlapped_data;
405 } else null;
406 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, overlapped) == 0) {
407 switch (kernel32.GetLastError()) {
408 .OPERATION_ABORTED => continue,
409 .BROKEN_PIPE => return index,
410 else => |err| return unexpectedError(err),
411 }
359412 }
413 if (amt_read == 0) return index;
414 index += amt_read;
360415 }
361 if (amt_read == 0) return index;
362 index += amt_read;
416 return index;
363417 }
364 return index;
365418}
366419
367420pub const WriteFileError = error{
......@@ -371,20 +424,66 @@ pub const WriteFileError = error{
371424 Unexpected,
372425};
373426
374/// This function is for blocking file descriptors only. For non-blocking, see
375/// `WriteFileAsync`.
376pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void {
377 var bytes_written: DWORD = undefined;
378 // TODO replace this @intCast with a loop that writes all the bytes
379 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {
380 switch (kernel32.GetLastError()) {
381 .INVALID_USER_BUFFER => return error.SystemResources,
382 .NOT_ENOUGH_MEMORY => return error.SystemResources,
383 .OPERATION_ABORTED => return error.OperationAborted,
384 .NOT_ENOUGH_QUOTA => return error.SystemResources,
385 .IO_PENDING => unreachable, // this function is for blocking files only
386 .BROKEN_PIPE => return error.BrokenPipe,
387 else => |err| return unexpectedError(err),
427pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void {
428 if (std.event.Loop.instance) |loop| {
429 // TODO support async WriteFile with no offset
430 const off = offset.?;
431 var resume_node = std.event.Loop.ResumeNode.Basic{
432 .base = .{
433 .id = .Basic,
434 .handle = @frame(),
435 .overlapped = OVERLAPPED{
436 .Internal = 0,
437 .InternalHigh = 0,
438 .Offset = @truncate(u32, off),
439 .OffsetHigh = @truncate(u32, off >> 32),
440 .hEvent = null,
441 },
442 },
443 };
444 // TODO only call create io completion port once per fd
445 _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);
446 loop.beginOneEvent();
447 suspend {
448 // TODO replace this @intCast with a loop that writes all the bytes
449 _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped);
450 }
451 var bytes_transferred: windows.DWORD = undefined;
452 if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
453 switch (kernel32.GetLastError()) {
454 .IO_PENDING => unreachable,
455 .INVALID_USER_BUFFER => return error.SystemResources,
456 .NOT_ENOUGH_MEMORY => return error.SystemResources,
457 .OPERATION_ABORTED => return error.OperationAborted,
458 .NOT_ENOUGH_QUOTA => return error.SystemResources,
459 .BROKEN_PIPE => return error.BrokenPipe,
460 else => |err| return windows.unexpectedError(err),
461 }
462 }
463 } else {
464 var bytes_written: DWORD = undefined;
465 var overlapped_data: OVERLAPPED = undefined;
466 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
467 overlapped_data = .{
468 .Internal = 0,
469 .InternalHigh = 0,
470 .Offset = @truncate(u32, off),
471 .OffsetHigh = @truncate(u32, off >> 32),
472 .hEvent = null,
473 };
474 break :blk &overlapped_data;
475 } else null;
476 // TODO replace this @intCast with a loop that writes all the bytes
477 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) {
478 switch (kernel32.GetLastError()) {
479 .INVALID_USER_BUFFER => return error.SystemResources,
480 .NOT_ENOUGH_MEMORY => return error.SystemResources,
481 .OPERATION_ABORTED => return error.OperationAborted,
482 .NOT_ENOUGH_QUOTA => return error.SystemResources,
483 .IO_PENDING => unreachable, // this function is for blocking files only
484 .BROKEN_PIPE => return error.BrokenPipe,
485 else => |err| return unexpectedError(err),
486 }
388487 }
389488 }
390489}
lib/std/special/test_runner.zig+24-2
......@@ -2,7 +2,7 @@ const std = @import("std");
22const io = std.io;
33const builtin = @import("builtin");
44
5pub const io_mode = builtin.test_io_mode;
5pub const io_mode: io.Mode = builtin.test_io_mode;
66
77pub fn main() anyerror!void {
88 const test_fn_list = builtin.test_functions;
......@@ -14,6 +14,11 @@ pub fn main() anyerror!void {
1414 error.TimerUnsupported => @panic("timer unsupported"),
1515 };
1616
17 var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined;
18 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
19 // ignores the alignment of the slice.
20 async_frame_buffer = &[_]u8{};
21
1722 for (test_fn_list) |test_fn, i| {
1823 std.testing.base_allocator_instance.reset();
1924
......@@ -23,7 +28,24 @@ pub fn main() anyerror!void {
2328 if (progress.terminal == null) {
2429 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });
2530 }
26 if (test_fn.func()) |_| {
31 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
32 .evented => blk: {
33 if (async_frame_buffer.len < size) {
34 std.heap.page_allocator.free(async_frame_buffer);
35 async_frame_buffer = try std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size);
36 }
37 const casted_fn = @ptrCast(async fn () anyerror!void, test_fn.func);
38 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn);
39 },
40 .blocking => {
41 skip_count += 1;
42 test_node.end();
43 progress.log("{}...SKIP (async test)\n", .{test_fn.name});
44 if (progress.terminal == null) std.debug.warn("SKIP (async test)\n", .{});
45 continue;
46 },
47 } else test_fn.func();
48 if (result) |_| {
2749 ok_count += 1;
2850 test_node.end();
2951 std.testing.allocator_instance.validate() catch |err| switch (err) {
src/analyze.cpp+16-4
......@@ -6144,6 +6144,15 @@ static bool scope_needs_spill(Scope *scope) {
61446144 zig_unreachable();
61456145}
61466146
6147static ZigType *resolve_type_isf(ZigType *ty) {
6148 if (ty->id != ZigTypeIdPointer) return ty;
6149 InferredStructField *isf = ty->data.pointer.inferred_struct_field;
6150 if (isf == nullptr) return ty;
6151 TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
6152 assert(field != nullptr);
6153 return field->type_entry;
6154}
6155
61476156static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
61486157 Error err;
61496158
......@@ -6245,6 +6254,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
62456254 }
62466255 ZigFn *callee = call->fn_entry;
62476256 if (callee == nullptr) {
6257 if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) {
6258 continue;
6259 }
62486260 add_node_error(g, call->base.base.source_node,
62496261 buf_sprintf("function is not comptime-known; @asyncCall required"));
62506262 return ErrorSemanticAnalyzeFail;
......@@ -6402,7 +6414,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64026414 } else {
64036415 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);
64046416 }
6405 ZigType *param_type = param_info->type;
6417 ZigType *param_type = resolve_type_isf(param_info->type);
64066418 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {
64076419 return err;
64086420 }
......@@ -6421,7 +6433,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64216433 instruction->field_index = SIZE_MAX;
64226434 ZigType *ptr_type = instruction->base.value->type;
64236435 assert(ptr_type->id == ZigTypeIdPointer);
6424 ZigType *child_type = ptr_type->data.pointer.child_type;
6436 ZigType *child_type = resolve_type_isf(ptr_type->data.pointer.child_type);
64256437 if (!type_has_bits(child_type))
64266438 continue;
64276439 if (instruction->base.base.ref_count == 0)
......@@ -6448,8 +6460,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64486460 }
64496461 instruction->field_index = fields.length;
64506462
6451 src_assert(child_type->id != ZigTypeIdPointer || child_type->data.pointer.inferred_struct_field == nullptr,
6452 instruction->base.base.source_node);
64536463 fields.append({name, child_type, instruction->align});
64546464 }
64556465
......@@ -8251,6 +8261,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
82518261 size_t debug_field_index = 0;
82528262 for (size_t i = 0; i < field_count; i += 1) {
82538263 TypeStructField *field = struct_type->data.structure.fields[i];
8264 //fprintf(stderr, "%s at gen index %zu\n", buf_ptr(field->name), field->gen_index);
8265
82548266 size_t gen_field_index = field->gen_index;
82558267 if (gen_field_index == SIZE_MAX) {
82568268 continue;
src/codegen.cpp+24-18
......@@ -362,14 +362,16 @@ static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) {
362362}
363363
364364// label (grep this): [fn_frame_struct_layout]
365static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) {
366 uint32_t result = frame_index_arg(g, fn_type_id->return_type);
367 for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
368 if (type_has_bits(fn_type_id->param_info->type)) {
369 result += 1;
370 }
365static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) {
366 size_t field_index = 6;
367 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type);
368 if (have_stack_trace) {
369 field_index += 2;
371370 }
372 return result;
371 field_index += fn->type_entry->data.fn.fn_type_id.param_count;
372 ZigType *locals_struct = fn->frame_type->data.frame.locals_struct;
373 TypeStructField *field = locals_struct->data.structure.fields[field_index];
374 return field->gen_index;
373375}
374376
375377
......@@ -7742,7 +7744,7 @@ static void do_code_gen(CodeGen *g) {
77427744 }
77437745 uint32_t trace_field_index_stack = UINT32_MAX;
77447746 if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) {
7745 trace_field_index_stack = frame_index_trace_stack(g, fn_type_id);
7747 trace_field_index_stack = frame_index_trace_stack(g, fn_table_entry);
77467748 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
77477749 trace_field_index_stack, "");
77487750 }
......@@ -9396,22 +9398,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
93969398 for (size_t i = 0; i < g->test_fns.length; i += 1) {
93979399 ZigFn *test_fn_entry = g->test_fns.at(i);
93989400
9399 if (fn_is_async(test_fn_entry)) {
9400 ErrorMsg *msg = add_node_error(g, test_fn_entry->proto_node,
9401 buf_create_from_str("test functions cannot be async"));
9402 add_error_note(g, msg, test_fn_entry->proto_node,
9403 buf_sprintf("this restriction may be lifted in the future. See https://github.com/ziglang/zig/issues/3117 for more details"));
9404 add_async_error_notes(g, msg, test_fn_entry);
9405 continue;
9406 }
9407
94089401 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];
94099402 this_val->special = ConstValSpecialStatic;
94109403 this_val->type = struct_type;
94119404 this_val->parent.id = ConstParentIdArray;
94129405 this_val->parent.data.p_array.array_val = test_fn_array;
94139406 this_val->parent.data.p_array.elem_index = i;
9414 this_val->data.x_struct.fields = alloc_const_vals_ptrs(2);
9407 this_val->data.x_struct.fields = alloc_const_vals_ptrs(3);
94159408
94169409 ZigValue *name_field = this_val->data.x_struct.fields[0];
94179410 ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee;
......@@ -9423,6 +9416,19 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
94239416 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;
94249417 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;
94259418 fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry;
9419
9420 ZigValue *frame_size_field = this_val->data.x_struct.fields[2];
9421 frame_size_field->type = get_optional_type(g, g->builtin_types.entry_usize);
9422 frame_size_field->special = ConstValSpecialStatic;
9423 frame_size_field->data.x_optional = nullptr;
9424
9425 if (fn_is_async(test_fn_entry)) {
9426 frame_size_field->data.x_optional = create_const_vals(1);
9427 frame_size_field->data.x_optional->special = ConstValSpecialStatic;
9428 frame_size_field->data.x_optional->type = g->builtin_types.entry_usize;
9429 bigint_init_unsigned(&frame_size_field->data.x_optional->data.x_bigint,
9430 test_fn_entry->frame_type->abi_size);
9431 }
94269432 }
94279433 report_errors_and_maybe_exit(g);
94289434