| author | |
| committer | |
| log | 0b5bcd2f56a84e66d5c700744ec1838381893667 |
| tree | 5b640a57055e50636fe7a9f782915b2126395ef3 |
| parent | 704cd977bdcdfa8cff4e70aaad93857d9b622fc7 |
| signature |
* `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 { | ... | @@ -458,6 +458,7 @@ pub const ExportOptions = struct { |
| 458 | pub const TestFn = struct { | 458 | pub const TestFn = struct { |
| 459 | name: []const u8, | 459 | name: []const u8, |
| 460 | func: fn () anyerror!void, | 460 | func: fn () anyerror!void, |
| 461 | async_frame_size: ?usize, | ||
| 461 | }; | 462 | }; |
| 462 | 463 | ||
| 463 | /// This function type is used by the Zig language code generation and | 464 | /// 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 { | ... | @@ -329,17 +329,18 @@ pub const ChildProcess = struct { |
| 329 | } | 329 | } |
| 330 | 330 | ||
| 331 | fn spawnPosix(self: *ChildProcess) SpawnError!void { | 331 | 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; | ||
| 333 | errdefer if (self.stdin_behavior == StdIo.Pipe) { | 334 | errdefer if (self.stdin_behavior == StdIo.Pipe) { |
| 334 | destroyPipe(stdin_pipe); | 335 | destroyPipe(stdin_pipe); |
| 335 | }; | 336 | }; |
| 336 | 337 | ||
| 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; |
| 338 | errdefer if (self.stdout_behavior == StdIo.Pipe) { | 339 | errdefer if (self.stdout_behavior == StdIo.Pipe) { |
| 339 | destroyPipe(stdout_pipe); | 340 | destroyPipe(stdout_pipe); |
| 340 | }; | 341 | }; |
| 341 | 342 | ||
| 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; |
| 343 | errdefer if (self.stderr_behavior == StdIo.Pipe) { | 344 | errdefer if (self.stderr_behavior == StdIo.Pipe) { |
| 344 | destroyPipe(stderr_pipe); | 345 | destroyPipe(stderr_pipe); |
| 345 | }; | 346 | }; |
| ... | @@ -426,17 +427,26 @@ pub const ChildProcess = struct { | ... | @@ -426,17 +427,26 @@ pub const ChildProcess = struct { |
| 426 | // we are the parent | 427 | // we are the parent |
| 427 | const pid = @intCast(i32, pid_result); | 428 | const pid = @intCast(i32, pid_result); |
| 428 | if (self.stdin_behavior == StdIo.Pipe) { | 429 | 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 | }; | ||
| 430 | } else { | 434 | } else { |
| 431 | self.stdin = null; | 435 | self.stdin = null; |
| 432 | } | 436 | } |
| 433 | if (self.stdout_behavior == StdIo.Pipe) { | 437 | 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 | }; | ||
| 435 | } else { | 442 | } else { |
| 436 | self.stdout = null; | 443 | self.stdout = null; |
| 437 | } | 444 | } |
| 438 | if (self.stderr_behavior == StdIo.Pipe) { | 445 | 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 | }; | ||
| 440 | } else { | 450 | } else { |
| 441 | self.stderr = null; | 451 | self.stderr = null; |
| 442 | } | 452 | } |
| ... | @@ -661,17 +671,26 @@ pub const ChildProcess = struct { | ... | @@ -661,17 +671,26 @@ pub const ChildProcess = struct { |
| 661 | }; | 671 | }; |
| 662 | 672 | ||
| 663 | if (g_hChildStd_IN_Wr) |h| { | 673 | 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 | }; | ||
| 665 | } else { | 678 | } else { |
| 666 | self.stdin = null; | 679 | self.stdin = null; |
| 667 | } | 680 | } |
| 668 | if (g_hChildStd_OUT_Rd) |h| { | 681 | 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 | }; | ||
| 670 | } else { | 686 | } else { |
| 671 | self.stdout = null; | 687 | self.stdout = null; |
| 672 | } | 688 | } |
| 673 | if (g_hChildStd_ERR_Rd) |h| { | 689 | 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 | }; | ||
| 675 | } else { | 694 | } else { |
| 676 | self.stderr = null; | 695 | self.stderr = null; |
| 677 | } | 696 | } |
| ... | @@ -693,10 +712,10 @@ pub const ChildProcess = struct { | ... | @@ -693,10 +712,10 @@ pub const ChildProcess = struct { |
| 693 | 712 | ||
| 694 | fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { | 713 | fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { |
| 695 | switch (stdio) { | 714 | switch (stdio) { |
| 696 | StdIo.Pipe => try os.dup2(pipe_fd, std_fileno), | 715 | .Pipe => try os.dup2(pipe_fd, std_fileno), |
| 697 | StdIo.Close => os.close(std_fileno), | 716 | .Close => os.close(std_fileno), |
| 698 | StdIo.Inherit => {}, | 717 | .Inherit => {}, |
| 699 | StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno), | 718 | .Ignore => try os.dup2(dev_null_fd, std_fileno), |
| 700 | } | 719 | } |
| 701 | } | 720 | } |
| 702 | }; | 721 | }; |
| ... | @@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { | ... | @@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { |
| 811 | const ErrInt = @IntType(false, @sizeOf(anyerror) * 8); | 830 | const ErrInt = @IntType(false, @sizeOf(anyerror) * 8); |
| 812 | 831 | ||
| 813 | fn writeIntFd(fd: i32, value: ErrInt) !void { | 832 | fn 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; | ||
| 815 | stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; | 839 | stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; |
| 816 | } | 840 | } |
| 817 | 841 | ||
| 818 | fn readIntFd(fd: i32) !ErrInt { | 842 | fn 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; | ||
| 820 | return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources); | 849 | return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources); |
| 821 | } | 850 | } |
| 822 | 851 |
lib/std/debug.zig+24-24| ... | @@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void { | ... | @@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void { |
| 50 | const held = stderr_mutex.acquire(); | 50 | const held = stderr_mutex.acquire(); |
| 51 | defer held.release(); | 51 | defer held.release(); |
| 52 | const stderr = getStderrStream(); | 52 | const stderr = getStderrStream(); |
| 53 | stderr.print(fmt, args) catch return; | 53 | noasync stderr.print(fmt, args) catch return; |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | pub fn getStderrStream() *io.OutStream(File.WriteError) { | 56 | pub fn getStderrStream() *io.OutStream(File.WriteError) { |
| ... | @@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config { | ... | @@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config { |
| 102 | pub fn dumpCurrentStackTrace(start_addr: ?usize) void { | 102 | pub fn dumpCurrentStackTrace(start_addr: ?usize) void { |
| 103 | const stderr = getStderrStream(); | 103 | const stderr = getStderrStream(); |
| 104 | if (builtin.strip_debug_info) { | 104 | 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; |
| 106 | return; | 106 | return; |
| 107 | } | 107 | } |
| 108 | const debug_info = getSelfDebugInfo() catch |err| { | 108 | 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; |
| 110 | return; | 110 | return; |
| 111 | }; | 111 | }; |
| 112 | writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| { | 112 | 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; |
| 114 | return; | 114 | return; |
| 115 | }; | 115 | }; |
| 116 | } | 116 | } |
| ... | @@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { | ... | @@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { |
| 121 | pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { | 121 | pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { |
| 122 | const stderr = getStderrStream(); | 122 | const stderr = getStderrStream(); |
| 123 | if (builtin.strip_debug_info) { | 123 | 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; |
| 125 | return; | 125 | return; |
| 126 | } | 126 | } |
| 127 | const debug_info = getSelfDebugInfo() catch |err| { | 127 | 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; |
| 129 | return; | 129 | return; |
| 130 | }; | 130 | }; |
| 131 | const tty_config = detectTTYConfig(); | 131 | const tty_config = detectTTYConfig(); |
| ... | @@ -195,15 +195,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace | ... | @@ -195,15 +195,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace |
| 195 | pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { | 195 | pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { |
| 196 | const stderr = getStderrStream(); | 196 | const stderr = getStderrStream(); |
| 197 | if (builtin.strip_debug_info) { | 197 | 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; |
| 199 | return; | 199 | return; |
| 200 | } | 200 | } |
| 201 | const debug_info = getSelfDebugInfo() catch |err| { | 201 | 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; |
| 203 | return; | 203 | return; |
| 204 | }; | 204 | }; |
| 205 | writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| { | 205 | 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; |
| 207 | return; | 207 | return; |
| 208 | }; | 208 | }; |
| 209 | } | 209 | } |
| ... | @@ -244,7 +244,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c | ... | @@ -244,7 +244,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 244 | switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) { | 244 | switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) { |
| 245 | 0 => { | 245 | 0 => { |
| 246 | const stderr = getStderrStream(); | 246 | const stderr = getStderrStream(); |
| 247 | stderr.print(format ++ "\n", args) catch os.abort(); | 247 | noasync stderr.print(format ++ "\n", args) catch os.abort(); |
| 248 | if (trace) |t| { | 248 | if (trace) |t| { |
| 249 | dumpStackTrace(t.*); | 249 | dumpStackTrace(t.*); |
| 250 | } | 250 | } |
| ... | @@ -556,12 +556,12 @@ pub const TTY = struct { | ... | @@ -556,12 +556,12 @@ pub const TTY = struct { |
| 556 | switch (conf) { | 556 | switch (conf) { |
| 557 | .no_color => return, | 557 | .no_color => return, |
| 558 | .escape_codes => switch (color) { | 558 | .escape_codes => switch (color) { |
| 559 | .Red => out_stream.write(RED) catch return, | 559 | .Red => noasync out_stream.write(RED) catch return, |
| 560 | .Green => out_stream.write(GREEN) catch return, | 560 | .Green => noasync out_stream.write(GREEN) catch return, |
| 561 | .Cyan => out_stream.write(CYAN) catch return, | 561 | .Cyan => noasync out_stream.write(CYAN) catch return, |
| 562 | .White, .Bold => out_stream.write(WHITE) catch return, | 562 | .White, .Bold => noasync out_stream.write(WHITE) catch return, |
| 563 | .Dim => out_stream.write(DIM) catch return, | 563 | .Dim => noasync out_stream.write(DIM) catch return, |
| 564 | .Reset => out_stream.write(RESET) catch return, | 564 | .Reset => noasync out_stream.write(RESET) catch return, |
| 565 | }, | 565 | }, |
| 566 | .windows_api => if (builtin.os == .windows) { | 566 | .windows_api => if (builtin.os == .windows) { |
| 567 | const S = struct { | 567 | const S = struct { |
| ... | @@ -717,17 +717,17 @@ fn printLineInfo( | ... | @@ -717,17 +717,17 @@ fn printLineInfo( |
| 717 | tty_config.setColor(out_stream, .White); | 717 | tty_config.setColor(out_stream, .White); |
| 718 | 718 | ||
| 719 | if (line_info) |*li| { | 719 | 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 }); |
| 721 | } else { | 721 | } else { |
| 722 | try out_stream.print("???:?:?", .{}); | 722 | try noasync out_stream.write("???:?:?"); |
| 723 | } | 723 | } |
| 724 | 724 | ||
| 725 | tty_config.setColor(out_stream, .Reset); | 725 | tty_config.setColor(out_stream, .Reset); |
| 726 | try out_stream.write(": "); | 726 | try noasync out_stream.write(": "); |
| 727 | tty_config.setColor(out_stream, .Dim); | 727 | 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 }); |
| 729 | tty_config.setColor(out_stream, .Reset); | 729 | tty_config.setColor(out_stream, .Reset); |
| 730 | try out_stream.write("\n"); | 730 | try noasync out_stream.write("\n"); |
| 731 | 731 | ||
| 732 | // Show the matching source code line if possible | 732 | // Show the matching source code line if possible |
| 733 | if (line_info) |li| { | 733 | if (line_info) |li| { |
| ... | @@ -736,12 +736,12 @@ fn printLineInfo( | ... | @@ -736,12 +736,12 @@ fn printLineInfo( |
| 736 | // The caret already takes one char | 736 | // The caret already takes one char |
| 737 | const space_needed = @intCast(usize, li.column - 1); | 737 | const space_needed = @intCast(usize, li.column - 1); |
| 738 | 738 | ||
| 739 | try out_stream.writeByteNTimes(' ', space_needed); | 739 | try noasync out_stream.writeByteNTimes(' ', space_needed); |
| 740 | tty_config.setColor(out_stream, .Green); | 740 | tty_config.setColor(out_stream, .Green); |
| 741 | try out_stream.write("^"); | 741 | try noasync out_stream.write("^"); |
| 742 | tty_config.setColor(out_stream, .Reset); | 742 | tty_config.setColor(out_stream, .Reset); |
| 743 | } | 743 | } |
| 744 | try out_stream.write("\n"); | 744 | try noasync out_stream.write("\n"); |
| 745 | } else |err| switch (err) { | 745 | } else |err| switch (err) { |
| 746 | error.EndOfFile, error.FileNotFound => {}, | 746 | error.EndOfFile, error.FileNotFound => {}, |
| 747 | error.BadPathName => {}, | 747 | error.BadPathName => {}, |
lib/std/event.zig-2| ... | @@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked; | ... | @@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked; |
| 6 | pub const RwLock = @import("event/rwlock.zig").RwLock; | 6 | pub const RwLock = @import("event/rwlock.zig").RwLock; |
| 7 | pub const RwLocked = @import("event/rwlocked.zig").RwLocked; | 7 | pub const RwLocked = @import("event/rwlocked.zig").RwLocked; |
| 8 | pub const Loop = @import("event/loop.zig").Loop; | 8 | pub const Loop = @import("event/loop.zig").Loop; |
| 9 | pub const fs = @import("event/fs.zig"); | ||
| 10 | 9 | ||
| 11 | test "import event tests" { | 10 | test "import event tests" { |
| 12 | _ = @import("event/channel.zig"); | 11 | _ = @import("event/channel.zig"); |
| 13 | _ = @import("event/fs.zig"); | ||
| 14 | _ = @import("event/future.zig"); | 12 | _ = @import("event/future.zig"); |
| 15 | _ = @import("event/group.zig"); | 13 | _ = @import("event/group.zig"); |
| 16 | _ = @import("event/lock.zig"); | 14 | _ = @import("event/lock.zig"); |
lib/std/event/fs.zig deleted-1418| ... | @@ -1,1418 +0,0 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const std = @import("../std.zig"); | ||
| 3 | const event = std.event; | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const testing = std.testing; | ||
| 6 | const os = std.os; | ||
| 7 | const mem = std.mem; | ||
| 8 | const windows = os.windows; | ||
| 9 | const Loop = event.Loop; | ||
| 10 | const fd_t = os.fd_t; | ||
| 11 | const File = std.fs.File; | ||
| 12 | const Allocator = mem.Allocator; | ||
| 13 | |||
| 14 | //! TODO mege this with `std.fs` | ||
| 15 | |||
| 16 | const global_event_loop = Loop.instance orelse | ||
| 17 | @compileError("std.event.fs currently only works with event-based I/O"); | ||
| 18 | |||
| 19 | pub const RequestNode = std.atomic.Queue(Request).Node; | ||
| 20 | |||
| 21 | pub 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 | |||
| 90 | pub const PWriteVError = error{OutOfMemory} || File.WriteError; | ||
| 91 | |||
| 92 | /// data - just the inner references - must live until pwritev frame completes. | ||
| 93 | pub 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 | ||
| 123 | pub 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 | |||
| 135 | pub 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. | ||
| 175 | pub 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. | ||
| 208 | pub 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 | |||
| 237 | pub const PReadVError = error{OutOfMemory} || File.ReadError; | ||
| 238 | |||
| 239 | /// data - just the inner references - must live until preadv frame completes. | ||
| 240 | pub 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 | ||
| 271 | pub 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 | |||
| 295 | pub 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 | ||
| 334 | pub 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 | |||
| 366 | pub 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 | |||
| 400 | pub 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. | ||
| 424 | pub 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. | ||
| 429 | pub 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. | ||
| 455 | pub 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`. | ||
| 483 | pub 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 | ||
| 613 | pub 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. | ||
| 618 | pub 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 | |||
| 631 | fn 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 | |||
| 646 | fn 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. | ||
| 684 | pub 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 | |||
| 709 | pub const WatchEventId = enum { | ||
| 710 | CloseWrite, | ||
| 711 | Delete, | ||
| 712 | }; | ||
| 713 | |||
| 714 | fn 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 | |||
| 720 | fn hashString(s: []const u16) u32 { | ||
| 721 | return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s))); | ||
| 722 | } | ||
| 723 | |||
| 724 | pub const WatchEventError = error{ | ||
| 725 | UserResourceLimitReached, | ||
| 726 | SystemResources, | ||
| 727 | AccessDenied, | ||
| 728 | Unexpected, // TODO remove this possibility | ||
| 729 | }; | ||
| 730 | |||
| 731 | pub 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 | |||
| 1307 | const test_tmp_dir = "std_event_fs_test"; | ||
| 1308 | |||
| 1309 | test "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 | |||
| 1322 | fn 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 | |||
| 1370 | pub 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 | |||
| 1395 | pub 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; | ... | @@ -6,7 +6,6 @@ const testing = std.testing; |
| 6 | const mem = std.mem; | 6 | const mem = std.mem; |
| 7 | const AtomicRmwOp = builtin.AtomicRmwOp; | 7 | const AtomicRmwOp = builtin.AtomicRmwOp; |
| 8 | const AtomicOrder = builtin.AtomicOrder; | 8 | const AtomicOrder = builtin.AtomicOrder; |
| 9 | const fs = std.event.fs; | ||
| 10 | const os = std.os; | 9 | const os = std.os; |
| 11 | const windows = os.windows; | 10 | const windows = os.windows; |
| 12 | const maxInt = std.math.maxInt; | 11 | const maxInt = std.math.maxInt; |
| ... | @@ -174,21 +173,19 @@ pub const Loop = struct { | ... | @@ -174,21 +173,19 @@ pub const Loop = struct { |
| 174 | fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void { | 173 | fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void { |
| 175 | switch (builtin.os) { | 174 | switch (builtin.os) { |
| 176 | .linux => { | 175 | .linux => { |
| 177 | self.os_data.fs_queue = std.atomic.Queue(fs.Request).init(); | 176 | self.os_data.fs_queue = std.atomic.Queue(Request).init(); |
| 178 | self.os_data.fs_queue_item = 0; | 177 | self.os_data.fs_queue_item = 0; |
| 179 | // we need another thread for the file system because Linux does not have an async | 178 | // we need another thread for the file system because Linux does not have an async |
| 180 | // file system I/O API. | 179 | // file system I/O API. |
| 181 | self.os_data.fs_end_request = fs.RequestNode{ | 180 | self.os_data.fs_end_request = Request.Node{ |
| 182 | .prev = undefined, | 181 | .data = Request{ |
| 183 | .next = undefined, | 182 | .msg = .end, |
| 184 | .data = fs.Request{ | 183 | .finish = .NoAction, |
| 185 | .msg = fs.Request.Msg.End, | ||
| 186 | .finish = fs.Request.Finish.NoAction, | ||
| 187 | }, | 184 | }, |
| 188 | }; | 185 | }; |
| 189 | 186 | ||
| 190 | errdefer { | 187 | 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); |
| 192 | } | 189 | } |
| 193 | for (self.eventfd_resume_nodes) |*eventfd_node| { | 190 | for (self.eventfd_resume_nodes) |*eventfd_node| { |
| 194 | eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{ | 191 | eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{ |
| ... | @@ -207,10 +204,10 @@ pub const Loop = struct { | ... | @@ -207,10 +204,10 @@ pub const Loop = struct { |
| 207 | } | 204 | } |
| 208 | 205 | ||
| 209 | self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC); | 206 | 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); |
| 211 | 208 | ||
| 212 | self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK); | 209 | 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); |
| 214 | 211 | ||
| 215 | self.os_data.final_eventfd_event = os.epoll_event{ | 212 | self.os_data.final_eventfd_event = os.epoll_event{ |
| 216 | .events = os.EPOLLIN, | 213 | .events = os.EPOLLIN, |
| ... | @@ -237,7 +234,7 @@ pub const Loop = struct { | ... | @@ -237,7 +234,7 @@ pub const Loop = struct { |
| 237 | var extra_thread_index: usize = 0; | 234 | var extra_thread_index: usize = 0; |
| 238 | errdefer { | 235 | errdefer { |
| 239 | // writing 8 bytes to an eventfd cannot fail | 236 | // 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; |
| 241 | while (extra_thread_index != 0) { | 238 | while (extra_thread_index != 0) { |
| 242 | extra_thread_index -= 1; | 239 | extra_thread_index -= 1; |
| 243 | self.extra_threads[extra_thread_index].wait(); | 240 | self.extra_threads[extra_thread_index].wait(); |
| ... | @@ -249,20 +246,20 @@ pub const Loop = struct { | ... | @@ -249,20 +246,20 @@ pub const Loop = struct { |
| 249 | }, | 246 | }, |
| 250 | .macosx, .freebsd, .netbsd, .dragonfly => { | 247 | .macosx, .freebsd, .netbsd, .dragonfly => { |
| 251 | self.os_data.kqfd = try os.kqueue(); | 248 | self.os_data.kqfd = try os.kqueue(); |
| 252 | errdefer os.close(self.os_data.kqfd); | 249 | errdefer noasync os.close(self.os_data.kqfd); |
| 253 | 250 | ||
| 254 | self.os_data.fs_kqfd = try os.kqueue(); | 251 | 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); |
| 256 | 253 | ||
| 257 | self.os_data.fs_queue = std.atomic.Queue(fs.Request).init(); | 254 | self.os_data.fs_queue = std.atomic.Queue(Request).init(); |
| 258 | // we need another thread for the file system because Darwin does not have an async | 255 | // we need another thread for the file system because Darwin does not have an async |
| 259 | // file system I/O API. | 256 | // file system I/O API. |
| 260 | self.os_data.fs_end_request = fs.RequestNode{ | 257 | self.os_data.fs_end_request = Request.Node{ |
| 261 | .prev = undefined, | 258 | .prev = undefined, |
| 262 | .next = undefined, | 259 | .next = undefined, |
| 263 | .data = fs.Request{ | 260 | .data = Request{ |
| 264 | .msg = fs.Request.Msg.End, | 261 | .msg = .end, |
| 265 | .finish = fs.Request.Finish.NoAction, | 262 | .finish = .NoAction, |
| 266 | }, | 263 | }, |
| 267 | }; | 264 | }; |
| 268 | 265 | ||
| ... | @@ -407,14 +404,14 @@ pub const Loop = struct { | ... | @@ -407,14 +404,14 @@ pub const Loop = struct { |
| 407 | fn deinitOsData(self: *Loop) void { | 404 | fn deinitOsData(self: *Loop) void { |
| 408 | switch (builtin.os) { | 405 | switch (builtin.os) { |
| 409 | .linux => { | 406 | .linux => { |
| 410 | os.close(self.os_data.final_eventfd); | 407 | noasync os.close(self.os_data.final_eventfd); |
| 411 | while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd); | 408 | while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd); |
| 412 | os.close(self.os_data.epollfd); | 409 | noasync os.close(self.os_data.epollfd); |
| 413 | self.allocator.free(self.eventfd_resume_nodes); | 410 | self.allocator.free(self.eventfd_resume_nodes); |
| 414 | }, | 411 | }, |
| 415 | .macosx, .freebsd, .netbsd, .dragonfly => { | 412 | .macosx, .freebsd, .netbsd, .dragonfly => { |
| 416 | os.close(self.os_data.kqfd); | 413 | noasync os.close(self.os_data.kqfd); |
| 417 | os.close(self.os_data.fs_kqfd); | 414 | noasync os.close(self.os_data.fs_kqfd); |
| 418 | }, | 415 | }, |
| 419 | .windows => { | 416 | .windows => { |
| 420 | windows.CloseHandle(self.os_data.io_port); | 417 | windows.CloseHandle(self.os_data.io_port); |
| ... | @@ -711,6 +708,190 @@ pub const Loop = struct { | ... | @@ -711,6 +708,190 @@ pub const Loop = struct { |
| 711 | } | 708 | } |
| 712 | } | 709 | } |
| 713 | 710 | ||
| 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 | |||
| 714 | fn workerRun(self: *Loop) void { | 895 | fn workerRun(self: *Loop) void { |
| 715 | while (true) { | 896 | while (true) { |
| 716 | while (true) { | 897 | while (true) { |
| ... | @@ -804,7 +985,7 @@ pub const Loop = struct { | ... | @@ -804,7 +985,7 @@ pub const Loop = struct { |
| 804 | } | 985 | } |
| 805 | } | 986 | } |
| 806 | 987 | ||
| 807 | fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void { | 988 | fn posixFsRequest(self: *Loop, request_node: *Request.Node) void { |
| 808 | self.beginOneEvent(); // finished in posixFsRun after processing the msg | 989 | self.beginOneEvent(); // finished in posixFsRun after processing the msg |
| 809 | self.os_data.fs_queue.put(request_node); | 990 | self.os_data.fs_queue.put(request_node); |
| 810 | switch (builtin.os) { | 991 | switch (builtin.os) { |
| ... | @@ -826,7 +1007,7 @@ pub const Loop = struct { | ... | @@ -826,7 +1007,7 @@ pub const Loop = struct { |
| 826 | } | 1007 | } |
| 827 | } | 1008 | } |
| 828 | 1009 | ||
| 829 | fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void { | 1010 | fn posixFsCancel(self: *Loop, request_node: *Request.Node) void { |
| 830 | if (self.os_data.fs_queue.remove(request_node)) { | 1011 | if (self.os_data.fs_queue.remove(request_node)) { |
| 831 | self.finishOneEvent(); | 1012 | self.finishOneEvent(); |
| 832 | } | 1013 | } |
| ... | @@ -841,37 +1022,32 @@ pub const Loop = struct { | ... | @@ -841,37 +1022,32 @@ pub const Loop = struct { |
| 841 | } | 1022 | } |
| 842 | while (self.os_data.fs_queue.get()) |node| { | 1023 | while (self.os_data.fs_queue.get()) |node| { |
| 843 | switch (node.data.msg) { | 1024 | switch (node.data.msg) { |
| 844 | .End => return, | 1025 | .end => return, |
| 845 | .WriteV => |*msg| { | 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| { | ||
| 846 | msg.result = noasync os.writev(msg.fd, msg.iov); | 1033 | msg.result = noasync os.writev(msg.fd, msg.iov); |
| 847 | }, | 1034 | }, |
| 848 | .PWriteV => |*msg| { | 1035 | .pwritev => |*msg| { |
| 849 | msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset); | 1036 | msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset); |
| 850 | }, | 1037 | }, |
| 851 | .PReadV => |*msg| { | 1038 | .preadv => |*msg| { |
| 852 | msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset); | 1039 | msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset); |
| 853 | }, | 1040 | }, |
| 854 | .Open => |*msg| { | 1041 | .open => |*msg| { |
| 855 | msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode); | 1042 | msg.result = noasync os.openC(msg.path, msg.flags, msg.mode); |
| 856 | }, | 1043 | }, |
| 857 | .Close => |*msg| noasync os.close(msg.fd), | 1044 | .openat => |*msg| { |
| 858 | .WriteFile => |*msg| blk: { | 1045 | msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode); |
| 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); | ||
| 868 | }, | 1046 | }, |
| 1047 | .close => |*msg| noasync os.close(msg.fd), | ||
| 869 | } | 1048 | } |
| 870 | switch (node.data.finish) { | 1049 | switch (node.data.finish) { |
| 871 | .TickNode => |*tick_node| self.onNextTick(tick_node), | 1050 | .TickNode => |*tick_node| self.onNextTick(tick_node), |
| 872 | .DeallocCloseOperation => |close_op| { | ||
| 873 | self.allocator.destroy(close_op); | ||
| 874 | }, | ||
| 875 | .NoAction => {}, | 1051 | .NoAction => {}, |
| 876 | } | 1052 | } |
| 877 | self.finishOneEvent(); | 1053 | self.finishOneEvent(); |
| ... | @@ -911,8 +1087,8 @@ pub const Loop = struct { | ... | @@ -911,8 +1087,8 @@ pub const Loop = struct { |
| 911 | fs_kevent_wait: os.Kevent, | 1087 | fs_kevent_wait: os.Kevent, |
| 912 | fs_thread: *Thread, | 1088 | fs_thread: *Thread, |
| 913 | fs_kqfd: i32, | 1089 | fs_kqfd: i32, |
| 914 | fs_queue: std.atomic.Queue(fs.Request), | 1090 | fs_queue: std.atomic.Queue(Request), |
| 915 | fs_end_request: fs.RequestNode, | 1091 | fs_end_request: Request.Node, |
| 916 | }; | 1092 | }; |
| 917 | 1093 | ||
| 918 | const LinuxOsData = struct { | 1094 | const LinuxOsData = struct { |
| ... | @@ -921,8 +1097,99 @@ pub const Loop = struct { | ... | @@ -921,8 +1097,99 @@ pub const Loop = struct { |
| 921 | final_eventfd_event: os.linux.epoll_event, | 1097 | final_eventfd_event: os.linux.epoll_event, |
| 922 | fs_thread: *Thread, | 1098 | fs_thread: *Thread, |
| 923 | fs_queue_item: i32, | 1099 | fs_queue_item: i32, |
| 924 | fs_queue: std.atomic.Queue(fs.Request), | 1100 | fs_queue: std.atomic.Queue(Request), |
| 925 | fs_end_request: fs.RequestNode, | 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 | }; | ||
| 926 | }; | 1193 | }; |
| 927 | }; | 1194 | }; |
| 928 | 1195 |
lib/std/fs.zig+31-6| ... | @@ -23,6 +23,8 @@ pub const realpathW = os.realpathW; | ... | @@ -23,6 +23,8 @@ pub const realpathW = os.realpathW; |
| 23 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; | 23 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; |
| 24 | pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; | 24 | pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; |
| 25 | 25 | ||
| 26 | pub const Watch = @import("fs/watch.zig").Watch; | ||
| 27 | |||
| 26 | /// This represents the maximum size of a UTF-8 encoded file path. | 28 | /// This represents the maximum size of a UTF-8 encoded file path. |
| 27 | /// All file system operations which return a path are guaranteed to | 29 | /// All file system operations which return a path are guaranteed to |
| 28 | /// fit into a UTF-8 encoded array of this length. | 30 | /// fit into a UTF-8 encoded array of this length. |
| ... | @@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init( | ... | @@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init( |
| 43 | base64.standard_pad_char, | 45 | base64.standard_pad_char, |
| 44 | ); | 46 | ); |
| 45 | 47 | ||
| 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. | ||
| 50 | pub const need_async_thread = std.io.is_async and switch (builtin.os) { | ||
| 51 | .windows, .other => false, | ||
| 52 | else => true, | ||
| 53 | }; | ||
| 54 | |||
| 46 | /// TODO remove the allocator requirement from this API | 55 | /// TODO remove the allocator requirement from this API |
| 47 | pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { | 56 | pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { |
| 48 | if (symLink(existing_path, new_path)) { | 57 | if (symLink(existing_path, new_path)) { |
| ... | @@ -688,7 +697,11 @@ pub const Dir = struct { | ... | @@ -688,7 +697,11 @@ pub const Dir = struct { |
| 688 | } | 697 | } |
| 689 | 698 | ||
| 690 | pub fn close(self: *Dir) void { | 699 | 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 | } | ||
| 692 | self.* = undefined; | 705 | self.* = undefined; |
| 693 | } | 706 | } |
| 694 | 707 | ||
| ... | @@ -718,8 +731,11 @@ pub const Dir = struct { | ... | @@ -718,8 +731,11 @@ pub const Dir = struct { |
| 718 | @as(u32, os.O_WRONLY) | 731 | @as(u32, os.O_WRONLY) |
| 719 | else | 732 | else |
| 720 | @as(u32, os.O_RDONLY); | 733 | @as(u32, os.O_RDONLY); |
| 721 | const fd = try os.openatC(self.fd, sub_path, os_flags, 0); | 734 | const fd = if (need_async_thread) |
| 722 | return File{ .handle = fd }; | 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 }; | ||
| 723 | } | 739 | } |
| 724 | 740 | ||
| 725 | /// Same as `openFile` but Windows-only and the path parameter is | 741 | /// Same as `openFile` but Windows-only and the path parameter is |
| ... | @@ -756,8 +772,11 @@ pub const Dir = struct { | ... | @@ -756,8 +772,11 @@ pub const Dir = struct { |
| 756 | (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) | | 772 | (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) | |
| 757 | (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) | | 773 | (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) | |
| 758 | (if (flags.exclusive) @as(u32, os.O_EXCL) else 0); | 774 | (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); | 775 | const fd = if (need_async_thread) |
| 760 | return File{ .handle = fd }; | 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 }; | ||
| 761 | } | 780 | } |
| 762 | 781 | ||
| 763 | /// Same as `createFile` but Windows-only and the path parameter is | 782 | /// Same as `createFile` but Windows-only and the path parameter is |
| ... | @@ -919,7 +938,12 @@ pub const Dir = struct { | ... | @@ -919,7 +938,12 @@ pub const Dir = struct { |
| 919 | } | 938 | } |
| 920 | 939 | ||
| 921 | fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir { | 940 | 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) { | ||
| 923 | error.FileTooBig => unreachable, // can't happen for directories | 947 | error.FileTooBig => unreachable, // can't happen for directories |
| 924 | error.IsDir => unreachable, // we're providing O_DIRECTORY | 948 | error.IsDir => unreachable, // we're providing O_DIRECTORY |
| 925 | error.NoSpaceLeft => unreachable, // not providing O_CREAT | 949 | error.NoSpaceLeft => unreachable, // not providing O_CREAT |
| ... | @@ -1588,4 +1612,5 @@ test "" { | ... | @@ -1588,4 +1612,5 @@ test "" { |
| 1588 | _ = @import("fs/path.zig"); | 1612 | _ = @import("fs/path.zig"); |
| 1589 | _ = @import("fs/file.zig"); | 1613 | _ = @import("fs/file.zig"); |
| 1590 | _ = @import("fs/get_app_data_dir.zig"); | 1614 | _ = @import("fs/get_app_data_dir.zig"); |
| 1615 | _ = @import("fs/watch.zig"); | ||
| 1591 | } | 1616 | } |
lib/std/fs/file.zig+77-74| ... | @@ -8,18 +8,29 @@ const assert = std.debug.assert; | ... | @@ -8,18 +8,29 @@ const assert = std.debug.assert; |
| 8 | const windows = os.windows; | 8 | const windows = os.windows; |
| 9 | const Os = builtin.Os; | 9 | const Os = builtin.Os; |
| 10 | const maxInt = std.math.maxInt; | 10 | const maxInt = std.math.maxInt; |
| 11 | const need_async_thread = std.fs.need_async_thread; | ||
| 11 | 12 | ||
| 12 | pub const File = struct { | 13 | pub const File = struct { |
| 13 | /// The OS-specific file descriptor or file handle. | 14 | /// The OS-specific file descriptor or file handle. |
| 14 | handle: os.fd_t, | 15 | handle: os.fd_t, |
| 15 | 16 | ||
| 16 | pub const Mode = switch (builtin.os) { | 17 | /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O. |
| 17 | Os.windows => void, | 18 | /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking |
| 18 | else => u32, | 19 | /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor, |
| 19 | }; | 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; | ||
| 20 | 31 | ||
| 21 | pub const default_mode = switch (builtin.os) { | 32 | pub const default_mode = switch (builtin.os) { |
| 22 | Os.windows => {}, | 33 | .windows => 0, |
| 23 | else => 0o666, | 34 | else => 0o666, |
| 24 | }; | 35 | }; |
| 25 | 36 | ||
| ... | @@ -49,87 +60,27 @@ pub const File = struct { | ... | @@ -49,87 +60,27 @@ pub const File = struct { |
| 49 | mode: Mode = default_mode, | 60 | mode: Mode = default_mode, |
| 50 | }; | 61 | }; |
| 51 | 62 | ||
| 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 | |||
| 115 | /// Test for the existence of `path`. | 63 | /// Test for the existence of `path`. |
| 116 | /// `path` is UTF8-encoded. | 64 | /// `path` is UTF8-encoded. |
| 117 | /// In general it is recommended to avoid this function. For example, | 65 | /// In general it is recommended to avoid this function. For example, |
| 118 | /// instead of testing if a file exists and then opening it, just | 66 | /// instead of testing if a file exists and then opening it, just |
| 119 | /// open it and handle the error for file not found. | 67 | /// open it and handle the error for file not found. |
| 120 | /// TODO: deprecate this and move it to `std.fs.Dir`. | 68 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 69 | /// TODO: integrate with async I/O | ||
| 121 | pub fn access(path: []const u8) !void { | 70 | pub fn access(path: []const u8) !void { |
| 122 | return os.access(path, os.F_OK); | 71 | return os.access(path, os.F_OK); |
| 123 | } | 72 | } |
| 124 | 73 | ||
| 125 | /// Same as `access` except the parameter is null-terminated. | 74 | /// Same as `access` except the parameter is null-terminated. |
| 126 | /// TODO: deprecate this and move it to `std.fs.Dir`. | 75 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 76 | /// TODO: integrate with async I/O | ||
| 127 | pub fn accessC(path: [*:0]const u8) !void { | 77 | pub fn accessC(path: [*:0]const u8) !void { |
| 128 | return os.accessC(path, os.F_OK); | 78 | return os.accessC(path, os.F_OK); |
| 129 | } | 79 | } |
| 130 | 80 | ||
| 131 | /// Same as `access` except the parameter is null-terminated UTF16LE-encoded. | 81 | /// Same as `access` except the parameter is null-terminated UTF16LE-encoded. |
| 132 | /// TODO: deprecate this and move it to `std.fs.Dir`. | 82 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 83 | /// TODO: integrate with async I/O | ||
| 133 | pub fn accessW(path: [*:0]const u16) !void { | 84 | pub fn accessW(path: [*:0]const u16) !void { |
| 134 | return os.accessW(path, os.F_OK); | 85 | return os.accessW(path, os.F_OK); |
| 135 | } | 86 | } |
| ... | @@ -137,7 +88,11 @@ pub const File = struct { | ... | @@ -137,7 +88,11 @@ pub const File = struct { |
| 137 | /// Upon success, the stream is in an uninitialized state. To continue using it, | 88 | /// Upon success, the stream is in an uninitialized state. To continue using it, |
| 138 | /// you must use the open() function. | 89 | /// you must use the open() function. |
| 139 | pub fn close(self: File) void { | 90 | 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 | } | ||
| 141 | } | 96 | } |
| 142 | 97 | ||
| 143 | /// Test whether the file refers to a terminal. | 98 | /// Test whether the file refers to a terminal. |
| ... | @@ -167,26 +122,31 @@ pub const File = struct { | ... | @@ -167,26 +122,31 @@ pub const File = struct { |
| 167 | pub const SeekError = os.SeekError; | 122 | pub const SeekError = os.SeekError; |
| 168 | 123 | ||
| 169 | /// Repositions read/write file offset relative to the current offset. | 124 | /// Repositions read/write file offset relative to the current offset. |
| 125 | /// TODO: integrate with async I/O | ||
| 170 | pub fn seekBy(self: File, offset: i64) SeekError!void { | 126 | pub fn seekBy(self: File, offset: i64) SeekError!void { |
| 171 | return os.lseek_CUR(self.handle, offset); | 127 | return os.lseek_CUR(self.handle, offset); |
| 172 | } | 128 | } |
| 173 | 129 | ||
| 174 | /// Repositions read/write file offset relative to the end. | 130 | /// Repositions read/write file offset relative to the end. |
| 131 | /// TODO: integrate with async I/O | ||
| 175 | pub fn seekFromEnd(self: File, offset: i64) SeekError!void { | 132 | pub fn seekFromEnd(self: File, offset: i64) SeekError!void { |
| 176 | return os.lseek_END(self.handle, offset); | 133 | return os.lseek_END(self.handle, offset); |
| 177 | } | 134 | } |
| 178 | 135 | ||
| 179 | /// Repositions read/write file offset relative to the beginning. | 136 | /// Repositions read/write file offset relative to the beginning. |
| 137 | /// TODO: integrate with async I/O | ||
| 180 | pub fn seekTo(self: File, offset: u64) SeekError!void { | 138 | pub fn seekTo(self: File, offset: u64) SeekError!void { |
| 181 | return os.lseek_SET(self.handle, offset); | 139 | return os.lseek_SET(self.handle, offset); |
| 182 | } | 140 | } |
| 183 | 141 | ||
| 184 | pub const GetPosError = os.SeekError || os.FStatError; | 142 | pub const GetPosError = os.SeekError || os.FStatError; |
| 185 | 143 | ||
| 144 | /// TODO: integrate with async I/O | ||
| 186 | pub fn getPos(self: File) GetPosError!u64 { | 145 | pub fn getPos(self: File) GetPosError!u64 { |
| 187 | return os.lseek_CUR_get(self.handle); | 146 | return os.lseek_CUR_get(self.handle); |
| 188 | } | 147 | } |
| 189 | 148 | ||
| 149 | /// TODO: integrate with async I/O | ||
| 190 | pub fn getEndPos(self: File) GetPosError!u64 { | 150 | pub fn getEndPos(self: File) GetPosError!u64 { |
| 191 | if (builtin.os == .windows) { | 151 | if (builtin.os == .windows) { |
| 192 | return windows.GetFileSizeEx(self.handle); | 152 | return windows.GetFileSizeEx(self.handle); |
| ... | @@ -196,6 +156,7 @@ pub const File = struct { | ... | @@ -196,6 +156,7 @@ pub const File = struct { |
| 196 | 156 | ||
| 197 | pub const ModeError = os.FStatError; | 157 | pub const ModeError = os.FStatError; |
| 198 | 158 | ||
| 159 | /// TODO: integrate with async I/O | ||
| 199 | pub fn mode(self: File) ModeError!Mode { | 160 | pub fn mode(self: File) ModeError!Mode { |
| 200 | if (builtin.os == .windows) { | 161 | if (builtin.os == .windows) { |
| 201 | return {}; | 162 | return {}; |
| ... | @@ -219,6 +180,7 @@ pub const File = struct { | ... | @@ -219,6 +180,7 @@ pub const File = struct { |
| 219 | 180 | ||
| 220 | pub const StatError = os.FStatError; | 181 | pub const StatError = os.FStatError; |
| 221 | 182 | ||
| 183 | /// TODO: integrate with async I/O | ||
| 222 | pub fn stat(self: File) StatError!Stat { | 184 | pub fn stat(self: File) StatError!Stat { |
| 223 | if (builtin.os == .windows) { | 185 | if (builtin.os == .windows) { |
| 224 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 186 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| ... | @@ -259,6 +221,7 @@ pub const File = struct { | ... | @@ -259,6 +221,7 @@ pub const File = struct { |
| 259 | /// and therefore this function cannot guarantee any precision will be stored. | 221 | /// and therefore this function cannot guarantee any precision will be stored. |
| 260 | /// Further, the maximum value is limited by the system ABI. When a value is provided | 222 | /// Further, the maximum value is limited by the system ABI. When a value is provided |
| 261 | /// that exceeds this range, the value is clamped to the maximum. | 223 | /// that exceeds this range, the value is clamped to the maximum. |
| 224 | /// TODO: integrate with async I/O | ||
| 262 | pub fn updateTimes( | 225 | pub fn updateTimes( |
| 263 | self: File, | 226 | self: File, |
| 264 | /// access timestamp in nanoseconds | 227 | /// access timestamp in nanoseconds |
| ... | @@ -287,21 +250,61 @@ pub const File = struct { | ... | @@ -287,21 +250,61 @@ pub const File = struct { |
| 287 | pub const ReadError = os.ReadError; | 250 | pub const ReadError = os.ReadError; |
| 288 | 251 | ||
| 289 | pub fn read(self: File, buffer: []u8) ReadError!usize { | 252 | 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 | } | ||
| 290 | return os.read(self.handle, buffer); | 256 | return os.read(self.handle, buffer); |
| 291 | } | 257 | } |
| 292 | 258 | ||
| 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 | |||
| 293 | pub const WriteError = os.WriteError; | 280 | pub const WriteError = os.WriteError; |
| 294 | 281 | ||
| 295 | pub fn write(self: File, bytes: []const u8) WriteError!void { | 282 | 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 | } | ||
| 296 | return os.write(self.handle, bytes); | 286 | return os.write(self.handle, bytes); |
| 297 | } | 287 | } |
| 298 | 288 | ||
| 299 | pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void { | 289 | pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void { |
| 300 | if (std.event.Loop.instance) |loop| { | 290 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { |
| 301 | return std.event.fs.writevPosix(loop, self.handle, iovecs); | 291 | return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset); |
| 302 | } else { | 292 | } |
| 303 | return os.writev(self.handle, iovecs); | 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); | ||
| 304 | } | 306 | } |
| 307 | return os.pwritev(self.handle, iovecs); | ||
| 305 | } | 308 | } |
| 306 | 309 | ||
| 307 | pub fn inStream(file: File) InStream { | 310 | pub fn inStream(file: File) InStream { |
lib/std/fs/watch.zig created+673| ... | @@ -0,0 +1,673 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const event = std.event; | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const testing = std.testing; | ||
| 6 | const os = std.os; | ||
| 7 | const mem = std.mem; | ||
| 8 | const windows = os.windows; | ||
| 9 | const Loop = event.Loop; | ||
| 10 | const fd_t = os.fd_t; | ||
| 11 | const File = std.fs.File; | ||
| 12 | const Allocator = mem.Allocator; | ||
| 13 | |||
| 14 | const WatchEventId = enum { | ||
| 15 | CloseWrite, | ||
| 16 | Delete, | ||
| 17 | }; | ||
| 18 | |||
| 19 | fn 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 | |||
| 25 | fn hashString(s: []const u16) u32 { | ||
| 26 | return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s))); | ||
| 27 | } | ||
| 28 | |||
| 29 | const WatchEventError = error{ | ||
| 30 | UserResourceLimitReached, | ||
| 31 | SystemResources, | ||
| 32 | AccessDenied, | ||
| 33 | Unexpected, // TODO remove this possibility | ||
| 34 | }; | ||
| 35 | |||
| 36 | pub 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 | |||
| 612 | const test_tmp_dir = "std_event_fs_test"; | ||
| 613 | |||
| 614 | test "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 | |||
| 627 | fn 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 { | ... | @@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t { |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | pub fn getStdOut() File { | 49 | pub fn getStdOut() File { |
| 50 | return File.openHandle(getStdOutHandle()); | 50 | return File{ |
| 51 | .handle = getStdOutHandle(), | ||
| 52 | .io_mode = .blocking, | ||
| 53 | }; | ||
| 51 | } | 54 | } |
| 52 | 55 | ||
| 53 | fn getStdErrHandle() os.fd_t { | 56 | fn getStdErrHandle() os.fd_t { |
| ... | @@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t { | ... | @@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t { |
| 63 | } | 66 | } |
| 64 | 67 | ||
| 65 | pub fn getStdErr() File { | 68 | pub 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 | }; | ||
| 67 | } | 74 | } |
| 68 | 75 | ||
| 69 | fn getStdInHandle() os.fd_t { | 76 | fn getStdInHandle() os.fd_t { |
| ... | @@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t { | ... | @@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t { |
| 79 | } | 86 | } |
| 80 | 87 | ||
| 81 | pub fn getStdIn() File { | 88 | pub fn getStdIn() File { |
| 82 | return File.openHandle(getStdInHandle()); | 89 | return File{ |
| 90 | .handle = getStdInHandle(), | ||
| 91 | .io_mode = .blocking, | ||
| 92 | }; | ||
| 83 | } | 93 | } |
| 84 | 94 | ||
| 85 | pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; | 95 | pub 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")) | ... | @@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream")) |
| 9 | else | 9 | else |
| 10 | default_stack_size; | 10 | default_stack_size; |
| 11 | 11 | ||
| 12 | /// TODO this is not integrated with evented I/O yet. | ||
| 13 | /// https://github.com/ziglang/zig/issues/3557 | ||
| 14 | pub fn OutStream(comptime WriteError: type) type { | 12 | pub fn OutStream(comptime WriteError: type) type { |
| 15 | return struct { | 13 | return struct { |
| 16 | const Self = @This(); | 14 | const Self = @This(); |
| 17 | pub const Error = WriteError; | 15 | pub const Error = WriteError; |
| 18 | // TODO https://github.com/ziglang/zig/issues/3557 | 16 | pub const WriteFn = if (std.io.is_async) |
| 19 | pub const WriteFn = if (std.io.is_async and false) | ||
| 20 | async fn (self: *Self, bytes: []const u8) Error!void | 17 | async fn (self: *Self, bytes: []const u8) Error!void |
| 21 | else | 18 | else |
| 22 | fn (self: *Self, bytes: []const u8) Error!void; | 19 | fn (self: *Self, bytes: []const u8) Error!void; |
| ... | @@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type { | ... | @@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type { |
| 24 | writeFn: WriteFn, | 21 | writeFn: WriteFn, |
| 25 | 22 | ||
| 26 | pub fn write(self: *Self, bytes: []const u8) Error!void { | 23 | pub fn write(self: *Self, bytes: []const u8) Error!void { |
| 27 | // TODO https://github.com/ziglang/zig/issues/3557 | 24 | if (std.io.is_async) { |
| 28 | if (std.io.is_async and false) { | ||
| 29 | // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write. | 25 | // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write. |
| 30 | @setRuntimeSafety(false); | 26 | @setRuntimeSafety(false); |
| 31 | var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; | 27 | var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; |
| ... | @@ -40,8 +36,8 @@ pub fn OutStream(comptime WriteError: type) type { | ... | @@ -40,8 +36,8 @@ pub fn OutStream(comptime WriteError: type) type { |
| 40 | } | 36 | } |
| 41 | 37 | ||
| 42 | pub fn writeByte(self: *Self, byte: u8) Error!void { | 38 | pub fn writeByte(self: *Self, byte: u8) Error!void { |
| 43 | const slice = @as(*const [1]u8, &byte)[0..]; | 39 | const array = [1]u8{byte}; |
| 44 | return self.writeFn(self, slice); | 40 | return self.write(&array); |
| 45 | } | 41 | } |
| 46 | 42 | ||
| 47 | pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void { | 43 | pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void { |
| ... | @@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type { | ... | @@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type { |
| 51 | var remaining: usize = n; | 47 | var remaining: usize = n; |
| 52 | while (remaining > 0) { | 48 | while (remaining > 0) { |
| 53 | const to_write = std.math.min(remaining, bytes.len); | 49 | 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]); |
| 55 | remaining -= to_write; | 51 | remaining -= to_write; |
| 56 | } | 52 | } |
| 57 | } | 53 | } |
| ... | @@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type { | ... | @@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type { |
| 60 | pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void { | 56 | pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void { |
| 61 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; | 57 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 62 | mem.writeIntNative(T, &bytes, value); | 58 | mem.writeIntNative(T, &bytes, value); |
| 63 | return self.writeFn(self, &bytes); | 59 | return self.write(&bytes); |
| 64 | } | 60 | } |
| 65 | 61 | ||
| 66 | /// Write a foreign-endian integer. | 62 | /// Write a foreign-endian integer. |
| 67 | pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void { | 63 | pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void { |
| 68 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; | 64 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 69 | mem.writeIntForeign(T, &bytes, value); | 65 | mem.writeIntForeign(T, &bytes, value); |
| 70 | return self.writeFn(self, &bytes); | 66 | return self.write(&bytes); |
| 71 | } | 67 | } |
| 72 | 68 | ||
| 73 | pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void { | 69 | pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void { |
| 74 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; | 70 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 75 | mem.writeIntLittle(T, &bytes, value); | 71 | mem.writeIntLittle(T, &bytes, value); |
| 76 | return self.writeFn(self, &bytes); | 72 | return self.write(&bytes); |
| 77 | } | 73 | } |
| 78 | 74 | ||
| 79 | pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void { | 75 | pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void { |
| 80 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; | 76 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 81 | mem.writeIntBig(T, &bytes, value); | 77 | mem.writeIntBig(T, &bytes, value); |
| 82 | return self.writeFn(self, &bytes); | 78 | return self.write(&bytes); |
| 83 | } | 79 | } |
| 84 | 80 | ||
| 85 | pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void { | 81 | pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void { |
| 86 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; | 82 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 87 | mem.writeInt(T, &bytes, value, endian); | 83 | mem.writeInt(T, &bytes, value, endian); |
| 88 | return self.writeFn(self, &bytes); | 84 | return self.write(&bytes); |
| 89 | } | 85 | } |
| 90 | }; | 86 | }; |
| 91 | } | 87 | } |
lib/std/linked_list.zig+3-6| ... | @@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type { | ... | @@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type { |
| 18 | 18 | ||
| 19 | /// Node inside the linked list wrapping the actual data. | 19 | /// Node inside the linked list wrapping the actual data. |
| 20 | pub const Node = struct { | 20 | pub const Node = struct { |
| 21 | next: ?*Node, | 21 | next: ?*Node = null, |
| 22 | data: T, | 22 | data: T, |
| 23 | 23 | ||
| 24 | pub fn init(data: T) Node { | 24 | pub fn init(data: T) Node { |
| 25 | return Node{ | 25 | return Node{ |
| 26 | .next = null, | ||
| 27 | .data = data, | 26 | .data = data, |
| 28 | }; | 27 | }; |
| 29 | } | 28 | } |
| ... | @@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type { | ... | @@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type { |
| 196 | 195 | ||
| 197 | /// Node inside the linked list wrapping the actual data. | 196 | /// Node inside the linked list wrapping the actual data. |
| 198 | pub const Node = struct { | 197 | pub const Node = struct { |
| 199 | prev: ?*Node, | 198 | prev: ?*Node = null, |
| 200 | next: ?*Node, | 199 | next: ?*Node = null, |
| 201 | data: T, | 200 | data: T, |
| 202 | 201 | ||
| 203 | pub fn init(data: T) Node { | 202 | pub fn init(data: T) Node { |
| 204 | return Node{ | 203 | return Node{ |
| 205 | .prev = null, | ||
| 206 | .next = null, | ||
| 207 | .data = data, | 204 | .data = data, |
| 208 | }; | 205 | }; |
| 209 | } | 206 | } |
lib/std/os.zig+179-26| ... | @@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void { | ... | @@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void { |
| 169 | return error.NoDevice; | 169 | return error.NoDevice; |
| 170 | } | 170 | } |
| 171 | 171 | ||
| 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; | ||
| 173 | stream.readNoEof(buf) catch return error.Unexpected; | 178 | stream.readNoEof(buf) catch return error.Unexpected; |
| 174 | } | 179 | } |
| 175 | 180 | ||
| ... | @@ -293,7 +298,7 @@ pub const ReadError = error{ | ... | @@ -293,7 +298,7 @@ pub const ReadError = error{ |
| 293 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | 298 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. |
| 294 | pub fn read(fd: fd_t, buf: []u8) ReadError!usize { | 299 | pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 295 | if (builtin.os == .windows) { | 300 | if (builtin.os == .windows) { |
| 296 | return windows.ReadFile(fd, buf); | 301 | return windows.ReadFile(fd, buf, null); |
| 297 | } | 302 | } |
| 298 | 303 | ||
| 299 | if (builtin.os == .wasi and !builtin.link_libc) { | 304 | if (builtin.os == .wasi and !builtin.link_libc) { |
| ... | @@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { | ... | @@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 335 | } | 340 | } |
| 336 | 341 | ||
| 337 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. | 342 | /// 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 | 343 | /// |
| 339 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | 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. | ||
| 340 | pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { | 352 | pub 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 | |||
| 341 | while (true) { | 374 | while (true) { |
| 342 | // TODO handle the case when iov_len is too large and get rid of this @intCast | 375 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 343 | const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len)); | 376 | 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 { | ... | @@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { |
| 363 | } | 396 | } |
| 364 | 397 | ||
| 365 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. | 398 | /// 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 | 399 | /// |
| 367 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | 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. | ||
| 406 | pub 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. | ||
| 368 | pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { | 449 | pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { |
| 369 | if (comptime std.Target.current.isDarwin()) { | 450 | if (comptime std.Target.current.isDarwin()) { |
| 370 | // Darwin does not have preadv but it does have pread. | 451 | // 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 { | ... | @@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { |
| 409 | } | 490 | } |
| 410 | } | 491 | } |
| 411 | } | 492 | } |
| 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 | |||
| 412 | while (true) { | 515 | while (true) { |
| 413 | // TODO handle the case when iov_len is too large and get rid of this @intCast | 516 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 414 | const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset); | 517 | const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset); |
| ... | @@ -451,11 +554,9 @@ pub const WriteError = error{ | ... | @@ -451,11 +554,9 @@ pub const WriteError = error{ |
| 451 | /// Write to a file descriptor. Keeps trying if it gets interrupted. | 554 | /// Write to a file descriptor. Keeps trying if it gets interrupted. |
| 452 | /// If the application has a global event loop enabled, EAGAIN is handled | 555 | /// If the application has a global event loop enabled, EAGAIN is handled |
| 453 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | 556 | /// 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. | ||
| 456 | pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { | 557 | pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { |
| 457 | if (builtin.os == .windows) { | 558 | if (builtin.os == .windows) { |
| 458 | return windows.WriteFile(fd, bytes); | 559 | return windows.WriteFile(fd, bytes, null); |
| 459 | } | 560 | } |
| 460 | 561 | ||
| 461 | if (builtin.os == .wasi and !builtin.link_libc) { | 562 | if (builtin.os == .wasi and !builtin.link_libc) { |
| ... | @@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { | ... | @@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { |
| 488 | EINTR => continue, | 589 | EINTR => continue, |
| 489 | EINVAL => unreachable, | 590 | EINVAL => unreachable, |
| 490 | EFAULT => unreachable, | 591 | EFAULT => unreachable, |
| 491 | // TODO https://github.com/ziglang/zig/issues/3557 | 592 | EAGAIN => if (std.event.Loop.instance) |loop| { |
| 492 | EAGAIN => return error.WouldBlock, | 593 | loop.waitUntilFdWritable(fd); |
| 493 | //EAGAIN => if (std.event.Loop.instance) |loop| { | 594 | continue; |
| 494 | // loop.waitUntilFdWritable(fd); | 595 | } else { |
| 495 | // continue; | 596 | return error.WouldBlock; |
| 496 | //} else { | 597 | }, |
| 497 | // return error.WouldBlock; | ||
| 498 | //}, | ||
| 499 | EBADF => unreachable, // Always a race condition. | 598 | EBADF => unreachable, // Always a race condition. |
| 500 | EDESTADDRREQ => unreachable, // `connect` was never called. | 599 | EDESTADDRREQ => unreachable, // `connect` was never called. |
| 501 | EDQUOT => return error.DiskQuota, | 600 | EDQUOT => return error.DiskQuota, |
| ... | @@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void { | ... | @@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void { |
| 540 | } | 639 | } |
| 541 | } | 640 | } |
| 542 | 641 | ||
| 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. | ||
| 650 | pub 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 | |||
| 543 | /// Write multiple buffers to a file descriptor, with a position offset. | 681 | /// 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. | ||
| 545 | pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void { | 693 | pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void { |
| 546 | if (comptime std.Target.current.isDarwin()) { | 694 | if (comptime std.Target.current.isDarwin()) { |
| 547 | // Darwin does not have pwritev but it does have pwrite. | 695 | // 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 | ... | @@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void |
| 589 | } | 737 | } |
| 590 | } | 738 | } |
| 591 | 739 | ||
| 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 | |||
| 592 | while (true) { | 749 | while (true) { |
| 593 | // TODO handle the case when iov_len is too large and get rid of this @intCast | 750 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 594 | const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset); | 751 | 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 { | ... | @@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t { |
| 694 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 851 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 695 | /// `file_path` is relative to the open directory handle `dir_fd`. | 852 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 696 | /// See also `openatC`. | 853 | /// See also `openatC`. |
| 697 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t { | 854 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { |
| 698 | const file_path_c = try toPosixPath(file_path); | 855 | const file_path_c = try toPosixPath(file_path); |
| 699 | return openatC(dir_fd, &file_path_c, flags, mode); | 856 | return openatC(dir_fd, &file_path_c, flags, mode); |
| 700 | } | 857 | } |
| ... | @@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open | ... | @@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open |
| 702 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 859 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 703 | /// `file_path` is relative to the open directory handle `dir_fd`. | 860 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 704 | /// See also `openat`. | 861 | /// See also `openat`. |
| 705 | pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) OpenError!fd_t { | 862 | pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t { |
| 706 | while (true) { | 863 | while (true) { |
| 707 | const rc = system.openat(dir_fd, file_path, flags, mode); | 864 | const rc = system.openat(dir_fd, file_path, flags, mode); |
| 708 | switch (errno(rc)) { | 865 | switch (errno(rc)) { |
| ... | @@ -3328,9 +3485,7 @@ pub fn getrusage(who: i32) rusage { | ... | @@ -3328,9 +3485,7 @@ pub fn getrusage(who: i32) rusage { |
| 3328 | } | 3485 | } |
| 3329 | } | 3486 | } |
| 3330 | 3487 | ||
| 3331 | pub const TermiosGetError = error{ | 3488 | pub const TermiosGetError = error{NotATerminal} || UnexpectedError; |
| 3332 | NotATerminal, | ||
| 3333 | } || UnexpectedError; | ||
| 3334 | 3489 | ||
| 3335 | pub fn tcgetattr(handle: fd_t) TermiosGetError!termios { | 3490 | pub fn tcgetattr(handle: fd_t) TermiosGetError!termios { |
| 3336 | var term: termios = undefined; | 3491 | var term: termios = undefined; |
| ... | @@ -3342,9 +3497,7 @@ pub fn tcgetattr(handle: fd_t) TermiosGetError!termios { | ... | @@ -3342,9 +3497,7 @@ pub fn tcgetattr(handle: fd_t) TermiosGetError!termios { |
| 3342 | } | 3497 | } |
| 3343 | } | 3498 | } |
| 3344 | 3499 | ||
| 3345 | pub const TermiosSetError = TermiosGetError || error{ | 3500 | pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned}; |
| 3346 | ProcessOrphaned, | ||
| 3347 | }; | ||
| 3348 | 3501 | ||
| 3349 | pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void { | 3502 | pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void { |
| 3350 | while (true) { | 3503 | while (true) { |
lib/std/os/bits/darwin.zig+1| ... | @@ -4,6 +4,7 @@ const maxInt = std.math.maxInt; | ... | @@ -4,6 +4,7 @@ const maxInt = std.math.maxInt; |
| 4 | 4 | ||
| 5 | pub const fd_t = c_int; | 5 | pub const fd_t = c_int; |
| 6 | pub const pid_t = c_int; | 6 | pub const pid_t = c_int; |
| 7 | pub const mode_t = c_uint; | ||
| 7 | 8 | ||
| 8 | pub const in_port_t = u16; | 9 | pub const in_port_t = u16; |
| 9 | pub const sa_family_t = u8; | 10 | pub const sa_family_t = u8; |
lib/std/os/bits/dragonfly.zig+1| ... | @@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool { | ... | @@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool { |
| 7 | pub const fd_t = c_int; | 7 | pub const fd_t = c_int; |
| 8 | pub const pid_t = c_int; | 8 | pub const pid_t = c_int; |
| 9 | pub const off_t = c_long; | 9 | pub const off_t = c_long; |
| 10 | pub const mode_t = c_uint; | ||
| 10 | 11 | ||
| 11 | pub const ENOTSUP = EOPNOTSUPP; | 12 | pub const ENOTSUP = EOPNOTSUPP; |
| 12 | pub const EWOULDBLOCK = EAGAIN; | 13 | pub const EWOULDBLOCK = EAGAIN; |
lib/std/os/bits/freebsd.zig+1| ... | @@ -3,6 +3,7 @@ const maxInt = std.math.maxInt; | ... | @@ -3,6 +3,7 @@ const maxInt = std.math.maxInt; |
| 3 | 3 | ||
| 4 | pub const fd_t = c_int; | 4 | pub const fd_t = c_int; |
| 5 | pub const pid_t = c_int; | 5 | pub const pid_t = c_int; |
| 6 | pub const mode_t = c_uint; | ||
| 6 | 7 | ||
| 7 | pub const socklen_t = u32; | 8 | pub const socklen_t = u32; |
| 8 | 9 |
lib/std/os/bits/linux/x86_64.zig+2| ... | @@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t; | ... | @@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t; |
| 12 | const iovec = linux.iovec; | 12 | const iovec = linux.iovec; |
| 13 | const iovec_const = linux.iovec_const; | 13 | const iovec_const = linux.iovec_const; |
| 14 | 14 | ||
| 15 | pub const mode_t = usize; | ||
| 16 | |||
| 15 | pub const SYS_read = 0; | 17 | pub const SYS_read = 0; |
| 16 | pub const SYS_write = 1; | 18 | pub const SYS_write = 1; |
| 17 | pub const SYS_open = 2; | 19 | pub const SYS_open = 2; |
lib/std/os/bits/netbsd.zig+1| ... | @@ -3,6 +3,7 @@ const maxInt = std.math.maxInt; | ... | @@ -3,6 +3,7 @@ const maxInt = std.math.maxInt; |
| 3 | 3 | ||
| 4 | pub const fd_t = c_int; | 4 | pub const fd_t = c_int; |
| 5 | pub const pid_t = c_int; | 5 | pub const pid_t = c_int; |
| 6 | pub const mode_t = c_uint; | ||
| 6 | 7 | ||
| 7 | /// Renamed from `kevent` to `Kevent` to avoid conflict with function name. | 8 | /// Renamed from `kevent` to `Kevent` to avoid conflict with function name. |
| 8 | pub const Kevent = extern struct { | 9 | pub const Kevent = extern struct { |
lib/std/os/bits/wasi.zig+1| ... | @@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2; | ... | @@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2; |
| 130 | pub const exitcode_t = u32; | 130 | pub const exitcode_t = u32; |
| 131 | 131 | ||
| 132 | pub const fd_t = u32; | 132 | pub const fd_t = u32; |
| 133 | pub const mode_t = u32; | ||
| 133 | 134 | ||
| 134 | pub const fdflags_t = u16; | 135 | pub const fdflags_t = u16; |
| 135 | pub const FDFLAG_APPEND: fdflags_t = 0x0001; | 136 | pub 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"); | ... | @@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig"); |
| 5 | 5 | ||
| 6 | pub const fd_t = HANDLE; | 6 | pub const fd_t = HANDLE; |
| 7 | pub const pid_t = HANDLE; | 7 | pub const pid_t = HANDLE; |
| 8 | pub const mode_t = u0; | ||
| 8 | 9 | ||
| 9 | pub const PATH_MAX = 260; | 10 | pub const PATH_MAX = 260; |
| 10 | 11 |
lib/std/os/windows.zig+128-29| ... | @@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void { | ... | @@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void { |
| 344 | assert(kernel32.FindClose(hFindFile) != 0); | 344 | assert(kernel32.FindClose(hFindFile) != 0); |
| 345 | } | 345 | } |
| 346 | 346 | ||
| 347 | pub const ReadFileError = error{Unexpected}; | 347 | pub const ReadFileError = error{ |
| 348 | 348 | OperationAborted, | |
| 349 | pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize { | 349 | BrokenPipe, |
| 350 | var index: usize = 0; | 350 | Unexpected, |
| 351 | while (index < buffer.len) { | 351 | }; |
| 352 | const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index)); | 352 | |
| 353 | var amt_read: DWORD = undefined; | 353 | /// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into |
| 354 | if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) { | 354 | /// multiple non-atomic reads. |
| 355 | switch (kernel32.GetLastError()) { | 355 | pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize { |
| 356 | .OPERATION_ABORTED => continue, | 356 | if (std.event.Loop.instance) |loop| { |
| 357 | .BROKEN_PIPE => return index, | 357 | // TODO support async ReadFile with no offset |
| 358 | else => |err| return unexpectedError(err), | 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 | } | ||
| 359 | } | 412 | } |
| 413 | if (amt_read == 0) return index; | ||
| 414 | index += amt_read; | ||
| 360 | } | 415 | } |
| 361 | if (amt_read == 0) return index; | 416 | return index; |
| 362 | index += amt_read; | ||
| 363 | } | 417 | } |
| 364 | return index; | ||
| 365 | } | 418 | } |
| 366 | 419 | ||
| 367 | pub const WriteFileError = error{ | 420 | pub const WriteFileError = error{ |
| ... | @@ -371,20 +424,66 @@ pub const WriteFileError = error{ | ... | @@ -371,20 +424,66 @@ pub const WriteFileError = error{ |
| 371 | Unexpected, | 424 | Unexpected, |
| 372 | }; | 425 | }; |
| 373 | 426 | ||
| 374 | /// This function is for blocking file descriptors only. For non-blocking, see | 427 | pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void { |
| 375 | /// `WriteFileAsync`. | 428 | if (std.event.Loop.instance) |loop| { |
| 376 | pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void { | 429 | // TODO support async WriteFile with no offset |
| 377 | var bytes_written: DWORD = undefined; | 430 | const off = offset.?; |
| 378 | // TODO replace this @intCast with a loop that writes all the bytes | 431 | var resume_node = std.event.Loop.ResumeNode.Basic{ |
| 379 | if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) { | 432 | .base = .{ |
| 380 | switch (kernel32.GetLastError()) { | 433 | .id = .Basic, |
| 381 | .INVALID_USER_BUFFER => return error.SystemResources, | 434 | .handle = @frame(), |
| 382 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | 435 | .overlapped = OVERLAPPED{ |
| 383 | .OPERATION_ABORTED => return error.OperationAborted, | 436 | .Internal = 0, |
| 384 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | 437 | .InternalHigh = 0, |
| 385 | .IO_PENDING => unreachable, // this function is for blocking files only | 438 | .Offset = @truncate(u32, off), |
| 386 | .BROKEN_PIPE => return error.BrokenPipe, | 439 | .OffsetHigh = @truncate(u32, off >> 32), |
| 387 | else => |err| return unexpectedError(err), | 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 | } | ||
| 388 | } | 487 | } |
| 389 | } | 488 | } |
| 390 | } | 489 | } |
lib/std/special/test_runner.zig+24-2| ... | @@ -2,7 +2,7 @@ const std = @import("std"); | ... | @@ -2,7 +2,7 @@ const std = @import("std"); |
| 2 | const io = std.io; | 2 | const io = std.io; |
| 3 | const builtin = @import("builtin"); | 3 | const builtin = @import("builtin"); |
| 4 | 4 | ||
| 5 | pub const io_mode = builtin.test_io_mode; | 5 | pub const io_mode: io.Mode = builtin.test_io_mode; |
| 6 | 6 | ||
| 7 | pub fn main() anyerror!void { | 7 | pub fn main() anyerror!void { |
| 8 | const test_fn_list = builtin.test_functions; | 8 | const test_fn_list = builtin.test_functions; |
| ... | @@ -14,6 +14,11 @@ pub fn main() anyerror!void { | ... | @@ -14,6 +14,11 @@ pub fn main() anyerror!void { |
| 14 | error.TimerUnsupported => @panic("timer unsupported"), | 14 | error.TimerUnsupported => @panic("timer unsupported"), |
| 15 | }; | 15 | }; |
| 16 | 16 | ||
| 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 | |||
| 17 | for (test_fn_list) |test_fn, i| { | 22 | for (test_fn_list) |test_fn, i| { |
| 18 | std.testing.base_allocator_instance.reset(); | 23 | std.testing.base_allocator_instance.reset(); |
| 19 | 24 | ||
| ... | @@ -23,7 +28,24 @@ pub fn main() anyerror!void { | ... | @@ -23,7 +28,24 @@ pub fn main() anyerror!void { |
| 23 | if (progress.terminal == null) { | 28 | if (progress.terminal == null) { |
| 24 | std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name }); | 29 | std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name }); |
| 25 | } | 30 | } |
| 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) |_| { | ||
| 27 | ok_count += 1; | 49 | ok_count += 1; |
| 28 | test_node.end(); | 50 | test_node.end(); |
| 29 | std.testing.allocator_instance.validate() catch |err| switch (err) { | 51 | 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) { | ... | @@ -6144,6 +6144,15 @@ static bool scope_needs_spill(Scope *scope) { |
| 6144 | zig_unreachable(); | 6144 | zig_unreachable(); |
| 6145 | } | 6145 | } |
| 6146 | 6146 | ||
| 6147 | static 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 | |||
| 6147 | static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { | 6156 | static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6148 | Error err; | 6157 | Error err; |
| 6149 | 6158 | ||
| ... | @@ -6245,6 +6254,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { | ... | @@ -6245,6 +6254,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6245 | } | 6254 | } |
| 6246 | ZigFn *callee = call->fn_entry; | 6255 | ZigFn *callee = call->fn_entry; |
| 6247 | if (callee == nullptr) { | 6256 | if (callee == nullptr) { |
| 6257 | if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) { | ||
| 6258 | continue; | ||
| 6259 | } | ||
| 6248 | add_node_error(g, call->base.base.source_node, | 6260 | add_node_error(g, call->base.base.source_node, |
| 6249 | buf_sprintf("function is not comptime-known; @asyncCall required")); | 6261 | buf_sprintf("function is not comptime-known; @asyncCall required")); |
| 6250 | return ErrorSemanticAnalyzeFail; | 6262 | return ErrorSemanticAnalyzeFail; |
| ... | @@ -6402,7 +6414,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { | ... | @@ -6402,7 +6414,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6402 | } else { | 6414 | } else { |
| 6403 | param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i); | 6415 | param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i); |
| 6404 | } | 6416 | } |
| 6405 | ZigType *param_type = param_info->type; | 6417 | ZigType *param_type = resolve_type_isf(param_info->type); |
| 6406 | if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) { | 6418 | if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) { |
| 6407 | return err; | 6419 | return err; |
| 6408 | } | 6420 | } |
| ... | @@ -6421,7 +6433,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { | ... | @@ -6421,7 +6433,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6421 | instruction->field_index = SIZE_MAX; | 6433 | instruction->field_index = SIZE_MAX; |
| 6422 | ZigType *ptr_type = instruction->base.value->type; | 6434 | ZigType *ptr_type = instruction->base.value->type; |
| 6423 | assert(ptr_type->id == ZigTypeIdPointer); | 6435 | 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); |
| 6425 | if (!type_has_bits(child_type)) | 6437 | if (!type_has_bits(child_type)) |
| 6426 | continue; | 6438 | continue; |
| 6427 | if (instruction->base.base.ref_count == 0) | 6439 | if (instruction->base.base.ref_count == 0) |
| ... | @@ -6448,8 +6460,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { | ... | @@ -6448,8 +6460,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6448 | } | 6460 | } |
| 6449 | instruction->field_index = fields.length; | 6461 | instruction->field_index = fields.length; |
| 6450 | 6462 | ||
| 6451 | src_assert(child_type->id != ZigTypeIdPointer || child_type->data.pointer.inferred_struct_field == nullptr, | ||
| 6452 | instruction->base.base.source_node); | ||
| 6453 | fields.append({name, child_type, instruction->align}); | 6463 | fields.append({name, child_type, instruction->align}); |
| 6454 | } | 6464 | } |
| 6455 | 6465 | ||
| ... | @@ -8251,6 +8261,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS | ... | @@ -8251,6 +8261,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS |
| 8251 | size_t debug_field_index = 0; | 8261 | size_t debug_field_index = 0; |
| 8252 | for (size_t i = 0; i < field_count; i += 1) { | 8262 | for (size_t i = 0; i < field_count; i += 1) { |
| 8253 | TypeStructField *field = struct_type->data.structure.fields[i]; | 8263 | 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 | |||
| 8254 | size_t gen_field_index = field->gen_index; | 8266 | size_t gen_field_index = field->gen_index; |
| 8255 | if (gen_field_index == SIZE_MAX) { | 8267 | if (gen_field_index == SIZE_MAX) { |
| 8256 | continue; | 8268 | continue; |
src/codegen.cpp+24-18| ... | @@ -362,14 +362,16 @@ static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) { | ... | @@ -362,14 +362,16 @@ static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) { |
| 362 | } | 362 | } |
| 363 | 363 | ||
| 364 | // label (grep this): [fn_frame_struct_layout] | 364 | // label (grep this): [fn_frame_struct_layout] |
| 365 | static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) { | 365 | static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) { |
| 366 | uint32_t result = frame_index_arg(g, fn_type_id->return_type); | 366 | size_t field_index = 6; |
| 367 | for (size_t i = 0; i < fn_type_id->param_count; i += 1) { | 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 (type_has_bits(fn_type_id->param_info->type)) { | 368 | if (have_stack_trace) { |
| 369 | result += 1; | 369 | field_index += 2; |
| 370 | } | ||
| 371 | } | 370 | } |
| 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; | ||
| 373 | } | 375 | } |
| 374 | 376 | ||
| 375 | 377 | ||
| ... | @@ -7742,7 +7744,7 @@ static void do_code_gen(CodeGen *g) { | ... | @@ -7742,7 +7744,7 @@ static void do_code_gen(CodeGen *g) { |
| 7742 | } | 7744 | } |
| 7743 | uint32_t trace_field_index_stack = UINT32_MAX; | 7745 | uint32_t trace_field_index_stack = UINT32_MAX; |
| 7744 | if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) { | 7746 | 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); |
| 7746 | g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, | 7748 | g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, |
| 7747 | trace_field_index_stack, ""); | 7749 | trace_field_index_stack, ""); |
| 7748 | } | 7750 | } |
| ... | @@ -9396,22 +9398,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) { | ... | @@ -9396,22 +9398,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) { |
| 9396 | for (size_t i = 0; i < g->test_fns.length; i += 1) { | 9398 | for (size_t i = 0; i < g->test_fns.length; i += 1) { |
| 9397 | ZigFn *test_fn_entry = g->test_fns.at(i); | 9399 | ZigFn *test_fn_entry = g->test_fns.at(i); |
| 9398 | 9400 | ||
| 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 | |||
| 9408 | ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i]; | 9401 | ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i]; |
| 9409 | this_val->special = ConstValSpecialStatic; | 9402 | this_val->special = ConstValSpecialStatic; |
| 9410 | this_val->type = struct_type; | 9403 | this_val->type = struct_type; |
| 9411 | this_val->parent.id = ConstParentIdArray; | 9404 | this_val->parent.id = ConstParentIdArray; |
| 9412 | this_val->parent.data.p_array.array_val = test_fn_array; | 9405 | this_val->parent.data.p_array.array_val = test_fn_array; |
| 9413 | this_val->parent.data.p_array.elem_index = i; | 9406 | 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); |
| 9415 | 9408 | ||
| 9416 | ZigValue *name_field = this_val->data.x_struct.fields[0]; | 9409 | ZigValue *name_field = this_val->data.x_struct.fields[0]; |
| 9417 | ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee; | 9410 | 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) { | ... | @@ -9423,6 +9416,19 @@ static void update_test_functions_builtin_decl(CodeGen *g) { |
| 9423 | fn_field->data.x_ptr.special = ConstPtrSpecialFunction; | 9416 | fn_field->data.x_ptr.special = ConstPtrSpecialFunction; |
| 9424 | fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst; | 9417 | fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst; |
| 9425 | fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry; | 9418 | 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 | } | ||
| 9426 | } | 9432 | } |
| 9427 | report_errors_and_maybe_exit(g); | 9433 | report_errors_and_maybe_exit(g); |
| 9428 | 9434 |