authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-09 13:38:42-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-02-09 13:38:42-08:00
log54bbc73f8502fe073d385361ddb34a43d12eec39
tree6eb554a4639f2c05bdfa5fc94553edc7f5df1650
parentd3cf911a803912a5aabe7a8d130c8b6468b95ff1
parent318e9cdaaae2f01c1a6e5db9cf66fe875821787e
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #18712 from Vexu/std.options

std: make options a struct instance instead of a namespace

50 files changed, 186 insertions(+), 4751 deletions(-)

CMakeLists.txt-3
......@@ -233,9 +233,6 @@ set(ZIG_STAGE2_SOURCES
233233 "${CMAKE_SOURCE_DIR}/lib/std/dwarf/OP.zig"
234234 "${CMAKE_SOURCE_DIR}/lib/std/dwarf/TAG.zig"
235235 "${CMAKE_SOURCE_DIR}/lib/std/elf.zig"
236 "${CMAKE_SOURCE_DIR}/lib/std/event.zig"
237 "${CMAKE_SOURCE_DIR}/lib/std/event/batch.zig"
238 "${CMAKE_SOURCE_DIR}/lib/std/event/loop.zig"
239236 "${CMAKE_SOURCE_DIR}/lib/std/fifo.zig"
240237 "${CMAKE_SOURCE_DIR}/lib/std/fmt.zig"
241238 "${CMAKE_SOURCE_DIR}/lib/std/fmt/errol.zig"
doc/langref.html.in+1-1
......@@ -10139,7 +10139,7 @@ pub fn main() void {
1013910139
1014010140 {#header_open|Invalid Error Set Cast#}
1014110141 <p>At compile-time:</p>
10142 {#code_begin|test_err|test_comptime_invalid_error_set_cast|'error.B' not a member of error set 'error{C,A}'#}
10142 {#code_begin|test_err|test_comptime_invalid_error_set_cast|'error.B' not a member of error set 'error{A,C}'#}
1014310143const Set1 = error{
1014410144 A,
1014510145 B,
lib/std/Build/Step/Compile.zig-5
......@@ -55,7 +55,6 @@ global_base: ?u64 = null,
5555zig_lib_dir: ?LazyPath,
5656exec_cmd_args: ?[]const ?[]const u8,
5757filter: ?[]const u8,
58test_evented_io: bool = false,
5958test_runner: ?[]const u8,
6059test_server_mode: bool,
6160wasi_exec_model: ?std.builtin.WasiExecModel = null,
......@@ -1307,10 +1306,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
13071306 try zig_args.append(filter);
13081307 }
13091308
1310 if (self.test_evented_io) {
1311 try zig_args.append("--test-evented-io");
1312 }
1313
13141309 if (self.test_runner) |test_runner| {
13151310 try zig_args.append("--test-runner");
13161311 try zig_args.append(b.pathFromRoot(test_runner));
lib/std/Build/Step/Run.zig+2-10
......@@ -1147,19 +1147,14 @@ fn evalZigTest(
11471147 test_count = tm_hdr.tests_len;
11481148
11491149 const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)];
1150 const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)];
1151 const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)];
1152 const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len];
1150 const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)];
1151 const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len];
11531152
11541153 const names = std.mem.bytesAsSlice(u32, names_bytes);
1155 const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes);
11561154 const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes);
11571155 const names_aligned = try arena.alloc(u32, names.len);
11581156 for (names_aligned, names) |*dest, src| dest.* = src;
11591157
1160 const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len);
1161 for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src;
1162
11631158 const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len);
11641159 for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src;
11651160
......@@ -1167,7 +1162,6 @@ fn evalZigTest(
11671162 metadata = .{
11681163 .string_bytes = try arena.dupe(u8, string_bytes),
11691164 .names = names_aligned,
1170 .async_frame_lens = async_frame_lens_aligned,
11711165 .expected_panic_msgs = expected_panic_msgs_aligned,
11721166 .next_index = 0,
11731167 .prog_node = prog_node,
......@@ -1237,7 +1231,6 @@ fn evalZigTest(
12371231
12381232const TestMetadata = struct {
12391233 names: []const u32,
1240 async_frame_lens: []const u32,
12411234 expected_panic_msgs: []const u32,
12421235 string_bytes: []const u8,
12431236 next_index: u32,
......@@ -1253,7 +1246,6 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr
12531246 const i = metadata.next_index;
12541247 metadata.next_index += 1;
12551248
1256 if (metadata.async_frame_lens[i] != 0) continue;
12571249 if (metadata.expected_panic_msgs[i] != 0) continue;
12581250
12591251 const name = metadata.testName(i);
lib/std/builtin.zig-1
......@@ -738,7 +738,6 @@ pub const CompilerBackend = enum(u64) {
738738pub const TestFn = struct {
739739 name: []const u8,
740740 func: *const fn () anyerror!void,
741 async_frame_size: ?usize,
742741};
743742
744743/// This function type is used by the Zig language code generation and
lib/std/child_process.zig+3-12
......@@ -495,7 +495,7 @@ pub const ChildProcess = struct {
495495 }
496496
497497 fn spawnPosix(self: *ChildProcess) SpawnError!void {
498 const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0;
498 const pipe_flags = 0;
499499 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
500500 errdefer if (self.stdin_behavior == StdIo.Pipe) {
501501 destroyPipe(stdin_pipe);
......@@ -667,7 +667,6 @@ pub const ChildProcess = struct {
667667 .share_access = windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE,
668668 .sa = &saAttr,
669669 .creation = windows.OPEN_EXISTING,
670 .io_mode = .blocking,
671670 }) catch |err| switch (err) {
672671 error.PathAlreadyExists => unreachable, // not possible for "NUL"
673672 error.PipeBusy => unreachable, // not possible for "NUL"
......@@ -1493,20 +1492,12 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
14931492const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
14941493
14951494fn writeIntFd(fd: i32, value: ErrInt) !void {
1496 const file = File{
1497 .handle = fd,
1498 .capable_io_mode = .blocking,
1499 .intended_io_mode = .blocking,
1500 };
1495 const file = File{ .handle = fd };
15011496 file.writer().writeInt(u64, @intCast(value), .little) catch return error.SystemResources;
15021497}
15031498
15041499fn readIntFd(fd: i32) !ErrInt {
1505 const file = File{
1506 .handle = fd,
1507 .capable_io_mode = .blocking,
1508 .intended_io_mode = .blocking,
1509 };
1500 const file = File{ .handle = fd };
15101501 return @as(ErrInt, @intCast(file.reader().readInt(u64, .little) catch return error.SystemResources));
15111502}
15121503
lib/std/debug.zig+5-8
......@@ -1141,8 +1141,8 @@ pub fn readElfDebugInfo(
11411141) !ModuleDebugInfo {
11421142 nosuspend {
11431143 const elf_file = (if (elf_filename) |filename| blk: {
1144 break :blk fs.cwd().openFile(filename, .{ .intended_io_mode = .blocking });
1145 } else fs.openSelfExe(.{ .intended_io_mode = .blocking })) catch |err| switch (err) {
1144 break :blk fs.cwd().openFile(filename, .{});
1145 } else fs.openSelfExe(.{})) catch |err| switch (err) {
11461146 error.FileNotFound => return error.MissingDebugInfo,
11471147 else => return err,
11481148 };
......@@ -1452,7 +1452,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn
14521452fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
14531453 // Need this to always block even in async I/O mode, because this could potentially
14541454 // be called from e.g. the event loop code crashing.
1455 var f = try fs.cwd().openFile(line_info.file_name, .{ .intended_io_mode = .blocking });
1455 var f = try fs.cwd().openFile(line_info.file_name, .{});
14561456 defer f.close();
14571457 // TODO fstat and make sure that the file has the correct size
14581458
......@@ -1640,7 +1640,6 @@ const MachoSymbol = struct {
16401640 }
16411641};
16421642
1643/// `file` is expected to have been opened with .intended_io_mode == .blocking.
16441643/// Takes ownership of file, even on error.
16451644/// TODO it's weird to take ownership even on error, rework this code.
16461645fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
......@@ -1824,9 +1823,7 @@ pub const DebugInfo = struct {
18241823 errdefer self.allocator.destroy(obj_di);
18251824
18261825 const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
1827 const macho_file = fs.cwd().openFile(macho_path, .{
1828 .intended_io_mode = .blocking,
1829 }) catch |err| switch (err) {
1826 const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
18301827 error.FileNotFound => return error.MissingDebugInfo,
18311828 else => return err,
18321829 };
......@@ -2162,7 +2159,7 @@ pub const ModuleDebugInfo = switch (native_os) {
21622159 }
21632160
21642161 fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo {
2165 const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking });
2162 const o_file = try fs.cwd().openFile(o_file_path, .{});
21662163 const mapped_mem = try mapWholeFile(o_file);
21672164
21682165 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
lib/std/event.zig deleted-23
......@@ -1,23 +0,0 @@
1pub const Channel = @import("event/channel.zig").Channel;
2pub const Future = @import("event/future.zig").Future;
3pub const Group = @import("event/group.zig").Group;
4pub const Batch = @import("event/batch.zig").Batch;
5pub const Lock = @import("event/lock.zig").Lock;
6pub const Locked = @import("event/locked.zig").Locked;
7pub const RwLock = @import("event/rwlock.zig").RwLock;
8pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
9pub const Loop = @import("event/loop.zig").Loop;
10pub const WaitGroup = @import("event/wait_group.zig").WaitGroup;
11
12test {
13 _ = @import("event/channel.zig");
14 _ = @import("event/future.zig");
15 _ = @import("event/group.zig");
16 _ = @import("event/batch.zig");
17 _ = @import("event/lock.zig");
18 _ = @import("event/locked.zig");
19 _ = @import("event/rwlock.zig");
20 _ = @import("event/rwlocked.zig");
21 _ = @import("event/loop.zig");
22 _ = @import("event/wait_group.zig");
23}
lib/std/event/batch.zig deleted-141
......@@ -1,141 +0,0 @@
1const std = @import("../std.zig");
2const testing = std.testing;
3
4/// Performs multiple async functions in parallel, without heap allocation.
5/// Async function frames are managed externally to this abstraction, and
6/// passed in via the `add` function. Once all the jobs are added, call `wait`.
7/// This API is *not* thread-safe. The object must be accessed from one thread at
8/// a time, however, it need not be the same thread.
9pub fn Batch(
10 /// The return value for each job.
11 /// If a job slot was re-used due to maxed out concurrency, then its result
12 /// value will be overwritten. The values can be accessed with the `results` field.
13 comptime Result: type,
14 /// How many jobs to run in parallel.
15 comptime max_jobs: comptime_int,
16 /// Controls whether the `add` and `wait` functions will be async functions.
17 comptime async_behavior: enum {
18 /// Observe the value of `std.io.is_async` to decide whether `add`
19 /// and `wait` will be async functions. Asserts that the jobs do not suspend when
20 /// `std.options.io_mode == .blocking`. This is a generally safe assumption, and the
21 /// usual recommended option for this parameter.
22 auto_async,
23
24 /// Always uses the `nosuspend` keyword when using `await` on the jobs,
25 /// making `add` and `wait` non-async functions. Asserts that the jobs do not suspend.
26 never_async,
27
28 /// `add` and `wait` use regular `await` keyword, making them async functions.
29 always_async,
30 },
31) type {
32 return struct {
33 jobs: [max_jobs]Job,
34 next_job_index: usize,
35 collected_result: CollectedResult,
36
37 const Job = struct {
38 frame: ?anyframe->Result,
39 result: Result,
40 };
41
42 const Self = @This();
43
44 const CollectedResult = switch (@typeInfo(Result)) {
45 .ErrorUnion => Result,
46 else => void,
47 };
48
49 const async_ok = switch (async_behavior) {
50 .auto_async => std.io.is_async,
51 .never_async => false,
52 .always_async => true,
53 };
54
55 pub fn init() Self {
56 return Self{
57 .jobs = [1]Job{
58 .{
59 .frame = null,
60 .result = undefined,
61 },
62 } ** max_jobs,
63 .next_job_index = 0,
64 .collected_result = {},
65 };
66 }
67
68 /// Add a frame to the Batch. If all jobs are in-flight, then this function
69 /// waits until one completes.
70 /// This function is *not* thread-safe. It must be called from one thread at
71 /// a time, however, it need not be the same thread.
72 /// TODO: "select" language feature to use the next available slot, rather than
73 /// awaiting the next index.
74 pub fn add(self: *Self, frame: anyframe->Result) void {
75 const job = &self.jobs[self.next_job_index];
76 self.next_job_index = (self.next_job_index + 1) % max_jobs;
77 if (job.frame) |existing| {
78 job.result = if (async_ok) await existing else nosuspend await existing;
79 if (CollectedResult != void) {
80 job.result catch |err| {
81 self.collected_result = err;
82 };
83 }
84 }
85 job.frame = frame;
86 }
87
88 /// Wait for all the jobs to complete.
89 /// Safe to call any number of times.
90 /// If `Result` is an error union, this function returns the last error that occurred, if any.
91 /// Unlike the `results` field, the return value of `wait` will report any error that occurred;
92 /// hitting max parallelism will not compromise the result.
93 /// This function is *not* thread-safe. It must be called from one thread at
94 /// a time, however, it need not be the same thread.
95 pub fn wait(self: *Self) CollectedResult {
96 for (self.jobs) |*job|
97 if (job.frame) |f| {
98 job.result = if (async_ok) await f else nosuspend await f;
99 if (CollectedResult != void) {
100 job.result catch |err| {
101 self.collected_result = err;
102 };
103 }
104 job.frame = null;
105 };
106 return self.collected_result;
107 }
108 };
109}
110
111test "std.event.Batch" {
112 if (true) return error.SkipZigTest;
113 var count: usize = 0;
114 var batch = Batch(void, 2, .auto_async).init();
115 batch.add(&async sleepALittle(&count));
116 batch.add(&async increaseByTen(&count));
117 batch.wait();
118 try testing.expect(count == 11);
119
120 var another = Batch(anyerror!void, 2, .auto_async).init();
121 another.add(&async somethingElse());
122 another.add(&async doSomethingThatFails());
123 try testing.expectError(error.ItBroke, another.wait());
124}
125
126fn sleepALittle(count: *usize) void {
127 std.time.sleep(1 * std.time.ns_per_ms);
128 _ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
129}
130
131fn increaseByTen(count: *usize) void {
132 var i: usize = 0;
133 while (i < 10) : (i += 1) {
134 _ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
135 }
136}
137
138fn doSomethingThatFails() anyerror!void {}
139fn somethingElse() anyerror!void {
140 return error.ItBroke;
141}
lib/std/event/channel.zig deleted-334
......@@ -1,334 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const testing = std.testing;
5const Loop = std.event.Loop;
6
7/// Many producer, many consumer, thread-safe, runtime configurable buffer size.
8/// When buffer is empty, consumers suspend and are resumed by producers.
9/// When buffer is full, producers suspend and are resumed by consumers.
10pub fn Channel(comptime T: type) type {
11 return struct {
12 getters: std.atomic.Queue(GetNode),
13 or_null_queue: std.atomic.Queue(*std.atomic.Queue(GetNode).Node),
14 putters: std.atomic.Queue(PutNode),
15 get_count: usize,
16 put_count: usize,
17 dispatch_lock: bool,
18 need_dispatch: bool,
19
20 // simple fixed size ring buffer
21 buffer_nodes: []T,
22 buffer_index: usize,
23 buffer_len: usize,
24
25 const SelfChannel = @This();
26 const GetNode = struct {
27 tick_node: *Loop.NextTickNode,
28 data: Data,
29
30 const Data = union(enum) {
31 Normal: Normal,
32 OrNull: OrNull,
33 };
34
35 const Normal = struct {
36 ptr: *T,
37 };
38
39 const OrNull = struct {
40 ptr: *?T,
41 or_null: *std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node,
42 };
43 };
44 const PutNode = struct {
45 data: T,
46 tick_node: *Loop.NextTickNode,
47 };
48
49 const global_event_loop = Loop.instance orelse
50 @compileError("std.event.Channel currently only works with event-based I/O");
51
52 /// Call `deinit` to free resources when done.
53 /// `buffer` must live until `deinit` is called.
54 /// For a zero length buffer, use `[0]T{}`.
55 /// TODO https://github.com/ziglang/zig/issues/2765
56 pub fn init(self: *SelfChannel, buffer: []T) void {
57 // The ring buffer implementation only works with power of 2 buffer sizes
58 // because of relying on subtracting across zero. For example (0 -% 1) % 10 == 5
59 assert(buffer.len == 0 or @popCount(buffer.len) == 1);
60
61 self.* = SelfChannel{
62 .buffer_len = 0,
63 .buffer_nodes = buffer,
64 .buffer_index = 0,
65 .dispatch_lock = false,
66 .need_dispatch = false,
67 .getters = std.atomic.Queue(GetNode).init(),
68 .putters = std.atomic.Queue(PutNode).init(),
69 .or_null_queue = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).init(),
70 .get_count = 0,
71 .put_count = 0,
72 };
73 }
74
75 /// Must be called when all calls to put and get have suspended and no more calls occur.
76 /// This can be omitted if caller can guarantee that the suspended putters and getters
77 /// do not need to be run to completion. Note that this may leave awaiters hanging.
78 pub fn deinit(self: *SelfChannel) void {
79 while (self.getters.get()) |get_node| {
80 resume get_node.data.tick_node.data;
81 }
82 while (self.putters.get()) |put_node| {
83 resume put_node.data.tick_node.data;
84 }
85 self.* = undefined;
86 }
87
88 /// puts a data item in the channel. The function returns when the value has been added to the
89 /// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter.
90 /// Or when the channel is destroyed.
91 pub fn put(self: *SelfChannel, data: T) void {
92 var my_tick_node = Loop.NextTickNode{ .data = @frame() };
93 var queue_node = std.atomic.Queue(PutNode).Node{
94 .data = PutNode{
95 .tick_node = &my_tick_node,
96 .data = data,
97 },
98 };
99
100 suspend {
101 self.putters.put(&queue_node);
102 _ = @atomicRmw(usize, &self.put_count, .Add, 1, .SeqCst);
103
104 self.dispatch();
105 }
106 }
107
108 /// await this function to get an item from the channel. If the buffer is empty, the frame will
109 /// complete when the next item is put in the channel.
110 pub fn get(self: *SelfChannel) callconv(.Async) T {
111 // TODO https://github.com/ziglang/zig/issues/2765
112 var result: T = undefined;
113 var my_tick_node = Loop.NextTickNode{ .data = @frame() };
114 var queue_node = std.atomic.Queue(GetNode).Node{
115 .data = GetNode{
116 .tick_node = &my_tick_node,
117 .data = GetNode.Data{
118 .Normal = GetNode.Normal{ .ptr = &result },
119 },
120 },
121 };
122
123 suspend {
124 self.getters.put(&queue_node);
125 _ = @atomicRmw(usize, &self.get_count, .Add, 1, .SeqCst);
126
127 self.dispatch();
128 }
129 return result;
130 }
131
132 //pub async fn select(comptime EnumUnion: type, channels: ...) EnumUnion {
133 // assert(@memberCount(EnumUnion) == channels.len); // enum union and channels mismatch
134 // assert(channels.len != 0); // enum unions cannot have 0 fields
135 // if (channels.len == 1) {
136 // const result = await (async channels[0].get() catch unreachable);
137 // return @unionInit(EnumUnion, @memberName(EnumUnion, 0), result);
138 // }
139 //}
140
141 /// Get an item from the channel. If the buffer is empty and there are no
142 /// puts waiting, this returns `null`.
143 pub fn getOrNull(self: *SelfChannel) ?T {
144 // TODO integrate this function with named return values
145 // so we can get rid of this extra result copy
146 var result: ?T = null;
147 var my_tick_node = Loop.NextTickNode{ .data = @frame() };
148 var or_null_node = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node{ .data = undefined };
149 var queue_node = std.atomic.Queue(GetNode).Node{
150 .data = GetNode{
151 .tick_node = &my_tick_node,
152 .data = GetNode.Data{
153 .OrNull = GetNode.OrNull{
154 .ptr = &result,
155 .or_null = &or_null_node,
156 },
157 },
158 },
159 };
160 or_null_node.data = &queue_node;
161
162 suspend {
163 self.getters.put(&queue_node);
164 _ = @atomicRmw(usize, &self.get_count, .Add, 1, .SeqCst);
165 self.or_null_queue.put(&or_null_node);
166
167 self.dispatch();
168 }
169 return result;
170 }
171
172 fn dispatch(self: *SelfChannel) void {
173 // set the "need dispatch" flag
174 @atomicStore(bool, &self.need_dispatch, true, .SeqCst);
175
176 lock: while (true) {
177 // set the lock flag
178 if (@atomicRmw(bool, &self.dispatch_lock, .Xchg, true, .SeqCst)) return;
179
180 // clear the need_dispatch flag since we're about to do it
181 @atomicStore(bool, &self.need_dispatch, false, .SeqCst);
182
183 while (true) {
184 one_dispatch: {
185 // later we correct these extra subtractions
186 var get_count = @atomicRmw(usize, &self.get_count, .Sub, 1, .SeqCst);
187 var put_count = @atomicRmw(usize, &self.put_count, .Sub, 1, .SeqCst);
188
189 // transfer self.buffer to self.getters
190 while (self.buffer_len != 0) {
191 if (get_count == 0) break :one_dispatch;
192
193 const get_node = &self.getters.get().?.data;
194 switch (get_node.data) {
195 GetNode.Data.Normal => |info| {
196 info.ptr.* = self.buffer_nodes[(self.buffer_index -% self.buffer_len) % self.buffer_nodes.len];
197 },
198 GetNode.Data.OrNull => |info| {
199 _ = self.or_null_queue.remove(info.or_null);
200 info.ptr.* = self.buffer_nodes[(self.buffer_index -% self.buffer_len) % self.buffer_nodes.len];
201 },
202 }
203 global_event_loop.onNextTick(get_node.tick_node);
204 self.buffer_len -= 1;
205
206 get_count = @atomicRmw(usize, &self.get_count, .Sub, 1, .SeqCst);
207 }
208
209 // direct transfer self.putters to self.getters
210 while (get_count != 0 and put_count != 0) {
211 const get_node = &self.getters.get().?.data;
212 const put_node = &self.putters.get().?.data;
213
214 switch (get_node.data) {
215 GetNode.Data.Normal => |info| {
216 info.ptr.* = put_node.data;
217 },
218 GetNode.Data.OrNull => |info| {
219 _ = self.or_null_queue.remove(info.or_null);
220 info.ptr.* = put_node.data;
221 },
222 }
223 global_event_loop.onNextTick(get_node.tick_node);
224 global_event_loop.onNextTick(put_node.tick_node);
225
226 get_count = @atomicRmw(usize, &self.get_count, .Sub, 1, .SeqCst);
227 put_count = @atomicRmw(usize, &self.put_count, .Sub, 1, .SeqCst);
228 }
229
230 // transfer self.putters to self.buffer
231 while (self.buffer_len != self.buffer_nodes.len and put_count != 0) {
232 const put_node = &self.putters.get().?.data;
233
234 self.buffer_nodes[self.buffer_index % self.buffer_nodes.len] = put_node.data;
235 global_event_loop.onNextTick(put_node.tick_node);
236 self.buffer_index +%= 1;
237 self.buffer_len += 1;
238
239 put_count = @atomicRmw(usize, &self.put_count, .Sub, 1, .SeqCst);
240 }
241 }
242
243 // undo the extra subtractions
244 _ = @atomicRmw(usize, &self.get_count, .Add, 1, .SeqCst);
245 _ = @atomicRmw(usize, &self.put_count, .Add, 1, .SeqCst);
246
247 // All the "get or null" functions should resume now.
248 var remove_count: usize = 0;
249 while (self.or_null_queue.get()) |or_null_node| {
250 remove_count += @intFromBool(self.getters.remove(or_null_node.data));
251 global_event_loop.onNextTick(or_null_node.data.data.tick_node);
252 }
253 if (remove_count != 0) {
254 _ = @atomicRmw(usize, &self.get_count, .Sub, remove_count, .SeqCst);
255 }
256
257 // clear need-dispatch flag
258 if (@atomicRmw(bool, &self.need_dispatch, .Xchg, false, .SeqCst)) continue;
259
260 assert(@atomicRmw(bool, &self.dispatch_lock, .Xchg, false, .SeqCst));
261
262 // we have to check again now that we unlocked
263 if (@atomicLoad(bool, &self.need_dispatch, .SeqCst)) continue :lock;
264
265 return;
266 }
267 }
268 }
269 };
270}
271
272test "std.event.Channel" {
273 if (!std.io.is_async) return error.SkipZigTest;
274
275 // https://github.com/ziglang/zig/issues/1908
276 if (builtin.single_threaded) return error.SkipZigTest;
277
278 // https://github.com/ziglang/zig/issues/3251
279 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
280
281 var channel: Channel(i32) = undefined;
282 channel.init(&[0]i32{});
283 defer channel.deinit();
284
285 var handle = async testChannelGetter(&channel);
286 var putter = async testChannelPutter(&channel);
287
288 await handle;
289 await putter;
290}
291
292test "std.event.Channel wraparound" {
293
294 // TODO provide a way to run tests in evented I/O mode
295 if (!std.io.is_async) return error.SkipZigTest;
296
297 const channel_size = 2;
298
299 var buf: [channel_size]i32 = undefined;
300 var channel: Channel(i32) = undefined;
301 channel.init(&buf);
302 defer channel.deinit();
303
304 // add items to channel and pull them out until
305 // the buffer wraps around, make sure it doesn't crash.
306 channel.put(5);
307 try testing.expectEqual(@as(i32, 5), channel.get());
308 channel.put(6);
309 try testing.expectEqual(@as(i32, 6), channel.get());
310 channel.put(7);
311 try testing.expectEqual(@as(i32, 7), channel.get());
312}
313fn testChannelGetter(channel: *Channel(i32)) callconv(.Async) void {
314 const value1 = channel.get();
315 try testing.expect(value1 == 1234);
316
317 const value2 = channel.get();
318 try testing.expect(value2 == 4567);
319
320 const value3 = channel.getOrNull();
321 try testing.expect(value3 == null);
322
323 var last_put = async testPut(channel, 4444);
324 const value4 = channel.getOrNull();
325 try testing.expect(value4.? == 4444);
326 await last_put;
327}
328fn testChannelPutter(channel: *Channel(i32)) callconv(.Async) void {
329 channel.put(1234);
330 channel.put(4567);
331}
332fn testPut(channel: *Channel(i32), value: i32) callconv(.Async) void {
333 channel.put(value);
334}
lib/std/event/future.zig deleted-115
......@@ -1,115 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const testing = std.testing;
5const Lock = std.event.Lock;
6
7/// This is a value that starts out unavailable, until resolve() is called.
8/// While it is unavailable, functions suspend when they try to get() it,
9/// and then are resumed when resolve() is called.
10/// At this point the value remains forever available, and another resolve() is not allowed.
11pub fn Future(comptime T: type) type {
12 return struct {
13 lock: Lock,
14 data: T,
15 available: Available,
16
17 const Available = enum(u8) {
18 NotStarted,
19 Started,
20 Finished,
21 };
22
23 const Self = @This();
24 const Queue = std.atomic.Queue(anyframe);
25
26 pub fn init() Self {
27 return Self{
28 .lock = Lock.initLocked(),
29 .available = .NotStarted,
30 .data = undefined,
31 };
32 }
33
34 /// Obtain the value. If it's not available, wait until it becomes
35 /// available.
36 /// Thread-safe.
37 pub fn get(self: *Self) callconv(.Async) *T {
38 if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) {
39 return &self.data;
40 }
41 const held = self.lock.acquire();
42 held.release();
43
44 return &self.data;
45 }
46
47 /// Gets the data without waiting for it. If it's available, a pointer is
48 /// returned. Otherwise, null is returned.
49 pub fn getOrNull(self: *Self) ?*T {
50 if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) {
51 return &self.data;
52 } else {
53 return null;
54 }
55 }
56
57 /// If someone else has started working on the data, wait for them to complete
58 /// and return a pointer to the data. Otherwise, return null, and the caller
59 /// should start working on the data.
60 /// It's not required to call start() before resolve() but it can be useful since
61 /// this method is thread-safe.
62 pub fn start(self: *Self) callconv(.Async) ?*T {
63 const state = @cmpxchgStrong(Available, &self.available, .NotStarted, .Started, .SeqCst, .SeqCst) orelse return null;
64 switch (state) {
65 .Started => {
66 const held = self.lock.acquire();
67 held.release();
68 return &self.data;
69 },
70 .Finished => return &self.data,
71 else => unreachable,
72 }
73 }
74
75 /// Make the data become available. May be called only once.
76 /// Before calling this, modify the `data` property.
77 pub fn resolve(self: *Self) void {
78 const prev = @atomicRmw(Available, &self.available, .Xchg, .Finished, .SeqCst);
79 assert(prev != .Finished); // resolve() called twice
80 Lock.Held.release(Lock.Held{ .lock = &self.lock });
81 }
82 };
83}
84
85test "std.event.Future" {
86 // https://github.com/ziglang/zig/issues/1908
87 if (builtin.single_threaded) return error.SkipZigTest;
88 // https://github.com/ziglang/zig/issues/3251
89 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
90 // TODO provide a way to run tests in evented I/O mode
91 if (!std.io.is_async) return error.SkipZigTest;
92
93 testFuture();
94}
95
96fn testFuture() void {
97 var future = Future(i32).init();
98
99 var a = async waitOnFuture(&future);
100 var b = async waitOnFuture(&future);
101 resolveFuture(&future);
102
103 const result = (await a) + (await b);
104
105 try testing.expect(result == 12);
106}
107
108fn waitOnFuture(future: *Future(i32)) i32 {
109 return future.get().*;
110}
111
112fn resolveFuture(future: *Future(i32)) void {
113 future.data = 6;
114 future.resolve();
115}
lib/std/event/group.zig deleted-160
......@@ -1,160 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const Lock = std.event.Lock;
4const testing = std.testing;
5const Allocator = std.mem.Allocator;
6
7/// ReturnType must be `void` or `E!void`
8/// TODO This API was created back with the old design of async/await, when calling any
9/// async function required an allocator. There is an ongoing experiment to transition
10/// all uses of this API to the simpler and more resource-aware `std.event.Batch` API.
11/// If the transition goes well, all usages of `Group` will be gone, and this API
12/// will be deleted.
13pub fn Group(comptime ReturnType: type) type {
14 return struct {
15 frame_stack: Stack,
16 alloc_stack: AllocStack,
17 lock: Lock,
18 allocator: Allocator,
19
20 const Self = @This();
21
22 const Error = switch (@typeInfo(ReturnType)) {
23 .ErrorUnion => |payload| payload.error_set,
24 else => void,
25 };
26 const Stack = std.atomic.Stack(anyframe->ReturnType);
27 const AllocStack = std.atomic.Stack(Node);
28
29 pub const Node = struct {
30 bytes: []const u8 = &[0]u8{},
31 handle: anyframe->ReturnType,
32 };
33
34 pub fn init(allocator: Allocator) Self {
35 return Self{
36 .frame_stack = Stack.init(),
37 .alloc_stack = AllocStack.init(),
38 .lock = .{},
39 .allocator = allocator,
40 };
41 }
42
43 /// Add a frame to the group. Thread-safe.
44 pub fn add(self: *Self, handle: anyframe->ReturnType) (error{OutOfMemory}!void) {
45 const node = try self.allocator.create(AllocStack.Node);
46 node.* = AllocStack.Node{
47 .next = undefined,
48 .data = Node{
49 .handle = handle,
50 },
51 };
52 self.alloc_stack.push(node);
53 }
54
55 /// Add a node to the group. Thread-safe. Cannot fail.
56 /// `node.data` should be the frame handle to add to the group.
57 /// The node's memory should be in the function frame of
58 /// the handle that is in the node, or somewhere guaranteed to live
59 /// at least as long.
60 pub fn addNode(self: *Self, node: *Stack.Node) void {
61 self.frame_stack.push(node);
62 }
63
64 /// This is equivalent to adding a frame to the group but the memory of its frame is
65 /// allocated by the group and freed by `wait`.
66 /// `func` must be async and have return type `ReturnType`.
67 /// Thread-safe.
68 pub fn call(self: *Self, comptime func: anytype, args: anytype) error{OutOfMemory}!void {
69 const frame = try self.allocator.create(@TypeOf(@call(.{ .modifier = .async_kw }, func, args)));
70 errdefer self.allocator.destroy(frame);
71 const node = try self.allocator.create(AllocStack.Node);
72 errdefer self.allocator.destroy(node);
73 node.* = AllocStack.Node{
74 .next = undefined,
75 .data = Node{
76 .handle = frame,
77 .bytes = std.mem.asBytes(frame),
78 },
79 };
80 frame.* = @call(.{ .modifier = .async_kw }, func, args);
81 self.alloc_stack.push(node);
82 }
83
84 /// Wait for all the calls and promises of the group to complete.
85 /// Thread-safe.
86 /// Safe to call any number of times.
87 pub fn wait(self: *Self) callconv(.Async) ReturnType {
88 const held = self.lock.acquire();
89 defer held.release();
90
91 var result: ReturnType = {};
92
93 while (self.frame_stack.pop()) |node| {
94 if (Error == void) {
95 await node.data;
96 } else {
97 (await node.data) catch |err| {
98 result = err;
99 };
100 }
101 }
102 while (self.alloc_stack.pop()) |node| {
103 const handle = node.data.handle;
104 if (Error == void) {
105 await handle;
106 } else {
107 (await handle) catch |err| {
108 result = err;
109 };
110 }
111 self.allocator.free(node.data.bytes);
112 self.allocator.destroy(node);
113 }
114 return result;
115 }
116 };
117}
118
119test "std.event.Group" {
120 // https://github.com/ziglang/zig/issues/1908
121 if (builtin.single_threaded) return error.SkipZigTest;
122
123 if (!std.io.is_async) return error.SkipZigTest;
124
125 // TODO this file has bit-rotted. repair it
126 if (true) return error.SkipZigTest;
127
128 _ = async testGroup(std.heap.page_allocator);
129}
130fn testGroup(allocator: Allocator) callconv(.Async) void {
131 var count: usize = 0;
132 var group = Group(void).init(allocator);
133 var sleep_a_little_frame = async sleepALittle(&count);
134 group.add(&sleep_a_little_frame) catch @panic("memory");
135 var increase_by_ten_frame = async increaseByTen(&count);
136 group.add(&increase_by_ten_frame) catch @panic("memory");
137 group.wait();
138 try testing.expect(count == 11);
139
140 var another = Group(anyerror!void).init(allocator);
141 var something_else_frame = async somethingElse();
142 another.add(&something_else_frame) catch @panic("memory");
143 var something_that_fails_frame = async doSomethingThatFails();
144 another.add(&something_that_fails_frame) catch @panic("memory");
145 try testing.expectError(error.ItBroke, another.wait());
146}
147fn sleepALittle(count: *usize) callconv(.Async) void {
148 std.time.sleep(1 * std.time.ns_per_ms);
149 _ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
150}
151fn increaseByTen(count: *usize) callconv(.Async) void {
152 var i: usize = 0;
153 while (i < 10) : (i += 1) {
154 _ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
155 }
156}
157fn doSomethingThatFails() callconv(.Async) anyerror!void {}
158fn somethingElse() callconv(.Async) anyerror!void {
159 return error.ItBroke;
160}
lib/std/event/lock.zig deleted-162
......@@ -1,162 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const testing = std.testing;
5const mem = std.mem;
6const Loop = std.event.Loop;
7
8/// Thread-safe async/await lock.
9/// Functions which are waiting for the lock are suspended, and
10/// are resumed when the lock is released, in order.
11/// Allows only one actor to hold the lock.
12/// TODO: make this API also work in blocking I/O mode.
13pub const Lock = struct {
14 mutex: std.Thread.Mutex = std.Thread.Mutex{},
15 head: usize = UNLOCKED,
16
17 const UNLOCKED = 0;
18 const LOCKED = 1;
19
20 const global_event_loop = Loop.instance orelse
21 @compileError("std.event.Lock currently only works with event-based I/O");
22
23 const Waiter = struct {
24 // forced Waiter alignment to ensure it doesn't clash with LOCKED
25 next: ?*Waiter align(2),
26 tail: *Waiter,
27 node: Loop.NextTickNode,
28 };
29
30 pub fn initLocked() Lock {
31 return Lock{ .head = LOCKED };
32 }
33
34 pub fn acquire(self: *Lock) Held {
35 self.mutex.lock();
36
37 // self.head transitions from multiple stages depending on the value:
38 // UNLOCKED -> LOCKED:
39 // acquire Lock ownership when there are no waiters
40 // LOCKED -> <Waiter head ptr>:
41 // Lock is already owned, enqueue first Waiter
42 // <head ptr> -> <head ptr>:
43 // Lock is owned with pending waiters. Push our waiter to the queue.
44
45 if (self.head == UNLOCKED) {
46 self.head = LOCKED;
47 self.mutex.unlock();
48 return Held{ .lock = self };
49 }
50
51 var waiter: Waiter = undefined;
52 waiter.next = null;
53 waiter.tail = &waiter;
54
55 const head = switch (self.head) {
56 UNLOCKED => unreachable,
57 LOCKED => null,
58 else => @as(*Waiter, @ptrFromInt(self.head)),
59 };
60
61 if (head) |h| {
62 h.tail.next = &waiter;
63 h.tail = &waiter;
64 } else {
65 self.head = @intFromPtr(&waiter);
66 }
67
68 suspend {
69 waiter.node = Loop.NextTickNode{
70 .prev = undefined,
71 .next = undefined,
72 .data = @frame(),
73 };
74 self.mutex.unlock();
75 }
76
77 return Held{ .lock = self };
78 }
79
80 pub const Held = struct {
81 lock: *Lock,
82
83 pub fn release(self: Held) void {
84 const waiter = blk: {
85 self.lock.mutex.lock();
86 defer self.lock.mutex.unlock();
87
88 // self.head goes through the reverse transition from acquire():
89 // <head ptr> -> <new head ptr>:
90 // pop a waiter from the queue to give Lock ownership when there are still others pending
91 // <head ptr> -> LOCKED:
92 // pop the laster waiter from the queue, while also giving it lock ownership when awaken
93 // LOCKED -> UNLOCKED:
94 // last lock owner releases lock while no one else is waiting for it
95
96 switch (self.lock.head) {
97 UNLOCKED => {
98 unreachable; // Lock unlocked while unlocking
99 },
100 LOCKED => {
101 self.lock.head = UNLOCKED;
102 break :blk null;
103 },
104 else => {
105 const waiter = @as(*Waiter, @ptrFromInt(self.lock.head));
106 self.lock.head = if (waiter.next == null) LOCKED else @intFromPtr(waiter.next);
107 if (waiter.next) |next|
108 next.tail = waiter.tail;
109 break :blk waiter;
110 },
111 }
112 };
113
114 if (waiter) |w| {
115 global_event_loop.onNextTick(&w.node);
116 }
117 }
118 };
119};
120
121test "std.event.Lock" {
122 if (!std.io.is_async) return error.SkipZigTest;
123
124 // TODO https://github.com/ziglang/zig/issues/1908
125 if (builtin.single_threaded) return error.SkipZigTest;
126
127 // TODO https://github.com/ziglang/zig/issues/3251
128 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
129
130 var lock = Lock{};
131 testLock(&lock);
132
133 const expected_result = [1]i32{3 * @as(i32, @intCast(shared_test_data.len))} ** shared_test_data.len;
134 try testing.expectEqualSlices(i32, &expected_result, &shared_test_data);
135}
136fn testLock(lock: *Lock) void {
137 var handle1 = async lockRunner(lock);
138 var handle2 = async lockRunner(lock);
139 var handle3 = async lockRunner(lock);
140
141 await handle1;
142 await handle2;
143 await handle3;
144}
145
146var shared_test_data = [1]i32{0} ** 10;
147var shared_test_index: usize = 0;
148
149fn lockRunner(lock: *Lock) void {
150 Lock.global_event_loop.yield();
151
152 var i: usize = 0;
153 while (i < shared_test_data.len) : (i += 1) {
154 const handle = lock.acquire();
155 defer handle.release();
156
157 shared_test_index = 0;
158 while (shared_test_index < shared_test_data.len) : (shared_test_index += 1) {
159 shared_test_data[shared_test_index] = shared_test_data[shared_test_index] + 1;
160 }
161 }
162}
lib/std/event/locked.zig deleted-42
......@@ -1,42 +0,0 @@
1const std = @import("../std.zig");
2const Lock = std.event.Lock;
3
4/// Thread-safe async/await lock that protects one piece of data.
5/// Functions which are waiting for the lock are suspended, and
6/// are resumed when the lock is released, in order.
7pub fn Locked(comptime T: type) type {
8 return struct {
9 lock: Lock,
10 private_data: T,
11
12 const Self = @This();
13
14 pub const HeldLock = struct {
15 value: *T,
16 held: Lock.Held,
17
18 pub fn release(self: HeldLock) void {
19 self.held.release();
20 }
21 };
22
23 pub fn init(data: T) Self {
24 return Self{
25 .lock = .{},
26 .private_data = data,
27 };
28 }
29
30 pub fn deinit(self: *Self) void {
31 self.lock.deinit();
32 }
33
34 pub fn acquire(self: *Self) callconv(.Async) HeldLock {
35 return HeldLock{
36 // TODO guaranteed allocation elision
37 .held = self.lock.acquire(),
38 .value = &self.private_data,
39 };
40 }
41 };
42}
lib/std/event/loop.zig deleted-1791
......@@ -1,1791 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const testing = std.testing;
5const mem = std.mem;
6const os = std.os;
7const windows = os.windows;
8const maxInt = std.math.maxInt;
9const Thread = std.Thread;
10
11const is_windows = builtin.os.tag == .windows;
12
13pub const Loop = struct {
14 next_tick_queue: std.atomic.Queue(anyframe),
15 os_data: OsData,
16 final_resume_node: ResumeNode,
17 pending_event_count: usize,
18 extra_threads: []Thread,
19 /// TODO change this to a pool of configurable number of threads
20 /// and rename it to be not file-system-specific. it will become
21 /// a thread pool for turning non-CPU-bound blocking things into
22 /// async things. A fallback for any missing OS-specific API.
23 fs_thread: Thread,
24 fs_queue: std.atomic.Queue(Request),
25 fs_end_request: Request.Node,
26 fs_thread_wakeup: std.Thread.ResetEvent,
27
28 /// For resources that have the same lifetime as the `Loop`.
29 /// This is only used by `Loop` for the thread pool and associated resources.
30 arena: std.heap.ArenaAllocator,
31
32 /// State which manages frames that are sleeping on timers
33 delay_queue: DelayQueue,
34
35 /// Pre-allocated eventfds. All permanently active.
36 /// This is how `Loop` sends promises to be resumed on other threads.
37 available_eventfd_resume_nodes: std.atomic.Stack(ResumeNode.EventFd),
38 eventfd_resume_nodes: []std.atomic.Stack(ResumeNode.EventFd).Node,
39
40 pub const NextTickNode = std.atomic.Queue(anyframe).Node;
41
42 pub const ResumeNode = struct {
43 id: Id,
44 handle: anyframe,
45 overlapped: Overlapped,
46
47 pub const overlapped_init = switch (builtin.os.tag) {
48 .windows => windows.OVERLAPPED{
49 .Internal = 0,
50 .InternalHigh = 0,
51 .DUMMYUNIONNAME = .{
52 .DUMMYSTRUCTNAME = .{
53 .Offset = 0,
54 .OffsetHigh = 0,
55 },
56 },
57 .hEvent = null,
58 },
59 else => {},
60 };
61 pub const Overlapped = @TypeOf(overlapped_init);
62
63 pub const Id = enum {
64 basic,
65 stop,
66 event_fd,
67 };
68
69 pub const EventFd = switch (builtin.os.tag) {
70 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => KEventFd,
71 .linux => struct {
72 base: ResumeNode,
73 epoll_op: u32,
74 eventfd: i32,
75 },
76 .windows => struct {
77 base: ResumeNode,
78 completion_key: usize,
79 },
80 else => struct {},
81 };
82
83 const KEventFd = struct {
84 base: ResumeNode,
85 kevent: os.Kevent,
86 };
87
88 pub const Basic = switch (builtin.os.tag) {
89 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => KEventBasic,
90 .linux => struct {
91 base: ResumeNode,
92 },
93 .windows => struct {
94 base: ResumeNode,
95 },
96 else => @compileError("unsupported OS"),
97 };
98
99 const KEventBasic = struct {
100 base: ResumeNode,
101 kev: os.Kevent,
102 };
103 };
104
105 pub const Instance = switch (std.options.io_mode) {
106 .blocking => @TypeOf(null),
107 .evented => ?*Loop,
108 };
109 pub const instance = std.options.event_loop;
110
111 var global_instance_state: Loop = undefined;
112 pub const default_instance = switch (std.options.io_mode) {
113 .blocking => null,
114 .evented => &global_instance_state,
115 };
116
117 pub const Mode = enum {
118 single_threaded,
119 multi_threaded,
120 };
121 pub const default_mode = .multi_threaded;
122
123 /// TODO copy elision / named return values so that the threads referencing *Loop
124 /// have the correct pointer value.
125 /// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
126 pub fn init(self: *Loop) !void {
127 if (builtin.single_threaded or std.options.event_loop_mode == .single_threaded) {
128 return self.initSingleThreaded();
129 } else {
130 return self.initMultiThreaded();
131 }
132 }
133
134 /// After initialization, call run().
135 /// TODO copy elision / named return values so that the threads referencing *Loop
136 /// have the correct pointer value.
137 /// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
138 pub fn initSingleThreaded(self: *Loop) !void {
139 return self.initThreadPool(1);
140 }
141
142 /// After initialization, call run().
143 /// This is the same as `initThreadPool` using `Thread.getCpuCount` to determine the thread
144 /// pool size.
145 /// TODO copy elision / named return values so that the threads referencing *Loop
146 /// have the correct pointer value.
147 /// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
148 pub fn initMultiThreaded(self: *Loop) !void {
149 if (builtin.single_threaded)
150 @compileError("initMultiThreaded unavailable when building in single-threaded mode");
151 const core_count = try Thread.getCpuCount();
152 return self.initThreadPool(core_count);
153 }
154
155 /// Thread count is the total thread count. The thread pool size will be
156 /// max(thread_count - 1, 0)
157 pub fn initThreadPool(self: *Loop, thread_count: usize) !void {
158 self.* = Loop{
159 .arena = std.heap.ArenaAllocator.init(std.heap.page_allocator),
160 .pending_event_count = 1,
161 .os_data = undefined,
162 .next_tick_queue = std.atomic.Queue(anyframe).init(),
163 .extra_threads = undefined,
164 .available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(),
165 .eventfd_resume_nodes = undefined,
166 .final_resume_node = ResumeNode{
167 .id = .stop,
168 .handle = undefined,
169 .overlapped = ResumeNode.overlapped_init,
170 },
171 .fs_end_request = .{ .data = .{ .msg = .end, .finish = .no_action } },
172 .fs_queue = std.atomic.Queue(Request).init(),
173 .fs_thread = undefined,
174 .fs_thread_wakeup = .{},
175 .delay_queue = undefined,
176 };
177 errdefer self.arena.deinit();
178
179 // We need at least one of these in case the fs thread wants to use onNextTick
180 const extra_thread_count = thread_count - 1;
181 const resume_node_count = @max(extra_thread_count, 1);
182 self.eventfd_resume_nodes = try self.arena.allocator().alloc(
183 std.atomic.Stack(ResumeNode.EventFd).Node,
184 resume_node_count,
185 );
186
187 self.extra_threads = try self.arena.allocator().alloc(Thread, extra_thread_count);
188
189 try self.initOsData(extra_thread_count);
190 errdefer self.deinitOsData();
191
192 if (!builtin.single_threaded) {
193 self.fs_thread = try Thread.spawn(.{}, posixFsRun, .{self});
194 }
195 errdefer if (!builtin.single_threaded) {
196 self.posixFsRequest(&self.fs_end_request);
197 self.fs_thread.join();
198 };
199
200 if (!builtin.single_threaded)
201 try self.delay_queue.init();
202 }
203
204 pub fn deinit(self: *Loop) void {
205 self.deinitOsData();
206 self.arena.deinit();
207 self.* = undefined;
208 }
209
210 const InitOsDataError = os.EpollCreateError || mem.Allocator.Error || os.EventFdError ||
211 Thread.SpawnError || os.EpollCtlError || os.KEventError ||
212 windows.CreateIoCompletionPortError;
213
214 const wakeup_bytes = [_]u8{0x1} ** 8;
215
216 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
217 nosuspend switch (builtin.os.tag) {
218 .linux => {
219 errdefer {
220 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
221 }
222 for (self.eventfd_resume_nodes) |*eventfd_node| {
223 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
224 .data = ResumeNode.EventFd{
225 .base = ResumeNode{
226 .id = .event_fd,
227 .handle = undefined,
228 .overlapped = ResumeNode.overlapped_init,
229 },
230 .eventfd = try os.eventfd(1, os.linux.EFD.CLOEXEC | os.linux.EFD.NONBLOCK),
231 .epoll_op = os.linux.EPOLL.CTL_ADD,
232 },
233 .next = undefined,
234 };
235 self.available_eventfd_resume_nodes.push(eventfd_node);
236 }
237
238 self.os_data.epollfd = try os.epoll_create1(os.linux.EPOLL.CLOEXEC);
239 errdefer os.close(self.os_data.epollfd);
240
241 self.os_data.final_eventfd = try os.eventfd(0, os.linux.EFD.CLOEXEC | os.linux.EFD.NONBLOCK);
242 errdefer os.close(self.os_data.final_eventfd);
243
244 self.os_data.final_eventfd_event = os.linux.epoll_event{
245 .events = os.linux.EPOLL.IN,
246 .data = os.linux.epoll_data{ .ptr = @intFromPtr(&self.final_resume_node) },
247 };
248 try os.epoll_ctl(
249 self.os_data.epollfd,
250 os.linux.EPOLL.CTL_ADD,
251 self.os_data.final_eventfd,
252 &self.os_data.final_eventfd_event,
253 );
254
255 if (builtin.single_threaded) {
256 assert(extra_thread_count == 0);
257 return;
258 }
259
260 var extra_thread_index: usize = 0;
261 errdefer {
262 // writing 8 bytes to an eventfd cannot fail
263 const amt = os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
264 assert(amt == wakeup_bytes.len);
265 while (extra_thread_index != 0) {
266 extra_thread_index -= 1;
267 self.extra_threads[extra_thread_index].join();
268 }
269 }
270 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
271 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
272 }
273 },
274 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly => {
275 self.os_data.kqfd = try os.kqueue();
276 errdefer os.close(self.os_data.kqfd);
277
278 const empty_kevs = &[0]os.Kevent{};
279
280 for (self.eventfd_resume_nodes, 0..) |*eventfd_node, i| {
281 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
282 .data = ResumeNode.EventFd{
283 .base = ResumeNode{
284 .id = .event_fd,
285 .handle = undefined,
286 .overlapped = ResumeNode.overlapped_init,
287 },
288 // this one is for sending events
289 .kevent = os.Kevent{
290 .ident = i,
291 .filter = os.system.EVFILT_USER,
292 .flags = os.system.EV_CLEAR | os.system.EV_ADD | os.system.EV_DISABLE,
293 .fflags = 0,
294 .data = 0,
295 .udata = @intFromPtr(&eventfd_node.data.base),
296 },
297 },
298 .next = undefined,
299 };
300 self.available_eventfd_resume_nodes.push(eventfd_node);
301 const kevent_array = @as(*const [1]os.Kevent, &eventfd_node.data.kevent);
302 _ = try os.kevent(self.os_data.kqfd, kevent_array, empty_kevs, null);
303 eventfd_node.data.kevent.flags = os.system.EV_CLEAR | os.system.EV_ENABLE;
304 eventfd_node.data.kevent.fflags = os.system.NOTE_TRIGGER;
305 }
306
307 // Pre-add so that we cannot get error.SystemResources
308 // later when we try to activate it.
309 self.os_data.final_kevent = os.Kevent{
310 .ident = extra_thread_count,
311 .filter = os.system.EVFILT_USER,
312 .flags = os.system.EV_ADD | os.system.EV_DISABLE,
313 .fflags = 0,
314 .data = 0,
315 .udata = @intFromPtr(&self.final_resume_node),
316 };
317 const final_kev_arr = @as(*const [1]os.Kevent, &self.os_data.final_kevent);
318 _ = try os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null);
319 self.os_data.final_kevent.flags = os.system.EV_ENABLE;
320 self.os_data.final_kevent.fflags = os.system.NOTE_TRIGGER;
321
322 if (builtin.single_threaded) {
323 assert(extra_thread_count == 0);
324 return;
325 }
326
327 var extra_thread_index: usize = 0;
328 errdefer {
329 _ = os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;
330 while (extra_thread_index != 0) {
331 extra_thread_index -= 1;
332 self.extra_threads[extra_thread_index].join();
333 }
334 }
335 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
336 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
337 }
338 },
339 .openbsd => {
340 self.os_data.kqfd = try os.kqueue();
341 errdefer os.close(self.os_data.kqfd);
342
343 const empty_kevs = &[0]os.Kevent{};
344
345 for (self.eventfd_resume_nodes, 0..) |*eventfd_node, i| {
346 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
347 .data = ResumeNode.EventFd{
348 .base = ResumeNode{
349 .id = .event_fd,
350 .handle = undefined,
351 .overlapped = ResumeNode.overlapped_init,
352 },
353 // this one is for sending events
354 .kevent = os.Kevent{
355 .ident = i,
356 .filter = os.system.EVFILT_TIMER,
357 .flags = os.system.EV_CLEAR | os.system.EV_ADD | os.system.EV_DISABLE | os.system.EV_ONESHOT,
358 .fflags = 0,
359 .data = 0,
360 .udata = @intFromPtr(&eventfd_node.data.base),
361 },
362 },
363 .next = undefined,
364 };
365 self.available_eventfd_resume_nodes.push(eventfd_node);
366 const kevent_array = @as(*const [1]os.Kevent, &eventfd_node.data.kevent);
367 _ = try os.kevent(self.os_data.kqfd, kevent_array, empty_kevs, null);
368 eventfd_node.data.kevent.flags = os.system.EV_CLEAR | os.system.EV_ENABLE;
369 }
370
371 // Pre-add so that we cannot get error.SystemResources
372 // later when we try to activate it.
373 self.os_data.final_kevent = os.Kevent{
374 .ident = extra_thread_count,
375 .filter = os.system.EVFILT_TIMER,
376 .flags = os.system.EV_ADD | os.system.EV_ONESHOT | os.system.EV_DISABLE,
377 .fflags = 0,
378 .data = 0,
379 .udata = @intFromPtr(&self.final_resume_node),
380 };
381 const final_kev_arr = @as(*const [1]os.Kevent, &self.os_data.final_kevent);
382 _ = try os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null);
383 self.os_data.final_kevent.flags = os.system.EV_ENABLE;
384
385 if (builtin.single_threaded) {
386 assert(extra_thread_count == 0);
387 return;
388 }
389
390 var extra_thread_index: usize = 0;
391 errdefer {
392 _ = os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;
393 while (extra_thread_index != 0) {
394 extra_thread_index -= 1;
395 self.extra_threads[extra_thread_index].join();
396 }
397 }
398 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
399 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
400 }
401 },
402 .windows => {
403 self.os_data.io_port = try windows.CreateIoCompletionPort(
404 windows.INVALID_HANDLE_VALUE,
405 null,
406 undefined,
407 maxInt(windows.DWORD),
408 );
409 errdefer windows.CloseHandle(self.os_data.io_port);
410
411 for (self.eventfd_resume_nodes) |*eventfd_node| {
412 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
413 .data = ResumeNode.EventFd{
414 .base = ResumeNode{
415 .id = .event_fd,
416 .handle = undefined,
417 .overlapped = ResumeNode.overlapped_init,
418 },
419 // this one is for sending events
420 .completion_key = @intFromPtr(&eventfd_node.data.base),
421 },
422 .next = undefined,
423 };
424 self.available_eventfd_resume_nodes.push(eventfd_node);
425 }
426
427 if (builtin.single_threaded) {
428 assert(extra_thread_count == 0);
429 return;
430 }
431
432 var extra_thread_index: usize = 0;
433 errdefer {
434 var i: usize = 0;
435 while (i < extra_thread_index) : (i += 1) {
436 while (true) {
437 const overlapped = &self.final_resume_node.overlapped;
438 windows.PostQueuedCompletionStatus(self.os_data.io_port, undefined, undefined, overlapped) catch continue;
439 break;
440 }
441 }
442 while (extra_thread_index != 0) {
443 extra_thread_index -= 1;
444 self.extra_threads[extra_thread_index].join();
445 }
446 }
447 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
448 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
449 }
450 },
451 else => {},
452 };
453 }
454
455 fn deinitOsData(self: *Loop) void {
456 nosuspend switch (builtin.os.tag) {
457 .linux => {
458 os.close(self.os_data.final_eventfd);
459 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
460 os.close(self.os_data.epollfd);
461 },
462 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => {
463 os.close(self.os_data.kqfd);
464 },
465 .windows => {
466 windows.CloseHandle(self.os_data.io_port);
467 },
468 else => {},
469 };
470 }
471
472 /// resume_node must live longer than the anyframe that it holds a reference to.
473 /// flags must contain EPOLLET
474 pub fn linuxAddFd(self: *Loop, fd: i32, resume_node: *ResumeNode, flags: u32) !void {
475 assert(flags & os.linux.EPOLL.ET == os.linux.EPOLL.ET);
476 self.beginOneEvent();
477 errdefer self.finishOneEvent();
478 try self.linuxModFd(
479 fd,
480 os.linux.EPOLL.CTL_ADD,
481 flags,
482 resume_node,
483 );
484 }
485
486 pub fn linuxModFd(self: *Loop, fd: i32, op: u32, flags: u32, resume_node: *ResumeNode) !void {
487 assert(flags & os.linux.EPOLL.ET == os.linux.EPOLL.ET);
488 var ev = os.linux.epoll_event{
489 .events = flags,
490 .data = os.linux.epoll_data{ .ptr = @intFromPtr(resume_node) },
491 };
492 try os.epoll_ctl(self.os_data.epollfd, op, fd, &ev);
493 }
494
495 pub fn linuxRemoveFd(self: *Loop, fd: i32) void {
496 os.epoll_ctl(self.os_data.epollfd, os.linux.EPOLL.CTL_DEL, fd, null) catch {};
497 self.finishOneEvent();
498 }
499
500 pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) void {
501 assert(flags & os.linux.EPOLL.ET == os.linux.EPOLL.ET);
502 assert(flags & os.linux.EPOLL.ONESHOT == os.linux.EPOLL.ONESHOT);
503 var resume_node = ResumeNode.Basic{
504 .base = ResumeNode{
505 .id = .basic,
506 .handle = @frame(),
507 .overlapped = ResumeNode.overlapped_init,
508 },
509 };
510 var need_to_delete = true;
511 defer if (need_to_delete) self.linuxRemoveFd(fd);
512
513 suspend {
514 self.linuxAddFd(fd, &resume_node.base, flags) catch |err| switch (err) {
515 error.FileDescriptorNotRegistered => unreachable,
516 error.OperationCausesCircularLoop => unreachable,
517 error.FileDescriptorIncompatibleWithEpoll => unreachable,
518 error.FileDescriptorAlreadyPresentInSet => unreachable, // evented writes to the same fd is not thread-safe
519
520 error.SystemResources,
521 error.UserResourceLimitReached,
522 error.Unexpected,
523 => {
524 need_to_delete = false;
525 // Fall back to a blocking poll(). Ideally this codepath is never hit, since
526 // epoll should be just fine. But this is better than incorrect behavior.
527 var poll_flags: i16 = 0;
528 if ((flags & os.linux.EPOLL.IN) != 0) poll_flags |= os.POLL.IN;
529 if ((flags & os.linux.EPOLL.OUT) != 0) poll_flags |= os.POLL.OUT;
530 var pfd = [1]os.pollfd{os.pollfd{
531 .fd = fd,
532 .events = poll_flags,
533 .revents = undefined,
534 }};
535 _ = os.poll(&pfd, -1) catch |poll_err| switch (poll_err) {
536 error.NetworkSubsystemFailed => unreachable, // only possible on windows
537
538 error.SystemResources,
539 error.Unexpected,
540 => {
541 // Even poll() didn't work. The best we can do now is sleep for a
542 // small duration and then hope that something changed.
543 std.time.sleep(1 * std.time.ns_per_ms);
544 },
545 };
546 resume @frame();
547 },
548 };
549 }
550 }
551
552 pub fn waitUntilFdReadable(self: *Loop, fd: os.fd_t) void {
553 switch (builtin.os.tag) {
554 .linux => {
555 self.linuxWaitFd(fd, os.linux.EPOLL.ET | os.linux.EPOLL.ONESHOT | os.linux.EPOLL.IN);
556 },
557 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => {
558 self.bsdWaitKev(@as(usize, @intCast(fd)), os.system.EVFILT_READ, os.system.EV_ONESHOT);
559 },
560 else => @compileError("Unsupported OS"),
561 }
562 }
563
564 pub fn waitUntilFdWritable(self: *Loop, fd: os.fd_t) void {
565 switch (builtin.os.tag) {
566 .linux => {
567 self.linuxWaitFd(fd, os.linux.EPOLL.ET | os.linux.EPOLL.ONESHOT | os.linux.EPOLL.OUT);
568 },
569 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => {
570 self.bsdWaitKev(@as(usize, @intCast(fd)), os.system.EVFILT_WRITE, os.system.EV_ONESHOT);
571 },
572 else => @compileError("Unsupported OS"),
573 }
574 }
575
576 pub fn waitUntilFdWritableOrReadable(self: *Loop, fd: os.fd_t) void {
577 switch (builtin.os.tag) {
578 .linux => {
579 self.linuxWaitFd(fd, os.linux.EPOLL.ET | os.linux.EPOLL.ONESHOT | os.linux.EPOLL.OUT | os.linux.EPOLL.IN);
580 },
581 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => {
582 self.bsdWaitKev(@as(usize, @intCast(fd)), os.system.EVFILT_READ, os.system.EV_ONESHOT);
583 self.bsdWaitKev(@as(usize, @intCast(fd)), os.system.EVFILT_WRITE, os.system.EV_ONESHOT);
584 },
585 else => @compileError("Unsupported OS"),
586 }
587 }
588
589 pub fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, flags: u16) void {
590 var resume_node = ResumeNode.Basic{
591 .base = ResumeNode{
592 .id = .basic,
593 .handle = @frame(),
594 .overlapped = ResumeNode.overlapped_init,
595 },
596 .kev = undefined,
597 };
598
599 defer {
600 // If the kevent was set to be ONESHOT, it doesn't need to be deleted manually.
601 if (flags & os.system.EV_ONESHOT != 0) {
602 self.bsdRemoveKev(ident, filter);
603 }
604 }
605
606 suspend {
607 self.bsdAddKev(&resume_node, ident, filter, flags) catch unreachable;
608 }
609 }
610
611 /// resume_node must live longer than the anyframe that it holds a reference to.
612 pub fn bsdAddKev(self: *Loop, resume_node: *ResumeNode.Basic, ident: usize, filter: i16, flags: u16) !void {
613 self.beginOneEvent();
614 errdefer self.finishOneEvent();
615 var kev = [1]os.Kevent{os.Kevent{
616 .ident = ident,
617 .filter = filter,
618 .flags = os.system.EV_ADD | os.system.EV_ENABLE | os.system.EV_CLEAR | flags,
619 .fflags = 0,
620 .data = 0,
621 .udata = @intFromPtr(&resume_node.base),
622 }};
623 const empty_kevs = &[0]os.Kevent{};
624 _ = try os.kevent(self.os_data.kqfd, &kev, empty_kevs, null);
625 }
626
627 pub fn bsdRemoveKev(self: *Loop, ident: usize, filter: i16) void {
628 var kev = [1]os.Kevent{os.Kevent{
629 .ident = ident,
630 .filter = filter,
631 .flags = os.system.EV_DELETE,
632 .fflags = 0,
633 .data = 0,
634 .udata = 0,
635 }};
636 const empty_kevs = &[0]os.Kevent{};
637 _ = os.kevent(self.os_data.kqfd, &kev, empty_kevs, null) catch undefined;
638 self.finishOneEvent();
639 }
640
641 fn dispatch(self: *Loop) void {
642 while (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| {
643 const next_tick_node = self.next_tick_queue.get() orelse {
644 self.available_eventfd_resume_nodes.push(resume_stack_node);
645 return;
646 };
647 const eventfd_node = &resume_stack_node.data;
648 eventfd_node.base.handle = next_tick_node.data;
649 switch (builtin.os.tag) {
650 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => {
651 const kevent_array = @as(*const [1]os.Kevent, &eventfd_node.kevent);
652 const empty_kevs = &[0]os.Kevent{};
653 _ = os.kevent(self.os_data.kqfd, kevent_array, empty_kevs, null) catch {
654 self.next_tick_queue.unget(next_tick_node);
655 self.available_eventfd_resume_nodes.push(resume_stack_node);
656 return;
657 };
658 },
659 .linux => {
660 // the pending count is already accounted for
661 const epoll_events = os.linux.EPOLL.ONESHOT | os.linux.EPOLL.IN | os.linux.EPOLL.OUT |
662 os.linux.EPOLL.ET;
663 self.linuxModFd(
664 eventfd_node.eventfd,
665 eventfd_node.epoll_op,
666 epoll_events,
667 &eventfd_node.base,
668 ) catch {
669 self.next_tick_queue.unget(next_tick_node);
670 self.available_eventfd_resume_nodes.push(resume_stack_node);
671 return;
672 };
673 },
674 .windows => {
675 windows.PostQueuedCompletionStatus(
676 self.os_data.io_port,
677 undefined,
678 undefined,
679 &eventfd_node.base.overlapped,
680 ) catch {
681 self.next_tick_queue.unget(next_tick_node);
682 self.available_eventfd_resume_nodes.push(resume_stack_node);
683 return;
684 };
685 },
686 else => @compileError("unsupported OS"),
687 }
688 }
689 }
690
691 /// Bring your own linked list node. This means it can't fail.
692 pub fn onNextTick(self: *Loop, node: *NextTickNode) void {
693 self.beginOneEvent(); // finished in dispatch()
694 self.next_tick_queue.put(node);
695 self.dispatch();
696 }
697
698 pub fn cancelOnNextTick(self: *Loop, node: *NextTickNode) void {
699 if (self.next_tick_queue.remove(node)) {
700 self.finishOneEvent();
701 }
702 }
703
704 pub fn run(self: *Loop) void {
705 self.finishOneEvent(); // the reference we start with
706
707 self.workerRun();
708
709 if (!builtin.single_threaded) {
710 switch (builtin.os.tag) {
711 .linux,
712 .macos,
713 .ios,
714 .tvos,
715 .watchos,
716 .freebsd,
717 .netbsd,
718 .dragonfly,
719 .openbsd,
720 => self.fs_thread.join(),
721 else => {},
722 }
723 }
724
725 for (self.extra_threads) |extra_thread| {
726 extra_thread.join();
727 }
728
729 self.delay_queue.deinit();
730 }
731
732 /// Runs the provided function asynchronously. The function's frame is allocated
733 /// with `allocator` and freed when the function returns.
734 /// `func` must return void and it can be an async function.
735 /// Yields to the event loop, running the function on the next tick.
736 pub fn runDetached(self: *Loop, alloc: mem.Allocator, comptime func: anytype, args: anytype) error{OutOfMemory}!void {
737 if (!std.io.is_async) @compileError("Can't use runDetached in non-async mode!");
738 if (@TypeOf(@call(.{}, func, args)) != void) {
739 @compileError("`func` must not have a return value");
740 }
741
742 const Wrapper = struct {
743 const Args = @TypeOf(args);
744 fn run(func_args: Args, loop: *Loop, allocator: mem.Allocator) void {
745 loop.beginOneEvent();
746 loop.yield();
747 @call(.{}, func, func_args); // compile error when called with non-void ret type
748 suspend {
749 loop.finishOneEvent();
750 allocator.destroy(@frame());
751 }
752 }
753 };
754
755 const run_frame = try alloc.create(@Frame(Wrapper.run));
756 run_frame.* = async Wrapper.run(args, self, alloc);
757 }
758
759 /// Yielding lets the event loop run, starting any unstarted async operations.
760 /// Note that async operations automatically start when a function yields for any other reason,
761 /// for example, when async I/O is performed. This function is intended to be used only when
762 /// CPU bound tasks would be waiting in the event loop but never get started because no async I/O
763 /// is performed.
764 pub fn yield(self: *Loop) void {
765 suspend {
766 var my_tick_node = NextTickNode{
767 .prev = undefined,
768 .next = undefined,
769 .data = @frame(),
770 };
771 self.onNextTick(&my_tick_node);
772 }
773 }
774
775 /// If the build is multi-threaded and there is an event loop, then it calls `yield`. Otherwise,
776 /// does nothing.
777 pub fn startCpuBoundOperation() void {
778 if (builtin.single_threaded) {
779 return;
780 } else if (instance) |event_loop| {
781 event_loop.yield();
782 }
783 }
784
785 /// call finishOneEvent when done
786 pub fn beginOneEvent(self: *Loop) void {
787 _ = @atomicRmw(usize, &self.pending_event_count, .Add, 1, .SeqCst);
788 }
789
790 pub fn finishOneEvent(self: *Loop) void {
791 nosuspend {
792 const prev = @atomicRmw(usize, &self.pending_event_count, .Sub, 1, .SeqCst);
793 if (prev != 1) return;
794
795 // cause all the threads to stop
796 self.posixFsRequest(&self.fs_end_request);
797
798 switch (builtin.os.tag) {
799 .linux => {
800 // writing to the eventfd will only wake up one thread, thus multiple writes
801 // are needed to wakeup all the threads
802 var i: usize = 0;
803 while (i < self.extra_threads.len + 1) : (i += 1) {
804 // writing 8 bytes to an eventfd cannot fail
805 const amt = os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
806 assert(amt == wakeup_bytes.len);
807 }
808 return;
809 },
810 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => {
811 const final_kevent = @as(*const [1]os.Kevent, &self.os_data.final_kevent);
812 const empty_kevs = &[0]os.Kevent{};
813 // cannot fail because we already added it and this just enables it
814 _ = os.kevent(self.os_data.kqfd, final_kevent, empty_kevs, null) catch unreachable;
815 return;
816 },
817 .windows => {
818 var i: usize = 0;
819 while (i < self.extra_threads.len + 1) : (i += 1) {
820 while (true) {
821 const overlapped = &self.final_resume_node.overlapped;
822 windows.PostQueuedCompletionStatus(self.os_data.io_port, undefined, undefined, overlapped) catch continue;
823 break;
824 }
825 }
826 return;
827 },
828 else => @compileError("unsupported OS"),
829 }
830 }
831 }
832
833 pub fn sleep(self: *Loop, nanoseconds: u64) void {
834 if (builtin.single_threaded)
835 @compileError("TODO: integrate timers with epoll/kevent/iocp for single-threaded");
836
837 suspend {
838 const now = self.delay_queue.timer.read();
839
840 var entry: DelayQueue.Waiters.Entry = undefined;
841 entry.init(@frame(), now + nanoseconds);
842 self.delay_queue.waiters.insert(&entry);
843
844 // Speculatively wake up the timer thread when we add a new entry.
845 // If the timer thread is sleeping on a longer entry, we need to
846 // interrupt it so that our entry can be expired in time.
847 self.delay_queue.event.set();
848 }
849 }
850
851 const DelayQueue = struct {
852 timer: std.time.Timer,
853 waiters: Waiters,
854 thread: std.Thread,
855 event: std.Thread.ResetEvent,
856 is_running: std.atomic.Value(bool),
857
858 /// Initialize the delay queue by spawning the timer thread
859 /// and starting any timer resources.
860 fn init(self: *DelayQueue) !void {
861 self.* = DelayQueue{
862 .timer = try std.time.Timer.start(),
863 .waiters = DelayQueue.Waiters{
864 .entries = std.atomic.Queue(anyframe).init(),
865 },
866 .thread = undefined,
867 .event = .{},
868 .is_running = std.atomic.Value(bool).init(true),
869 };
870
871 // Must be after init so that it can read the other state, such as `is_running`.
872 self.thread = try std.Thread.spawn(.{}, DelayQueue.run, .{self});
873 }
874
875 fn deinit(self: *DelayQueue) void {
876 self.is_running.store(false, .SeqCst);
877 self.event.set();
878 self.thread.join();
879 }
880
881 /// Entry point for the timer thread
882 /// which waits for timer entries to expire and reschedules them.
883 fn run(self: *DelayQueue) void {
884 const loop = @fieldParentPtr(Loop, "delay_queue", self);
885
886 while (self.is_running.load(.SeqCst)) {
887 self.event.reset();
888 const now = self.timer.read();
889
890 if (self.waiters.popExpired(now)) |entry| {
891 loop.onNextTick(&entry.node);
892 continue;
893 }
894
895 if (self.waiters.nextExpire()) |expires| {
896 if (now >= expires)
897 continue;
898 self.event.timedWait(expires - now) catch {};
899 } else {
900 self.event.wait();
901 }
902 }
903 }
904
905 // TODO: use a tickless hierarchical timer wheel:
906 // https://github.com/wahern/timeout/
907 const Waiters = struct {
908 entries: std.atomic.Queue(anyframe),
909
910 const Entry = struct {
911 node: NextTickNode,
912 expires: u64,
913
914 fn init(self: *Entry, frame: anyframe, expires: u64) void {
915 self.node.data = frame;
916 self.expires = expires;
917 }
918 };
919
920 /// Registers the entry into the queue of waiting frames
921 fn insert(self: *Waiters, entry: *Entry) void {
922 self.entries.put(&entry.node);
923 }
924
925 /// Dequeues one expired event relative to `now`
926 fn popExpired(self: *Waiters, now: u64) ?*Entry {
927 const entry = self.peekExpiringEntry() orelse return null;
928 if (entry.expires > now)
929 return null;
930
931 assert(self.entries.remove(&entry.node));
932 return entry;
933 }
934
935 /// Returns an estimate for the amount of time
936 /// to wait until the next waiting entry expires.
937 fn nextExpire(self: *Waiters) ?u64 {
938 const entry = self.peekExpiringEntry() orelse return null;
939 return entry.expires;
940 }
941
942 fn peekExpiringEntry(self: *Waiters) ?*Entry {
943 self.entries.mutex.lock();
944 defer self.entries.mutex.unlock();
945
946 // starting from the head
947 var head = self.entries.head orelse return null;
948
949 // traverse the list of waiting entries to
950 // find the Node with the smallest `expires` field
951 var min = head;
952 while (head.next) |node| {
953 const minEntry = @fieldParentPtr(Entry, "node", min);
954 const nodeEntry = @fieldParentPtr(Entry, "node", node);
955 if (nodeEntry.expires < minEntry.expires)
956 min = node;
957 head = node;
958 }
959
960 return @fieldParentPtr(Entry, "node", min);
961 }
962 };
963 };
964
965 /// ------- I/0 APIs -------
966 pub fn accept(
967 self: *Loop,
968 /// This argument is a socket that has been created with `socket`, bound to a local address
969 /// with `bind`, and is listening for connections after a `listen`.
970 sockfd: os.socket_t,
971 /// This argument is a pointer to a sockaddr structure. This structure is filled in with the
972 /// address of the peer socket, as known to the communications layer. The exact format of the
973 /// address returned addr is determined by the socket's address family (see `socket` and the
974 /// respective protocol man pages).
975 addr: *os.sockaddr,
976 /// This argument is a value-result argument: the caller must initialize it to contain the
977 /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size
978 /// of the peer address.
979 ///
980 /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size`
981 /// will return a value greater than was supplied to the call.
982 addr_size: *os.socklen_t,
983 /// The following values can be bitwise ORed in flags to obtain different behavior:
984 /// * `SOCK.CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
985 /// description of the `O.CLOEXEC` flag in `open` for reasons why this may be useful.
986 flags: u32,
987 ) os.AcceptError!os.socket_t {
988 while (true) {
989 return os.accept(sockfd, addr, addr_size, flags | os.SOCK.NONBLOCK) catch |err| switch (err) {
990 error.WouldBlock => {
991 self.waitUntilFdReadable(sockfd);
992 continue;
993 },
994 else => return err,
995 };
996 }
997 }
998
999 pub fn connect(self: *Loop, sockfd: os.socket_t, sock_addr: *const os.sockaddr, len: os.socklen_t) os.ConnectError!void {
1000 os.connect(sockfd, sock_addr, len) catch |err| switch (err) {
1001 error.WouldBlock => {
1002 self.waitUntilFdWritable(sockfd);
1003 return os.getsockoptError(sockfd);
1004 },
1005 else => return err,
1006 };
1007 }
1008
1009 /// Performs an async `os.open` using a separate thread.
1010 pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: os.mode_t) os.OpenError!os.fd_t {
1011 var req_node = Request.Node{
1012 .data = .{
1013 .msg = .{
1014 .open = .{
1015 .path = file_path,
1016 .flags = flags,
1017 .mode = mode,
1018 .result = undefined,
1019 },
1020 },
1021 .finish = .{ .tick_node = .{ .data = @frame() } },
1022 },
1023 };
1024 suspend {
1025 self.posixFsRequest(&req_node);
1026 }
1027 return req_node.data.msg.open.result;
1028 }
1029
1030 /// Performs an async `os.opent` using a separate thread.
1031 pub fn openatZ(self: *Loop, fd: os.fd_t, file_path: [*:0]const u8, flags: u32, mode: os.mode_t) os.OpenError!os.fd_t {
1032 var req_node = Request.Node{
1033 .data = .{
1034 .msg = .{
1035 .openat = .{
1036 .fd = fd,
1037 .path = file_path,
1038 .flags = flags,
1039 .mode = mode,
1040 .result = undefined,
1041 },
1042 },
1043 .finish = .{ .tick_node = .{ .data = @frame() } },
1044 },
1045 };
1046 suspend {
1047 self.posixFsRequest(&req_node);
1048 }
1049 return req_node.data.msg.openat.result;
1050 }
1051
1052 /// Performs an async `os.close` using a separate thread.
1053 pub fn close(self: *Loop, fd: os.fd_t) void {
1054 var req_node = Request.Node{
1055 .data = .{
1056 .msg = .{ .close = .{ .fd = fd } },
1057 .finish = .{ .tick_node = .{ .data = @frame() } },
1058 },
1059 };
1060 suspend {
1061 self.posixFsRequest(&req_node);
1062 }
1063 }
1064
1065 /// Performs an async `os.read` using a separate thread.
1066 /// `fd` must block and not return EAGAIN.
1067 pub fn read(self: *Loop, fd: os.fd_t, buf: []u8, simulate_evented: bool) os.ReadError!usize {
1068 if (simulate_evented) {
1069 var req_node = Request.Node{
1070 .data = .{
1071 .msg = .{
1072 .read = .{
1073 .fd = fd,
1074 .buf = buf,
1075 .result = undefined,
1076 },
1077 },
1078 .finish = .{ .tick_node = .{ .data = @frame() } },
1079 },
1080 };
1081 suspend {
1082 self.posixFsRequest(&req_node);
1083 }
1084 return req_node.data.msg.read.result;
1085 } else {
1086 while (true) {
1087 return os.read(fd, buf) catch |err| switch (err) {
1088 error.WouldBlock => {
1089 self.waitUntilFdReadable(fd);
1090 continue;
1091 },
1092 else => return err,
1093 };
1094 }
1095 }
1096 }
1097
1098 /// Performs an async `os.readv` using a separate thread.
1099 /// `fd` must block and not return EAGAIN.
1100 pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, simulate_evented: bool) os.ReadError!usize {
1101 if (simulate_evented) {
1102 var req_node = Request.Node{
1103 .data = .{
1104 .msg = .{
1105 .readv = .{
1106 .fd = fd,
1107 .iov = iov,
1108 .result = undefined,
1109 },
1110 },
1111 .finish = .{ .tick_node = .{ .data = @frame() } },
1112 },
1113 };
1114 suspend {
1115 self.posixFsRequest(&req_node);
1116 }
1117 return req_node.data.msg.readv.result;
1118 } else {
1119 while (true) {
1120 return os.readv(fd, iov) catch |err| switch (err) {
1121 error.WouldBlock => {
1122 self.waitUntilFdReadable(fd);
1123 continue;
1124 },
1125 else => return err,
1126 };
1127 }
1128 }
1129 }
1130
1131 /// Performs an async `os.pread` using a separate thread.
1132 /// `fd` must block and not return EAGAIN.
1133 pub fn pread(self: *Loop, fd: os.fd_t, buf: []u8, offset: u64, simulate_evented: bool) os.PReadError!usize {
1134 if (simulate_evented) {
1135 var req_node = Request.Node{
1136 .data = .{
1137 .msg = .{
1138 .pread = .{
1139 .fd = fd,
1140 .buf = buf,
1141 .offset = offset,
1142 .result = undefined,
1143 },
1144 },
1145 .finish = .{ .tick_node = .{ .data = @frame() } },
1146 },
1147 };
1148 suspend {
1149 self.posixFsRequest(&req_node);
1150 }
1151 return req_node.data.msg.pread.result;
1152 } else {
1153 while (true) {
1154 return os.pread(fd, buf, offset) catch |err| switch (err) {
1155 error.WouldBlock => {
1156 self.waitUntilFdReadable(fd);
1157 continue;
1158 },
1159 else => return err,
1160 };
1161 }
1162 }
1163 }
1164
1165 /// Performs an async `os.preadv` using a separate thread.
1166 /// `fd` must block and not return EAGAIN.
1167 pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64, simulate_evented: bool) os.ReadError!usize {
1168 if (simulate_evented) {
1169 var req_node = Request.Node{
1170 .data = .{
1171 .msg = .{
1172 .preadv = .{
1173 .fd = fd,
1174 .iov = iov,
1175 .offset = offset,
1176 .result = undefined,
1177 },
1178 },
1179 .finish = .{ .tick_node = .{ .data = @frame() } },
1180 },
1181 };
1182 suspend {
1183 self.posixFsRequest(&req_node);
1184 }
1185 return req_node.data.msg.preadv.result;
1186 } else {
1187 while (true) {
1188 return os.preadv(fd, iov, offset) catch |err| switch (err) {
1189 error.WouldBlock => {
1190 self.waitUntilFdReadable(fd);
1191 continue;
1192 },
1193 else => return err,
1194 };
1195 }
1196 }
1197 }
1198
1199 /// Performs an async `os.write` using a separate thread.
1200 /// `fd` must block and not return EAGAIN.
1201 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8, simulate_evented: bool) os.WriteError!usize {
1202 if (simulate_evented) {
1203 var req_node = Request.Node{
1204 .data = .{
1205 .msg = .{
1206 .write = .{
1207 .fd = fd,
1208 .bytes = bytes,
1209 .result = undefined,
1210 },
1211 },
1212 .finish = .{ .tick_node = .{ .data = @frame() } },
1213 },
1214 };
1215 suspend {
1216 self.posixFsRequest(&req_node);
1217 }
1218 return req_node.data.msg.write.result;
1219 } else {
1220 while (true) {
1221 return os.write(fd, bytes) catch |err| switch (err) {
1222 error.WouldBlock => {
1223 self.waitUntilFdWritable(fd);
1224 continue;
1225 },
1226 else => return err,
1227 };
1228 }
1229 }
1230 }
1231
1232 /// Performs an async `os.writev` using a separate thread.
1233 /// `fd` must block and not return EAGAIN.
1234 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, simulate_evented: bool) os.WriteError!usize {
1235 if (simulate_evented) {
1236 var req_node = Request.Node{
1237 .data = .{
1238 .msg = .{
1239 .writev = .{
1240 .fd = fd,
1241 .iov = iov,
1242 .result = undefined,
1243 },
1244 },
1245 .finish = .{ .tick_node = .{ .data = @frame() } },
1246 },
1247 };
1248 suspend {
1249 self.posixFsRequest(&req_node);
1250 }
1251 return req_node.data.msg.writev.result;
1252 } else {
1253 while (true) {
1254 return os.writev(fd, iov) catch |err| switch (err) {
1255 error.WouldBlock => {
1256 self.waitUntilFdWritable(fd);
1257 continue;
1258 },
1259 else => return err,
1260 };
1261 }
1262 }
1263 }
1264
1265 /// Performs an async `os.pwrite` using a separate thread.
1266 /// `fd` must block and not return EAGAIN.
1267 pub fn pwrite(self: *Loop, fd: os.fd_t, bytes: []const u8, offset: u64, simulate_evented: bool) os.PerformsWriteError!usize {
1268 if (simulate_evented) {
1269 var req_node = Request.Node{
1270 .data = .{
1271 .msg = .{
1272 .pwrite = .{
1273 .fd = fd,
1274 .bytes = bytes,
1275 .offset = offset,
1276 .result = undefined,
1277 },
1278 },
1279 .finish = .{ .tick_node = .{ .data = @frame() } },
1280 },
1281 };
1282 suspend {
1283 self.posixFsRequest(&req_node);
1284 }
1285 return req_node.data.msg.pwrite.result;
1286 } else {
1287 while (true) {
1288 return os.pwrite(fd, bytes, offset) catch |err| switch (err) {
1289 error.WouldBlock => {
1290 self.waitUntilFdWritable(fd);
1291 continue;
1292 },
1293 else => return err,
1294 };
1295 }
1296 }
1297 }
1298
1299 /// Performs an async `os.pwritev` using a separate thread.
1300 /// `fd` must block and not return EAGAIN.
1301 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64, simulate_evented: bool) os.PWriteError!usize {
1302 if (simulate_evented) {
1303 var req_node = Request.Node{
1304 .data = .{
1305 .msg = .{
1306 .pwritev = .{
1307 .fd = fd,
1308 .iov = iov,
1309 .offset = offset,
1310 .result = undefined,
1311 },
1312 },
1313 .finish = .{ .tick_node = .{ .data = @frame() } },
1314 },
1315 };
1316 suspend {
1317 self.posixFsRequest(&req_node);
1318 }
1319 return req_node.data.msg.pwritev.result;
1320 } else {
1321 while (true) {
1322 return os.pwritev(fd, iov, offset) catch |err| switch (err) {
1323 error.WouldBlock => {
1324 self.waitUntilFdWritable(fd);
1325 continue;
1326 },
1327 else => return err,
1328 };
1329 }
1330 }
1331 }
1332
1333 pub fn sendto(
1334 self: *Loop,
1335 /// The file descriptor of the sending socket.
1336 sockfd: os.fd_t,
1337 /// Message to send.
1338 buf: []const u8,
1339 flags: u32,
1340 dest_addr: ?*const os.sockaddr,
1341 addrlen: os.socklen_t,
1342 ) os.SendToError!usize {
1343 while (true) {
1344 return os.sendto(sockfd, buf, flags, dest_addr, addrlen) catch |err| switch (err) {
1345 error.WouldBlock => {
1346 self.waitUntilFdWritable(sockfd);
1347 continue;
1348 },
1349 else => return err,
1350 };
1351 }
1352 }
1353
1354 pub fn recvfrom(
1355 self: *Loop,
1356 sockfd: os.fd_t,
1357 buf: []u8,
1358 flags: u32,
1359 src_addr: ?*os.sockaddr,
1360 addrlen: ?*os.socklen_t,
1361 ) os.RecvFromError!usize {
1362 while (true) {
1363 return os.recvfrom(sockfd, buf, flags, src_addr, addrlen) catch |err| switch (err) {
1364 error.WouldBlock => {
1365 self.waitUntilFdReadable(sockfd);
1366 continue;
1367 },
1368 else => return err,
1369 };
1370 }
1371 }
1372
1373 /// Performs an async `os.faccessatZ` using a separate thread.
1374 /// `fd` must block and not return EAGAIN.
1375 pub fn faccessatZ(
1376 self: *Loop,
1377 dirfd: os.fd_t,
1378 path_z: [*:0]const u8,
1379 mode: u32,
1380 flags: u32,
1381 ) os.AccessError!void {
1382 var req_node = Request.Node{
1383 .data = .{
1384 .msg = .{
1385 .faccessat = .{
1386 .dirfd = dirfd,
1387 .path = path_z,
1388 .mode = mode,
1389 .flags = flags,
1390 .result = undefined,
1391 },
1392 },
1393 .finish = .{ .tick_node = .{ .data = @frame() } },
1394 },
1395 };
1396 suspend {
1397 self.posixFsRequest(&req_node);
1398 }
1399 return req_node.data.msg.faccessat.result;
1400 }
1401
1402 fn workerRun(self: *Loop) void {
1403 while (true) {
1404 while (true) {
1405 const next_tick_node = self.next_tick_queue.get() orelse break;
1406 self.dispatch();
1407 resume next_tick_node.data;
1408 self.finishOneEvent();
1409 }
1410
1411 switch (builtin.os.tag) {
1412 .linux => {
1413 // only process 1 event so we don't steal from other threads
1414 var events: [1]os.linux.epoll_event = undefined;
1415 const count = os.epoll_wait(self.os_data.epollfd, events[0..], -1);
1416 for (events[0..count]) |ev| {
1417 const resume_node = @as(*ResumeNode, @ptrFromInt(ev.data.ptr));
1418 const handle = resume_node.handle;
1419 const resume_node_id = resume_node.id;
1420 switch (resume_node_id) {
1421 .basic => {},
1422 .stop => return,
1423 .event_fd => {
1424 const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node);
1425 event_fd_node.epoll_op = os.linux.EPOLL.CTL_MOD;
1426 const stack_node = @fieldParentPtr(std.atomic.Stack(ResumeNode.EventFd).Node, "data", event_fd_node);
1427 self.available_eventfd_resume_nodes.push(stack_node);
1428 },
1429 }
1430 resume handle;
1431 if (resume_node_id == .event_fd) {
1432 self.finishOneEvent();
1433 }
1434 }
1435 },
1436 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => {
1437 var eventlist: [1]os.Kevent = undefined;
1438 const empty_kevs = &[0]os.Kevent{};
1439 const count = os.kevent(self.os_data.kqfd, empty_kevs, eventlist[0..], null) catch unreachable;
1440 for (eventlist[0..count]) |ev| {
1441 const resume_node = @as(*ResumeNode, @ptrFromInt(ev.udata));
1442 const handle = resume_node.handle;
1443 const resume_node_id = resume_node.id;
1444 switch (resume_node_id) {
1445 .basic => {
1446 const basic_node = @fieldParentPtr(ResumeNode.Basic, "base", resume_node);
1447 basic_node.kev = ev;
1448 },
1449 .stop => return,
1450 .event_fd => {
1451 const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node);
1452 const stack_node = @fieldParentPtr(std.atomic.Stack(ResumeNode.EventFd).Node, "data", event_fd_node);
1453 self.available_eventfd_resume_nodes.push(stack_node);
1454 },
1455 }
1456 resume handle;
1457 if (resume_node_id == .event_fd) {
1458 self.finishOneEvent();
1459 }
1460 }
1461 },
1462 .windows => {
1463 var completion_key: usize = undefined;
1464 const overlapped = while (true) {
1465 var nbytes: windows.DWORD = undefined;
1466 var overlapped: ?*windows.OVERLAPPED = undefined;
1467 switch (windows.GetQueuedCompletionStatus(self.os_data.io_port, &nbytes, &completion_key, &overlapped, windows.INFINITE)) {
1468 .Aborted => return,
1469 .Normal => {},
1470 .EOF => {},
1471 .Cancelled => continue,
1472 }
1473 if (overlapped) |o| break o;
1474 };
1475 const resume_node = @fieldParentPtr(ResumeNode, "overlapped", overlapped);
1476 const handle = resume_node.handle;
1477 const resume_node_id = resume_node.id;
1478 switch (resume_node_id) {
1479 .basic => {},
1480 .stop => return,
1481 .event_fd => {
1482 const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node);
1483 const stack_node = @fieldParentPtr(std.atomic.Stack(ResumeNode.EventFd).Node, "data", event_fd_node);
1484 self.available_eventfd_resume_nodes.push(stack_node);
1485 },
1486 }
1487 resume handle;
1488 self.finishOneEvent();
1489 },
1490 else => @compileError("unsupported OS"),
1491 }
1492 }
1493 }
1494
1495 fn posixFsRequest(self: *Loop, request_node: *Request.Node) void {
1496 self.beginOneEvent(); // finished in posixFsRun after processing the msg
1497 self.fs_queue.put(request_node);
1498 self.fs_thread_wakeup.set();
1499 }
1500
1501 fn posixFsCancel(self: *Loop, request_node: *Request.Node) void {
1502 if (self.fs_queue.remove(request_node)) {
1503 self.finishOneEvent();
1504 }
1505 }
1506
1507 fn posixFsRun(self: *Loop) void {
1508 nosuspend while (true) {
1509 self.fs_thread_wakeup.reset();
1510 while (self.fs_queue.get()) |node| {
1511 switch (node.data.msg) {
1512 .end => return,
1513 .read => |*msg| {
1514 msg.result = os.read(msg.fd, msg.buf);
1515 },
1516 .readv => |*msg| {
1517 msg.result = os.readv(msg.fd, msg.iov);
1518 },
1519 .write => |*msg| {
1520 msg.result = os.write(msg.fd, msg.bytes);
1521 },
1522 .writev => |*msg| {
1523 msg.result = os.writev(msg.fd, msg.iov);
1524 },
1525 .pwrite => |*msg| {
1526 msg.result = os.pwrite(msg.fd, msg.bytes, msg.offset);
1527 },
1528 .pwritev => |*msg| {
1529 msg.result = os.pwritev(msg.fd, msg.iov, msg.offset);
1530 },
1531 .pread => |*msg| {
1532 msg.result = os.pread(msg.fd, msg.buf, msg.offset);
1533 },
1534 .preadv => |*msg| {
1535 msg.result = os.preadv(msg.fd, msg.iov, msg.offset);
1536 },
1537 .open => |*msg| {
1538 if (is_windows) unreachable; // TODO
1539 msg.result = os.openZ(msg.path, msg.flags, msg.mode);
1540 },
1541 .openat => |*msg| {
1542 if (is_windows) unreachable; // TODO
1543 msg.result = os.openatZ(msg.fd, msg.path, msg.flags, msg.mode);
1544 },
1545 .faccessat => |*msg| {
1546 msg.result = os.faccessatZ(msg.dirfd, msg.path, msg.mode, msg.flags);
1547 },
1548 .close => |*msg| os.close(msg.fd),
1549 }
1550 switch (node.data.finish) {
1551 .tick_node => |*tick_node| self.onNextTick(tick_node),
1552 .no_action => {},
1553 }
1554 self.finishOneEvent();
1555 }
1556 self.fs_thread_wakeup.wait();
1557 };
1558 }
1559
1560 const OsData = switch (builtin.os.tag) {
1561 .linux => LinuxOsData,
1562 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => KEventData,
1563 .windows => struct {
1564 io_port: windows.HANDLE,
1565 extra_thread_count: usize,
1566 },
1567 else => struct {},
1568 };
1569
1570 const KEventData = struct {
1571 kqfd: i32,
1572 final_kevent: os.Kevent,
1573 };
1574
1575 const LinuxOsData = struct {
1576 epollfd: i32,
1577 final_eventfd: i32,
1578 final_eventfd_event: os.linux.epoll_event,
1579 };
1580
1581 pub const Request = struct {
1582 msg: Msg,
1583 finish: Finish,
1584
1585 pub const Node = std.atomic.Queue(Request).Node;
1586
1587 pub const Finish = union(enum) {
1588 tick_node: Loop.NextTickNode,
1589 no_action,
1590 };
1591
1592 pub const Msg = union(enum) {
1593 read: Read,
1594 readv: ReadV,
1595 write: Write,
1596 writev: WriteV,
1597 pwrite: PWrite,
1598 pwritev: PWriteV,
1599 pread: PRead,
1600 preadv: PReadV,
1601 open: Open,
1602 openat: OpenAt,
1603 close: Close,
1604 faccessat: FAccessAt,
1605
1606 /// special - means the fs thread should exit
1607 end,
1608
1609 pub const Read = struct {
1610 fd: os.fd_t,
1611 buf: []u8,
1612 result: Error!usize,
1613
1614 pub const Error = os.ReadError;
1615 };
1616
1617 pub const ReadV = struct {
1618 fd: os.fd_t,
1619 iov: []const os.iovec,
1620 result: Error!usize,
1621
1622 pub const Error = os.ReadError;
1623 };
1624
1625 pub const Write = struct {
1626 fd: os.fd_t,
1627 bytes: []const u8,
1628 result: Error!usize,
1629
1630 pub const Error = os.WriteError;
1631 };
1632
1633 pub const WriteV = struct {
1634 fd: os.fd_t,
1635 iov: []const os.iovec_const,
1636 result: Error!usize,
1637
1638 pub const Error = os.WriteError;
1639 };
1640
1641 pub const PWrite = struct {
1642 fd: os.fd_t,
1643 bytes: []const u8,
1644 offset: usize,
1645 result: Error!usize,
1646
1647 pub const Error = os.PWriteError;
1648 };
1649
1650 pub const PWriteV = struct {
1651 fd: os.fd_t,
1652 iov: []const os.iovec_const,
1653 offset: usize,
1654 result: Error!usize,
1655
1656 pub const Error = os.PWriteError;
1657 };
1658
1659 pub const PRead = struct {
1660 fd: os.fd_t,
1661 buf: []u8,
1662 offset: usize,
1663 result: Error!usize,
1664
1665 pub const Error = os.PReadError;
1666 };
1667
1668 pub const PReadV = struct {
1669 fd: os.fd_t,
1670 iov: []const os.iovec,
1671 offset: usize,
1672 result: Error!usize,
1673
1674 pub const Error = os.PReadError;
1675 };
1676
1677 pub const Open = struct {
1678 path: [*:0]const u8,
1679 flags: u32,
1680 mode: os.mode_t,
1681 result: Error!os.fd_t,
1682
1683 pub const Error = os.OpenError;
1684 };
1685
1686 pub const OpenAt = struct {
1687 fd: os.fd_t,
1688 path: [*:0]const u8,
1689 flags: u32,
1690 mode: os.mode_t,
1691 result: Error!os.fd_t,
1692
1693 pub const Error = os.OpenError;
1694 };
1695
1696 pub const Close = struct {
1697 fd: os.fd_t,
1698 };
1699
1700 pub const FAccessAt = struct {
1701 dirfd: os.fd_t,
1702 path: [*:0]const u8,
1703 mode: u32,
1704 flags: u32,
1705 result: Error!void,
1706
1707 pub const Error = os.AccessError;
1708 };
1709 };
1710 };
1711};
1712
1713test "std.event.Loop - basic" {
1714 // https://github.com/ziglang/zig/issues/1908
1715 if (builtin.single_threaded) return error.SkipZigTest;
1716
1717 if (true) {
1718 // https://github.com/ziglang/zig/issues/4922
1719 return error.SkipZigTest;
1720 }
1721
1722 var loop: Loop = undefined;
1723 try loop.initMultiThreaded();
1724 defer loop.deinit();
1725
1726 loop.run();
1727}
1728
1729fn testEventLoop() i32 {
1730 return 1234;
1731}
1732
1733fn testEventLoop2(h: anyframe->i32, did_it: *bool) void {
1734 const value = await h;
1735 try testing.expect(value == 1234);
1736 did_it.* = true;
1737}
1738
1739var testRunDetachedData: usize = 0;
1740test "std.event.Loop - runDetached" {
1741 // https://github.com/ziglang/zig/issues/1908
1742 if (builtin.single_threaded) return error.SkipZigTest;
1743 if (!std.io.is_async) return error.SkipZigTest;
1744 if (true) {
1745 // https://github.com/ziglang/zig/issues/4922
1746 return error.SkipZigTest;
1747 }
1748
1749 var loop: Loop = undefined;
1750 try loop.initMultiThreaded();
1751 defer loop.deinit();
1752
1753 // Schedule the execution, won't actually start until we start the
1754 // event loop.
1755 try loop.runDetached(std.testing.allocator, testRunDetached, .{});
1756
1757 // Now we can start the event loop. The function will return only
1758 // after all tasks have been completed, allowing us to synchronize
1759 // with the previous runDetached.
1760 loop.run();
1761
1762 try testing.expect(testRunDetachedData == 1);
1763}
1764
1765fn testRunDetached() void {
1766 testRunDetachedData += 1;
1767}
1768
1769test "std.event.Loop - sleep" {
1770 // https://github.com/ziglang/zig/issues/1908
1771 if (builtin.single_threaded) return error.SkipZigTest;
1772 if (!std.io.is_async) return error.SkipZigTest;
1773
1774 const frames = try testing.allocator.alloc(@Frame(testSleep), 10);
1775 defer testing.allocator.free(frames);
1776
1777 const wait_time = 100 * std.time.ns_per_ms;
1778 var sleep_count: usize = 0;
1779
1780 for (frames) |*frame|
1781 frame.* = async testSleep(wait_time, &sleep_count);
1782 for (frames) |*frame|
1783 await frame;
1784
1785 try testing.expect(sleep_count == frames.len);
1786}
1787
1788fn testSleep(wait_ns: u64, sleep_count: *usize) void {
1789 Loop.instance.?.sleep(wait_ns);
1790 _ = @atomicRmw(usize, sleep_count, .Add, 1, .SeqCst);
1791}
lib/std/event/rwlock.zig deleted-292
......@@ -1,292 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const testing = std.testing;
5const mem = std.mem;
6const Loop = std.event.Loop;
7const Allocator = std.mem.Allocator;
8
9/// Thread-safe async/await lock.
10/// Functions which are waiting for the lock are suspended, and
11/// are resumed when the lock is released, in order.
12/// Many readers can hold the lock at the same time; however locking for writing is exclusive.
13/// When a read lock is held, it will not be released until the reader queue is empty.
14/// When a write lock is held, it will not be released until the writer queue is empty.
15/// TODO: make this API also work in blocking I/O mode
16pub const RwLock = struct {
17 shared_state: State,
18 writer_queue: Queue,
19 reader_queue: Queue,
20 writer_queue_empty: bool,
21 reader_queue_empty: bool,
22 reader_lock_count: usize,
23
24 const State = enum(u8) {
25 Unlocked,
26 WriteLock,
27 ReadLock,
28 };
29
30 const Queue = std.atomic.Queue(anyframe);
31
32 const global_event_loop = Loop.instance orelse
33 @compileError("std.event.RwLock currently only works with event-based I/O");
34
35 pub const HeldRead = struct {
36 lock: *RwLock,
37
38 pub fn release(self: HeldRead) void {
39 // If other readers still hold the lock, we're done.
40 if (@atomicRmw(usize, &self.lock.reader_lock_count, .Sub, 1, .SeqCst) != 1) {
41 return;
42 }
43
44 @atomicStore(bool, &self.lock.reader_queue_empty, true, .SeqCst);
45 if (@cmpxchgStrong(State, &self.lock.shared_state, .ReadLock, .Unlocked, .SeqCst, .SeqCst) != null) {
46 // Didn't unlock. Someone else's problem.
47 return;
48 }
49
50 self.lock.commonPostUnlock();
51 }
52 };
53
54 pub const HeldWrite = struct {
55 lock: *RwLock,
56
57 pub fn release(self: HeldWrite) void {
58 // See if we can leave it locked for writing, and pass the lock to the next writer
59 // in the queue to grab the lock.
60 if (self.lock.writer_queue.get()) |node| {
61 global_event_loop.onNextTick(node);
62 return;
63 }
64
65 // We need to release the write lock. Check if any readers are waiting to grab the lock.
66 if (!@atomicLoad(bool, &self.lock.reader_queue_empty, .SeqCst)) {
67 // Switch to a read lock.
68 @atomicStore(State, &self.lock.shared_state, .ReadLock, .SeqCst);
69 while (self.lock.reader_queue.get()) |node| {
70 global_event_loop.onNextTick(node);
71 }
72 return;
73 }
74
75 @atomicStore(bool, &self.lock.writer_queue_empty, true, .SeqCst);
76 @atomicStore(State, &self.lock.shared_state, .Unlocked, .SeqCst);
77
78 self.lock.commonPostUnlock();
79 }
80 };
81
82 pub fn init() RwLock {
83 return .{
84 .shared_state = .Unlocked,
85 .writer_queue = Queue.init(),
86 .writer_queue_empty = true,
87 .reader_queue = Queue.init(),
88 .reader_queue_empty = true,
89 .reader_lock_count = 0,
90 };
91 }
92
93 /// Must be called when not locked. Not thread safe.
94 /// All calls to acquire() and release() must complete before calling deinit().
95 pub fn deinit(self: *RwLock) void {
96 assert(self.shared_state == .Unlocked);
97 while (self.writer_queue.get()) |node| resume node.data;
98 while (self.reader_queue.get()) |node| resume node.data;
99 }
100
101 pub fn acquireRead(self: *RwLock) callconv(.Async) HeldRead {
102 _ = @atomicRmw(usize, &self.reader_lock_count, .Add, 1, .SeqCst);
103
104 suspend {
105 var my_tick_node = Loop.NextTickNode{
106 .data = @frame(),
107 .prev = undefined,
108 .next = undefined,
109 };
110
111 self.reader_queue.put(&my_tick_node);
112
113 // At this point, we are in the reader_queue, so we might have already been resumed.
114
115 // We set this bit so that later we can rely on the fact, that if reader_queue_empty == true,
116 // some actor will attempt to grab the lock.
117 @atomicStore(bool, &self.reader_queue_empty, false, .SeqCst);
118
119 // Here we don't care if we are the one to do the locking or if it was already locked for reading.
120 const have_read_lock = if (@cmpxchgStrong(State, &self.shared_state, .Unlocked, .ReadLock, .SeqCst, .SeqCst)) |old_state| old_state == .ReadLock else true;
121 if (have_read_lock) {
122 // Give out all the read locks.
123 if (self.reader_queue.get()) |first_node| {
124 while (self.reader_queue.get()) |node| {
125 global_event_loop.onNextTick(node);
126 }
127 resume first_node.data;
128 }
129 }
130 }
131 return HeldRead{ .lock = self };
132 }
133
134 pub fn acquireWrite(self: *RwLock) callconv(.Async) HeldWrite {
135 suspend {
136 var my_tick_node = Loop.NextTickNode{
137 .data = @frame(),
138 .prev = undefined,
139 .next = undefined,
140 };
141
142 self.writer_queue.put(&my_tick_node);
143
144 // At this point, we are in the writer_queue, so we might have already been resumed.
145
146 // We set this bit so that later we can rely on the fact, that if writer_queue_empty == true,
147 // some actor will attempt to grab the lock.
148 @atomicStore(bool, &self.writer_queue_empty, false, .SeqCst);
149
150 // Here we must be the one to acquire the write lock. It cannot already be locked.
151 if (@cmpxchgStrong(State, &self.shared_state, .Unlocked, .WriteLock, .SeqCst, .SeqCst) == null) {
152 // We now have a write lock.
153 if (self.writer_queue.get()) |node| {
154 // Whether this node is us or someone else, we tail resume it.
155 resume node.data;
156 }
157 }
158 }
159 return HeldWrite{ .lock = self };
160 }
161
162 fn commonPostUnlock(self: *RwLock) void {
163 while (true) {
164 // There might be a writer_queue item or a reader_queue item
165 // If we check and both are empty, we can be done, because the other actors will try to
166 // obtain the lock.
167 // But if there's a writer_queue item or a reader_queue item,
168 // we are the actor which must loop and attempt to grab the lock again.
169 if (!@atomicLoad(bool, &self.writer_queue_empty, .SeqCst)) {
170 if (@cmpxchgStrong(State, &self.shared_state, .Unlocked, .WriteLock, .SeqCst, .SeqCst) != null) {
171 // We did not obtain the lock. Great, the queues are someone else's problem.
172 return;
173 }
174 // If there's an item in the writer queue, give them the lock, and we're done.
175 if (self.writer_queue.get()) |node| {
176 global_event_loop.onNextTick(node);
177 return;
178 }
179 // Release the lock again.
180 @atomicStore(bool, &self.writer_queue_empty, true, .SeqCst);
181 @atomicStore(State, &self.shared_state, .Unlocked, .SeqCst);
182 continue;
183 }
184
185 if (!@atomicLoad(bool, &self.reader_queue_empty, .SeqCst)) {
186 if (@cmpxchgStrong(State, &self.shared_state, .Unlocked, .ReadLock, .SeqCst, .SeqCst) != null) {
187 // We did not obtain the lock. Great, the queues are someone else's problem.
188 return;
189 }
190 // If there are any items in the reader queue, give out all the reader locks, and we're done.
191 if (self.reader_queue.get()) |first_node| {
192 global_event_loop.onNextTick(first_node);
193 while (self.reader_queue.get()) |node| {
194 global_event_loop.onNextTick(node);
195 }
196 return;
197 }
198 // Release the lock again.
199 @atomicStore(bool, &self.reader_queue_empty, true, .SeqCst);
200 if (@cmpxchgStrong(State, &self.shared_state, .ReadLock, .Unlocked, .SeqCst, .SeqCst) != null) {
201 // Didn't unlock. Someone else's problem.
202 return;
203 }
204 continue;
205 }
206 return;
207 }
208 }
209};
210
211test "std.event.RwLock" {
212 // https://github.com/ziglang/zig/issues/2377
213 if (true) return error.SkipZigTest;
214
215 // https://github.com/ziglang/zig/issues/1908
216 if (builtin.single_threaded) return error.SkipZigTest;
217
218 // TODO provide a way to run tests in evented I/O mode
219 if (!std.io.is_async) return error.SkipZigTest;
220
221 var lock = RwLock.init();
222 defer lock.deinit();
223
224 _ = testLock(std.heap.page_allocator, &lock);
225
226 const expected_result = [1]i32{shared_it_count * @as(i32, @intCast(shared_test_data.len))} ** shared_test_data.len;
227 try testing.expectEqualSlices(i32, expected_result, shared_test_data);
228}
229fn testLock(allocator: Allocator, lock: *RwLock) callconv(.Async) void {
230 var read_nodes: [100]Loop.NextTickNode = undefined;
231 for (&read_nodes) |*read_node| {
232 const frame = allocator.create(@Frame(readRunner)) catch @panic("memory");
233 read_node.data = frame;
234 frame.* = async readRunner(lock);
235 Loop.instance.?.onNextTick(read_node);
236 }
237
238 var write_nodes: [shared_it_count]Loop.NextTickNode = undefined;
239 for (&write_nodes) |*write_node| {
240 const frame = allocator.create(@Frame(writeRunner)) catch @panic("memory");
241 write_node.data = frame;
242 frame.* = async writeRunner(lock);
243 Loop.instance.?.onNextTick(write_node);
244 }
245
246 for (&write_nodes) |*write_node| {
247 const casted = @as(*const @Frame(writeRunner), @ptrCast(write_node.data));
248 await casted;
249 allocator.destroy(casted);
250 }
251 for (&read_nodes) |*read_node| {
252 const casted = @as(*const @Frame(readRunner), @ptrCast(read_node.data));
253 await casted;
254 allocator.destroy(casted);
255 }
256}
257
258const shared_it_count = 10;
259var shared_test_data = [1]i32{0} ** 10;
260var shared_test_index: usize = 0;
261var shared_count: usize = 0;
262fn writeRunner(lock: *RwLock) callconv(.Async) void {
263 suspend {} // resumed by onNextTick
264
265 var i: usize = 0;
266 while (i < shared_test_data.len) : (i += 1) {
267 std.time.sleep(100 * std.time.microsecond);
268 const lock_promise = async lock.acquireWrite();
269 const handle = await lock_promise;
270 defer handle.release();
271
272 shared_count += 1;
273 while (shared_test_index < shared_test_data.len) : (shared_test_index += 1) {
274 shared_test_data[shared_test_index] = shared_test_data[shared_test_index] + 1;
275 }
276 shared_test_index = 0;
277 }
278}
279fn readRunner(lock: *RwLock) callconv(.Async) void {
280 suspend {} // resumed by onNextTick
281 std.time.sleep(1);
282
283 var i: usize = 0;
284 while (i < shared_test_data.len) : (i += 1) {
285 const lock_promise = async lock.acquireRead();
286 const handle = await lock_promise;
287 defer handle.release();
288
289 try testing.expect(shared_test_index == 0);
290 try testing.expect(shared_test_data[i] == @as(i32, @intCast(shared_count)));
291 }
292}
lib/std/event/rwlocked.zig deleted-57
......@@ -1,57 +0,0 @@
1const std = @import("../std.zig");
2const RwLock = std.event.RwLock;
3
4/// Thread-safe async/await RW lock that protects one piece of data.
5/// Functions which are waiting for the lock are suspended, and
6/// are resumed when the lock is released, in order.
7pub fn RwLocked(comptime T: type) type {
8 return struct {
9 lock: RwLock,
10 locked_data: T,
11
12 const Self = @This();
13
14 pub const HeldReadLock = struct {
15 value: *const T,
16 held: RwLock.HeldRead,
17
18 pub fn release(self: HeldReadLock) void {
19 self.held.release();
20 }
21 };
22
23 pub const HeldWriteLock = struct {
24 value: *T,
25 held: RwLock.HeldWrite,
26
27 pub fn release(self: HeldWriteLock) void {
28 self.held.release();
29 }
30 };
31
32 pub fn init(data: T) Self {
33 return Self{
34 .lock = RwLock.init(),
35 .locked_data = data,
36 };
37 }
38
39 pub fn deinit(self: *Self) void {
40 self.lock.deinit();
41 }
42
43 pub fn acquireRead(self: *Self) callconv(.Async) HeldReadLock {
44 return HeldReadLock{
45 .held = self.lock.acquireRead(),
46 .value = &self.locked_data,
47 };
48 }
49
50 pub fn acquireWrite(self: *Self) callconv(.Async) HeldWriteLock {
51 return HeldWriteLock{
52 .held = self.lock.acquireWrite(),
53 .value = &self.locked_data,
54 };
55 }
56 };
57}
lib/std/event/wait_group.zig deleted-115
......@@ -1,115 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const Loop = std.event.Loop;
4
5/// A WaitGroup keeps track and waits for a group of async tasks to finish.
6/// Call `begin` when creating new tasks, and have tasks call `finish` when done.
7/// You can provide a count for both operations to perform them in bulk.
8/// Call `wait` to suspend until all tasks are completed.
9/// Multiple waiters are supported.
10///
11/// WaitGroup is an instance of WaitGroupGeneric, which takes in a bitsize
12/// for the internal counter. WaitGroup defaults to a `usize` counter.
13/// It's also possible to define a max value for the counter so that
14/// `begin` will return error.Overflow when the limit is reached, even
15/// if the integer type has not has not overflowed.
16/// By default `max_value` is set to std.math.maxInt(CounterType).
17pub const WaitGroup = WaitGroupGeneric(@bitSizeOf(usize));
18
19pub fn WaitGroupGeneric(comptime counter_size: u16) type {
20 const CounterType = std.meta.Int(.unsigned, counter_size);
21
22 const global_event_loop = Loop.instance orelse
23 @compileError("std.event.WaitGroup currently only works with event-based I/O");
24
25 return struct {
26 counter: CounterType = 0,
27 max_counter: CounterType = std.math.maxInt(CounterType),
28 mutex: std.Thread.Mutex = .{},
29 waiters: ?*Waiter = null,
30 const Waiter = struct {
31 next: ?*Waiter,
32 tail: *Waiter,
33 node: Loop.NextTickNode,
34 };
35
36 const Self = @This();
37 pub fn begin(self: *Self, count: CounterType) error{Overflow}!void {
38 self.mutex.lock();
39 defer self.mutex.unlock();
40
41 const new_counter = try std.math.add(CounterType, self.counter, count);
42 if (new_counter > self.max_counter) return error.Overflow;
43 self.counter = new_counter;
44 }
45
46 pub fn finish(self: *Self, count: CounterType) void {
47 var waiters = blk: {
48 self.mutex.lock();
49 defer self.mutex.unlock();
50 self.counter = std.math.sub(CounterType, self.counter, count) catch unreachable;
51 if (self.counter == 0) {
52 const temp = self.waiters;
53 self.waiters = null;
54 break :blk temp;
55 }
56 break :blk null;
57 };
58
59 // We don't need to hold the lock to reschedule any potential waiter.
60 while (waiters) |w| {
61 const temp_w = w;
62 waiters = w.next;
63 global_event_loop.onNextTick(&temp_w.node);
64 }
65 }
66
67 pub fn wait(self: *Self) void {
68 self.mutex.lock();
69
70 if (self.counter == 0) {
71 self.mutex.unlock();
72 return;
73 }
74
75 var self_waiter: Waiter = undefined;
76 self_waiter.node.data = @frame();
77 if (self.waiters) |head| {
78 head.tail.next = &self_waiter;
79 head.tail = &self_waiter;
80 } else {
81 self.waiters = &self_waiter;
82 self_waiter.tail = &self_waiter;
83 self_waiter.next = null;
84 }
85 suspend {
86 self.mutex.unlock();
87 }
88 }
89 };
90}
91
92test "basic WaitGroup usage" {
93 if (!std.io.is_async) return error.SkipZigTest;
94
95 // TODO https://github.com/ziglang/zig/issues/1908
96 if (builtin.single_threaded) return error.SkipZigTest;
97
98 // TODO https://github.com/ziglang/zig/issues/3251
99 if (builtin.os.tag == .freebsd) return error.SkipZigTest;
100
101 var initial_wg = WaitGroup{};
102 var final_wg = WaitGroup{};
103
104 try initial_wg.begin(1);
105 try final_wg.begin(1);
106 var task_frame = async task(&initial_wg, &final_wg);
107 initial_wg.finish(1);
108 final_wg.wait();
109 await task_frame;
110}
111
112fn task(wg_i: *WaitGroup, wg_f: *WaitGroup) void {
113 wg_i.wait();
114 wg_f.finish(1);
115}
lib/std/fs.zig+5-15
......@@ -31,8 +31,6 @@ pub const realpathW = os.realpathW;
3131pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
3232pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
3333
34pub const Watch = @import("fs/watch.zig").Watch;
35
3634/// This represents the maximum size of a UTF-8 encoded file path that the
3735/// operating system will accept. Paths, including those returned from file
3836/// system operations, may be longer than this length, but such paths cannot
......@@ -86,13 +84,6 @@ pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
8684/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
8785pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
8886
89/// Whether or not async file system syscalls need a dedicated thread because the operating
90/// system does not support non-blocking I/O on the file system.
91pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
92 .windows, .other => false,
93 else => true,
94};
95
9687/// TODO remove the allocator requirement from this API
9788/// TODO move to Dir
9889pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
......@@ -231,17 +222,17 @@ pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_
231222/// On POSIX targets, this function is comptime-callable.
232223pub fn cwd() Dir {
233224 if (builtin.os.tag == .windows) {
234 return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
225 return .{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
235226 } else if (builtin.os.tag == .wasi) {
236 return std.options.wasiCwd();
227 return .{ .fd = std.options.wasiCwd() };
237228 } else {
238 return Dir{ .fd = os.AT.FDCWD };
229 return .{ .fd = os.AT.FDCWD };
239230 }
240231}
241232
242pub fn defaultWasiCwd() Dir {
233pub fn defaultWasiCwd() std.os.wasi.fd_t {
243234 // Expect the first preopen to be current working directory.
244 return .{ .fd = 3 };
235 return 3;
245236}
246237
247238/// Opens a directory at the given path. The directory is a system resource that remains
......@@ -641,5 +632,4 @@ test {
641632 _ = &path;
642633 _ = @import("fs/test.zig");
643634 _ = @import("fs/get_app_data_dir.zig");
644 _ = @import("fs/watch.zig");
645635}
lib/std/fs/Dir.zig+12-59
......@@ -751,11 +751,7 @@ pub const OpenError = error{
751751} || posix.UnexpectedError;
752752
753753pub fn close(self: *Dir) void {
754 if (fs.need_async_thread) {
755 std.event.Loop.instance.?.close(self.fd);
756 } else {
757 posix.close(self.fd);
758 }
754 posix.close(self.fd);
759755 self.* = undefined;
760756}
761757
......@@ -837,10 +833,7 @@ pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File
837833 .write_only => @as(u32, posix.O.WRONLY),
838834 .read_write => @as(u32, posix.O.RDWR),
839835 };
840 const fd = if (flags.intended_io_mode != .blocking)
841 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
842 else
843 try posix.openatZ(self.fd, sub_path, os_flags, 0);
836 const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
844837 errdefer posix.close(fd);
845838
846839 // WASI doesn't have posix.flock so we intetinally check OS prior to the inner if block
......@@ -877,11 +870,7 @@ pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File
877870 };
878871 }
879872
880 return File{
881 .handle = fd,
882 .capable_io_mode = .blocking,
883 .intended_io_mode = flags.intended_io_mode,
884 };
873 return File{ .handle = fd };
885874}
886875
887876/// Same as `openFile` but Windows-only and the path parameter is
......@@ -895,10 +884,7 @@ pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File
895884 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
896885 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
897886 .creation = w.FILE_OPEN,
898 .io_mode = flags.intended_io_mode,
899887 }),
900 .capable_io_mode = std.io.default_mode,
901 .intended_io_mode = flags.intended_io_mode,
902888 };
903889 errdefer file.close();
904890 var io: w.IO_STATUS_BLOCK = undefined;
......@@ -994,10 +980,7 @@ pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags
994980 (if (flags.truncate) @as(u32, posix.O.TRUNC) else 0) |
995981 (if (flags.read) @as(u32, posix.O.RDWR) else posix.O.WRONLY) |
996982 (if (flags.exclusive) @as(u32, posix.O.EXCL) else 0);
997 const fd = if (flags.intended_io_mode != .blocking)
998 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
999 else
1000 try posix.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
983 const fd = try posix.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
1001984 errdefer posix.close(fd);
1002985
1003986 // WASI doesn't have posix.flock so we intetinally check OS prior to the inner if block
......@@ -1034,11 +1017,7 @@ pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags
10341017 };
10351018 }
10361019
1037 return File{
1038 .handle = fd,
1039 .capable_io_mode = .blocking,
1040 .intended_io_mode = flags.intended_io_mode,
1041 };
1020 return File{ .handle = fd };
10421021}
10431022
10441023/// Same as `createFile` but Windows-only and the path parameter is
......@@ -1056,10 +1035,7 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags)
10561035 @as(u32, w.FILE_OVERWRITE_IF)
10571036 else
10581037 @as(u32, w.FILE_OPEN_IF),
1059 .io_mode = flags.intended_io_mode,
10601038 }),
1061 .capable_io_mode = std.io.default_mode,
1062 .intended_io_mode = flags.intended_io_mode,
10631039 };
10641040 errdefer file.close();
10651041 var io: w.IO_STATUS_BLOCK = undefined;
......@@ -1276,7 +1252,6 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 {
12761252 .access_mask = access_mask,
12771253 .share_access = share_access,
12781254 .creation = creation,
1279 .io_mode = .blocking,
12801255 .filter = .any,
12811256 }) catch |err| switch (err) {
12821257 error.WouldBlock => unreachable,
......@@ -1449,11 +1424,7 @@ pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) Ope
14491424
14501425/// `flags` must contain `posix.O.DIRECTORY`.
14511426fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
1452 const result = if (fs.need_async_thread)
1453 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, flags, 0)
1454 else
1455 posix.openatZ(self.fd, sub_path_c, flags, 0);
1456 const fd = result catch |err| switch (err) {
1427 const fd = posix.openatZ(self.fd, sub_path_c, flags, 0) catch |err| switch (err) {
14571428 error.FileTooBig => unreachable, // can't happen for directories
14581429 error.IsDir => unreachable, // we're providing O.DIRECTORY
14591430 error.NoSpaceLeft => unreachable, // not providing O.CREAT
......@@ -2270,10 +2241,7 @@ pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) Access
22702241 .write_only => @as(u32, posix.W_OK),
22712242 .read_write => @as(u32, posix.R_OK | posix.W_OK),
22722243 };
2273 const result = if (fs.need_async_thread and flags.intended_io_mode != .blocking)
2274 std.event.Loop.instance.?.faccessatZ(self.fd, sub_path, os_mode, 0)
2275 else
2276 posix.faccessatZ(self.fd, sub_path, os_mode, 0);
2244 const result = posix.faccessatZ(self.fd, sub_path, os_mode, 0);
22772245 return result;
22782246}
22792247
......@@ -2457,10 +2425,7 @@ pub const Stat = File.Stat;
24572425pub const StatError = File.StatError;
24582426
24592427pub fn stat(self: Dir) StatError!Stat {
2460 const file: File = .{
2461 .handle = self.fd,
2462 .capable_io_mode = .blocking,
2463 };
2428 const file: File = .{ .handle = self.fd };
24642429 return file.stat();
24652430}
24662431
......@@ -2496,10 +2461,7 @@ pub const ChmodError = File.ChmodError;
24962461/// of the directory. Additionally, the directory must have been opened
24972462/// with `OpenDirOptions{ .iterate = true }`.
24982463pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2499 const file: File = .{
2500 .handle = self.fd,
2501 .capable_io_mode = .blocking,
2502 };
2464 const file: File = .{ .handle = self.fd };
25032465 try file.chmod(new_mode);
25042466}
25052467
......@@ -2510,10 +2472,7 @@ pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
25102472/// must have been opened with `OpenDirOptions{ .iterate = true }`. If the
25112473/// owner or group is specified as `null`, the ID is not changed.
25122474pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2513 const file: File = .{
2514 .handle = self.fd,
2515 .capable_io_mode = .blocking,
2516 };
2475 const file: File = .{ .handle = self.fd };
25172476 try file.chown(owner, group);
25182477}
25192478
......@@ -2525,10 +2484,7 @@ pub const SetPermissionsError = File.SetPermissionsError;
25252484/// Sets permissions according to the provided `Permissions` struct.
25262485/// This method is *NOT* available on WASI
25272486pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
2528 const file: File = .{
2529 .handle = self.fd,
2530 .capable_io_mode = .blocking,
2531 };
2487 const file: File = .{ .handle = self.fd };
25322488 try file.setPermissions(permissions);
25332489}
25342490
......@@ -2537,10 +2493,7 @@ pub const MetadataError = File.MetadataError;
25372493
25382494/// Returns a `Metadata` struct, representing the permissions on the directory
25392495pub fn metadata(self: Dir) MetadataError!Metadata {
2540 const file: File = .{
2541 .handle = self.fd,
2542 .capable_io_mode = .blocking,
2543 };
2496 const file: File = .{ .handle = self.fd };
25442497 return try file.metadata();
25452498}
25462499
lib/std/fs/File.zig+16-80
......@@ -1,20 +1,6 @@
11/// The OS-specific file descriptor or file handle.
22handle: Handle,
33
4/// On some systems, such as Linux, file system file descriptors are incapable
5/// of non-blocking I/O. This forces us to perform asynchronous I/O on a dedicated thread,
6/// to achieve non-blocking file-system I/O. To do this, `File` must be aware of whether
7/// it is a file system file descriptor, or, more specifically, whether the I/O is always
8/// blocking.
9capable_io_mode: io.ModeOverride = io.default_mode,
10
11/// Furthermore, even when `std.options.io_mode` is async, it is still sometimes desirable
12/// to perform blocking I/O, although not by default. For example, when printing a
13/// stack trace to stderr. This field tracks both by acting as an overriding I/O mode.
14/// When not building in async I/O mode, the type only has the `.blocking` tag, making
15/// it a zero-bit type.
16intended_io_mode: io.ModeOverride = io.default_mode,
17
184pub const Handle = posix.fd_t;
195pub const Mode = posix.mode_t;
206pub const INode = posix.ino_t;
......@@ -108,16 +94,8 @@ pub const OpenFlags = struct {
10894 /// Sets whether or not to wait until the file is locked to return. If set to true,
10995 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
11096 /// is available to proceed.
111 /// In async I/O mode, non-blocking at the OS level is
112 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
113 /// and `false` means `error.WouldBlock` is handled by the event loop.
11497 lock_nonblocking: bool = false,
11598
116 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
117 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
118 /// related to opening the file, reading, writing, and locking.
119 intended_io_mode: io.ModeOverride = io.default_mode,
120
12199 /// Set this to allow the opened file to automatically become the
122100 /// controlling TTY for the current process.
123101 allow_ctty: bool = false,
......@@ -172,19 +150,11 @@ pub const CreateFlags = struct {
172150 /// Sets whether or not to wait until the file is locked to return. If set to true,
173151 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
174152 /// is available to proceed.
175 /// In async I/O mode, non-blocking at the OS level is
176 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
177 /// and `false` means `error.WouldBlock` is handled by the event loop.
178153 lock_nonblocking: bool = false,
179154
180155 /// For POSIX systems this is the file system mode the file will
181156 /// be created with. On other systems this is always 0.
182157 mode: Mode = default_mode,
183
184 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
185 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
186 /// related to opening the file, reading, writing, and locking.
187 intended_io_mode: io.ModeOverride = io.default_mode,
188158};
189159
190160/// Upon success, the stream is in an uninitialized state. To continue using it,
......@@ -192,8 +162,6 @@ pub const CreateFlags = struct {
192162pub fn close(self: File) void {
193163 if (is_windows) {
194164 windows.CloseHandle(self.handle);
195 } else if (self.capable_io_mode != self.intended_io_mode) {
196 std.event.Loop.instance.?.close(self.handle);
197165 } else {
198166 posix.close(self.handle);
199167 }
......@@ -1013,14 +981,10 @@ pub const PReadError = posix.PReadError;
1013981
1014982pub fn read(self: File, buffer: []u8) ReadError!usize {
1015983 if (is_windows) {
1016 return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode);
984 return windows.ReadFile(self.handle, buffer, null);
1017985 }
1018986
1019 if (self.intended_io_mode == .blocking) {
1020 return posix.read(self.handle, buffer);
1021 } else {
1022 return std.event.Loop.instance.?.read(self.handle, buffer, self.capable_io_mode != self.intended_io_mode);
1023 }
987 return posix.read(self.handle, buffer);
1024988}
1025989
1026990/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
......@@ -1039,14 +1003,10 @@ pub fn readAll(self: File, buffer: []u8) ReadError!usize {
10391003/// https://github.com/ziglang/zig/issues/12783
10401004pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
10411005 if (is_windows) {
1042 return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode);
1006 return windows.ReadFile(self.handle, buffer, offset);
10431007 }
10441008
1045 if (self.intended_io_mode == .blocking) {
1046 return posix.pread(self.handle, buffer, offset);
1047 } else {
1048 return std.event.Loop.instance.?.pread(self.handle, buffer, offset, self.capable_io_mode != self.intended_io_mode);
1049 }
1009 return posix.pread(self.handle, buffer, offset);
10501010}
10511011
10521012/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
......@@ -1069,14 +1029,10 @@ pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
10691029 // TODO improve this to use ReadFileScatter
10701030 if (iovecs.len == 0) return @as(usize, 0);
10711031 const first = iovecs[0];
1072 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1032 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null);
10731033 }
10741034
1075 if (self.intended_io_mode == .blocking) {
1076 return posix.readv(self.handle, iovecs);
1077 } else {
1078 return std.event.Loop.instance.?.readv(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1079 }
1035 return posix.readv(self.handle, iovecs);
10801036}
10811037
10821038/// Returns the number of bytes read. If the number read is smaller than the total bytes
......@@ -1129,14 +1085,10 @@ pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!u
11291085 // TODO improve this to use ReadFileScatter
11301086 if (iovecs.len == 0) return @as(usize, 0);
11311087 const first = iovecs[0];
1132 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1088 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset);
11331089 }
11341090
1135 if (self.intended_io_mode == .blocking) {
1136 return posix.preadv(self.handle, iovecs, offset);
1137 } else {
1138 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1139 }
1091 return posix.preadv(self.handle, iovecs, offset);
11401092}
11411093
11421094/// Returns the number of bytes read. If the number read is smaller than the total bytes
......@@ -1173,14 +1125,10 @@ pub const PWriteError = posix.PWriteError;
11731125
11741126pub fn write(self: File, bytes: []const u8) WriteError!usize {
11751127 if (is_windows) {
1176 return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode);
1128 return windows.WriteFile(self.handle, bytes, null);
11771129 }
11781130
1179 if (self.intended_io_mode == .blocking) {
1180 return posix.write(self.handle, bytes);
1181 } else {
1182 return std.event.Loop.instance.?.write(self.handle, bytes, self.capable_io_mode != self.intended_io_mode);
1183 }
1131 return posix.write(self.handle, bytes);
11841132}
11851133
11861134pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
......@@ -1194,14 +1142,10 @@ pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
11941142/// https://github.com/ziglang/zig/issues/12783
11951143pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
11961144 if (is_windows) {
1197 return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode);
1145 return windows.WriteFile(self.handle, bytes, offset);
11981146 }
11991147
1200 if (self.intended_io_mode == .blocking) {
1201 return posix.pwrite(self.handle, bytes, offset);
1202 } else {
1203 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode);
1204 }
1148 return posix.pwrite(self.handle, bytes, offset);
12051149}
12061150
12071151/// On Windows, this function currently does alter the file pointer.
......@@ -1220,14 +1164,10 @@ pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
12201164 // TODO improve this to use WriteFileScatter
12211165 if (iovecs.len == 0) return @as(usize, 0);
12221166 const first = iovecs[0];
1223 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1167 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null);
12241168 }
12251169
1226 if (self.intended_io_mode == .blocking) {
1227 return posix.writev(self.handle, iovecs);
1228 } else {
1229 return std.event.Loop.instance.?.writev(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1230 }
1170 return posix.writev(self.handle, iovecs);
12311171}
12321172
12331173/// The `iovecs` parameter is mutable because:
......@@ -1271,14 +1211,10 @@ pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError
12711211 // TODO improve this to use WriteFileScatter
12721212 if (iovecs.len == 0) return @as(usize, 0);
12731213 const first = iovecs[0];
1274 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1214 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset);
12751215 }
12761216
1277 if (self.intended_io_mode == .blocking) {
1278 return posix.pwritev(self.handle, iovecs, offset);
1279 } else {
1280 return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1281 }
1217 return posix.pwritev(self.handle, iovecs, offset);
12821218}
12831219
12841220/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
lib/std/fs/test.zig-5
......@@ -1508,11 +1508,6 @@ test "open file with exclusive and shared nonblocking lock" {
15081508test "open file with exclusive lock twice, make sure second lock waits" {
15091509 if (builtin.single_threaded) return error.SkipZigTest;
15101510
1511 if (std.io.is_async) {
1512 // This test starts its own threads and is not compatible with async I/O.
1513 return error.SkipZigTest;
1514 }
1515
15161511 try testWithAllSupportedPathTypes(struct {
15171512 fn impl(ctx: *TestContext) !void {
15181513 const filename = try ctx.transformPath("file_lock_test.txt");
lib/std/fs/watch.zig deleted-719
......@@ -1,719 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14const global_event_loop = Loop.instance orelse
15 @compileError("std.fs.Watch currently only works with event-based I/O");
16
17const WatchEventId = enum {
18 CloseWrite,
19 Delete,
20};
21
22const WatchEventError = error{
23 UserResourceLimitReached,
24 SystemResources,
25 AccessDenied,
26 Unexpected, // TODO remove this possibility
27};
28
29pub fn Watch(comptime V: type) type {
30 return struct {
31 channel: event.Channel(Event.Error!Event),
32 os_data: OsData,
33 allocator: Allocator,
34
35 const OsData = switch (builtin.os.tag) {
36 // TODO https://github.com/ziglang/zig/issues/3778
37 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => KqOsData,
38 .linux => LinuxOsData,
39 .windows => WindowsOsData,
40
41 else => @compileError("Unsupported OS"),
42 };
43
44 const KqOsData = struct {
45 table_lock: event.Lock,
46 file_table: FileTable,
47
48 const FileTable = std.StringHashMapUnmanaged(*Put);
49 const Put = struct {
50 putter_frame: @Frame(kqPutEvents),
51 cancelled: bool = false,
52 value: V,
53 };
54 };
55
56 const WindowsOsData = struct {
57 table_lock: event.Lock,
58 dir_table: DirTable,
59 cancelled: bool = false,
60
61 const DirTable = std.StringHashMapUnmanaged(*Dir);
62 const FileTable = std.StringHashMapUnmanaged(V);
63
64 const Dir = struct {
65 putter_frame: @Frame(windowsDirReader),
66 file_table: FileTable,
67 dir_handle: os.windows.HANDLE,
68 };
69 };
70
71 const LinuxOsData = struct {
72 putter_frame: @Frame(linuxEventPutter),
73 inotify_fd: i32,
74 wd_table: WdTable,
75 table_lock: event.Lock,
76 cancelled: bool = false,
77
78 const WdTable = std.AutoHashMapUnmanaged(i32, Dir);
79 const FileTable = std.StringHashMapUnmanaged(V);
80
81 const Dir = struct {
82 dirname: []const u8,
83 file_table: FileTable,
84 };
85 };
86
87 const Self = @This();
88
89 pub const Event = struct {
90 id: Id,
91 data: V,
92 dirname: []const u8,
93 basename: []const u8,
94
95 pub const Id = WatchEventId;
96 pub const Error = WatchEventError;
97 };
98
99 pub fn init(allocator: Allocator, event_buf_count: usize) !*Self {
100 const self = try allocator.create(Self);
101 errdefer allocator.destroy(self);
102
103 switch (builtin.os.tag) {
104 .linux => {
105 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
106 errdefer os.close(inotify_fd);
107
108 self.* = Self{
109 .allocator = allocator,
110 .channel = undefined,
111 .os_data = OsData{
112 .putter_frame = undefined,
113 .inotify_fd = inotify_fd,
114 .wd_table = OsData.WdTable.init(allocator),
115 .table_lock = event.Lock{},
116 },
117 };
118
119 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
120 self.channel.init(buf);
121 self.os_data.putter_frame = async self.linuxEventPutter();
122 return self;
123 },
124
125 .windows => {
126 self.* = Self{
127 .allocator = allocator,
128 .channel = undefined,
129 .os_data = OsData{
130 .table_lock = event.Lock{},
131 .dir_table = OsData.DirTable.init(allocator),
132 },
133 };
134
135 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
136 self.channel.init(buf);
137 return self;
138 },
139
140 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
141 self.* = Self{
142 .allocator = allocator,
143 .channel = undefined,
144 .os_data = OsData{
145 .table_lock = event.Lock{},
146 .file_table = OsData.FileTable.init(allocator),
147 },
148 };
149
150 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
151 self.channel.init(buf);
152 return self;
153 },
154 else => @compileError("Unsupported OS"),
155 }
156 }
157
158 pub fn deinit(self: *Self) void {
159 switch (builtin.os.tag) {
160 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
161 var it = self.os_data.file_table.iterator();
162 while (it.next()) |entry| {
163 const key = entry.key_ptr.*;
164 const value = entry.value_ptr.*;
165 value.cancelled = true;
166 // @TODO Close the fd here?
167 await value.putter_frame;
168 self.allocator.free(key);
169 self.allocator.destroy(value);
170 }
171 },
172 .linux => {
173 self.os_data.cancelled = true;
174 {
175 // Remove all directory watches linuxEventPutter will take care of
176 // cleaning up the memory and closing the inotify fd.
177 var dir_it = self.os_data.wd_table.keyIterator();
178 while (dir_it.next()) |wd_key| {
179 const rc = os.linux.inotify_rm_watch(self.os_data.inotify_fd, wd_key.*);
180 // Errno can only be EBADF, EINVAL if either the inotify fs or the wd are invalid
181 std.debug.assert(rc == 0);
182 }
183 }
184 await self.os_data.putter_frame;
185 },
186 .windows => {
187 self.os_data.cancelled = true;
188 var dir_it = self.os_data.dir_table.iterator();
189 while (dir_it.next()) |dir_entry| {
190 if (windows.kernel32.CancelIoEx(dir_entry.value.dir_handle, null) != 0) {
191 // We canceled the pending ReadDirectoryChangesW operation, but our
192 // frame is still suspending, now waiting indefinitely.
193 // Thus, it is safe to resume it ourslves
194 resume dir_entry.value.putter_frame;
195 } else {
196 std.debug.assert(windows.kernel32.GetLastError() == .NOT_FOUND);
197 // We are at another suspend point, we can await safely for the
198 // function to exit the loop
199 await dir_entry.value.putter_frame;
200 }
201
202 self.allocator.free(dir_entry.key_ptr.*);
203 var file_it = dir_entry.value.file_table.keyIterator();
204 while (file_it.next()) |file_entry| {
205 self.allocator.free(file_entry.*);
206 }
207 dir_entry.value.file_table.deinit(self.allocator);
208 self.allocator.destroy(dir_entry.value_ptr.*);
209 }
210 self.os_data.dir_table.deinit(self.allocator);
211 },
212 else => @compileError("Unsupported OS"),
213 }
214 self.allocator.free(self.channel.buffer_nodes);
215 self.channel.deinit();
216 self.allocator.destroy(self);
217 }
218
219 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
220 switch (builtin.os.tag) {
221 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => return addFileKEvent(self, file_path, value),
222 .linux => return addFileLinux(self, file_path, value),
223 .windows => return addFileWindows(self, file_path, value),
224 else => @compileError("Unsupported OS"),
225 }
226 }
227
228 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
229 var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
230 const realpath = try os.realpath(file_path, &realpath_buf);
231
232 const held = self.os_data.table_lock.acquire();
233 defer held.release();
234
235 const gop = try self.os_data.file_table.getOrPut(self.allocator, realpath);
236 errdefer assert(self.os_data.file_table.remove(realpath));
237 if (gop.found_existing) {
238 const prev_value = gop.value_ptr.value;
239 gop.value_ptr.value = value;
240 return prev_value;
241 }
242
243 gop.key_ptr.* = try self.allocator.dupe(u8, realpath);
244 errdefer self.allocator.free(gop.key_ptr.*);
245 gop.value_ptr.* = try self.allocator.create(OsData.Put);
246 errdefer self.allocator.destroy(gop.value_ptr.*);
247 gop.value_ptr.* = .{
248 .putter_frame = undefined,
249 .value = value,
250 };
251
252 // @TODO Can I close this fd and get an error from bsdWaitKev?
253 const flags = if (comptime builtin.target.isDarwin()) os.O.SYMLINK | os.O.EVTONLY else 0;
254 const fd = try os.open(realpath, flags, 0);
255 gop.value_ptr.putter_frame = async self.kqPutEvents(fd, gop.key_ptr.*, gop.value_ptr.*);
256 return null;
257 }
258
259 fn kqPutEvents(self: *Self, fd: os.fd_t, file_path: []const u8, put: *OsData.Put) void {
260 global_event_loop.beginOneEvent();
261 defer {
262 global_event_loop.finishOneEvent();
263 // @TODO: Remove this if we force close otherwise
264 os.close(fd);
265 }
266
267 // We need to manually do a bsdWaitKev to access the fflags.
268 var resume_node = event.Loop.ResumeNode.Basic{
269 .base = .{
270 .id = .Basic,
271 .handle = @frame(),
272 .overlapped = event.Loop.ResumeNode.overlapped_init,
273 },
274 .kev = undefined,
275 };
276
277 var kevs = [1]os.Kevent{undefined};
278 const kev = &kevs[0];
279
280 while (!put.cancelled) {
281 kev.* = os.Kevent{
282 .ident = @as(usize, @intCast(fd)),
283 .filter = os.EVFILT_VNODE,
284 .flags = os.EV_ADD | os.EV_ENABLE | os.EV_CLEAR | os.EV_ONESHOT |
285 os.NOTE_WRITE | os.NOTE_DELETE | os.NOTE_REVOKE,
286 .fflags = 0,
287 .data = 0,
288 .udata = @intFromPtr(&resume_node.base),
289 };
290 suspend {
291 global_event_loop.beginOneEvent();
292 errdefer global_event_loop.finishOneEvent();
293
294 const empty_kevs = &[0]os.Kevent{};
295 _ = os.kevent(global_event_loop.os_data.kqfd, &kevs, empty_kevs, null) catch |err| switch (err) {
296 error.EventNotFound,
297 error.ProcessNotFound,
298 error.Overflow,
299 => unreachable,
300 error.AccessDenied, error.SystemResources => |e| {
301 self.channel.put(e);
302 continue;
303 },
304 };
305 }
306
307 if (kev.flags & os.EV_ERROR != 0) {
308 self.channel.put(os.unexpectedErrno(os.errno(kev.data)));
309 continue;
310 }
311
312 if (kev.fflags & os.NOTE_DELETE != 0 or kev.fflags & os.NOTE_REVOKE != 0) {
313 self.channel.put(Self.Event{
314 .id = .Delete,
315 .data = put.value,
316 .dirname = std.fs.path.dirname(file_path) orelse "/",
317 .basename = std.fs.path.basename(file_path),
318 });
319 } else if (kev.fflags & os.NOTE_WRITE != 0) {
320 self.channel.put(Self.Event{
321 .id = .CloseWrite,
322 .data = put.value,
323 .dirname = std.fs.path.dirname(file_path) orelse "/",
324 .basename = std.fs.path.basename(file_path),
325 });
326 }
327 }
328 }
329
330 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
331 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
332 const basename = std.fs.path.basename(file_path);
333
334 const wd = try os.inotify_add_watch(
335 self.os_data.inotify_fd,
336 dirname,
337 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_DELETE | os.linux.IN_EXCL_UNLINK,
338 );
339 // wd is either a newly created watch or an existing one.
340
341 const held = self.os_data.table_lock.acquire();
342 defer held.release();
343
344 const gop = try self.os_data.wd_table.getOrPut(self.allocator, wd);
345 errdefer assert(self.os_data.wd_table.remove(wd));
346 if (!gop.found_existing) {
347 gop.value_ptr.* = OsData.Dir{
348 .dirname = try self.allocator.dupe(u8, dirname),
349 .file_table = OsData.FileTable.init(self.allocator),
350 };
351 }
352
353 const dir = gop.value_ptr;
354 const file_table_gop = try dir.file_table.getOrPut(self.allocator, basename);
355 errdefer assert(dir.file_table.remove(basename));
356 if (file_table_gop.found_existing) {
357 const prev_value = file_table_gop.value_ptr.*;
358 file_table_gop.value_ptr.* = value;
359 return prev_value;
360 } else {
361 file_table_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
362 file_table_gop.value_ptr.* = value;
363 return null;
364 }
365 }
366
367 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
368 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
369 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
370 var dirname_path_space: windows.PathSpace = undefined;
371 dirname_path_space.len = try std.unicode.utf8ToUtf16Le(&dirname_path_space.data, dirname);
372 dirname_path_space.data[dirname_path_space.len] = 0;
373
374 const basename = std.fs.path.basename(file_path);
375 var basename_path_space: windows.PathSpace = undefined;
376 basename_path_space.len = try std.unicode.utf8ToUtf16Le(&basename_path_space.data, basename);
377 basename_path_space.data[basename_path_space.len] = 0;
378
379 const held = self.os_data.table_lock.acquire();
380 defer held.release();
381
382 const gop = try self.os_data.dir_table.getOrPut(self.allocator, dirname);
383 errdefer assert(self.os_data.dir_table.remove(dirname));
384 if (gop.found_existing) {
385 const dir = gop.value_ptr.*;
386
387 const file_gop = try dir.file_table.getOrPut(self.allocator, basename);
388 errdefer assert(dir.file_table.remove(basename));
389 if (file_gop.found_existing) {
390 const prev_value = file_gop.value_ptr.*;
391 file_gop.value_ptr.* = value;
392 return prev_value;
393 } else {
394 file_gop.value_ptr.* = value;
395 file_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
396 return null;
397 }
398 } else {
399 const dir_handle = try windows.OpenFile(dirname_path_space.span(), .{
400 .dir = std.fs.cwd().fd,
401 .access_mask = windows.FILE_LIST_DIRECTORY,
402 .creation = windows.FILE_OPEN,
403 .io_mode = .evented,
404 .filter = .dir_only,
405 });
406 errdefer windows.CloseHandle(dir_handle);
407
408 const dir = try self.allocator.create(OsData.Dir);
409 errdefer self.allocator.destroy(dir);
410
411 gop.key_ptr.* = try self.allocator.dupe(u8, dirname);
412 errdefer self.allocator.free(gop.key_ptr.*);
413
414 dir.* = OsData.Dir{
415 .file_table = OsData.FileTable.init(self.allocator),
416 .putter_frame = undefined,
417 .dir_handle = dir_handle,
418 };
419 gop.value_ptr.* = dir;
420 try dir.file_table.put(self.allocator, try self.allocator.dupe(u8, basename), value);
421 dir.putter_frame = async self.windowsDirReader(dir, gop.key_ptr.*);
422 return null;
423 }
424 }
425
426 fn windowsDirReader(self: *Self, dir: *OsData.Dir, dirname: []const u8) void {
427 defer os.close(dir.dir_handle);
428 var resume_node = Loop.ResumeNode.Basic{
429 .base = Loop.ResumeNode{
430 .id = .Basic,
431 .handle = @frame(),
432 .overlapped = windows.OVERLAPPED{
433 .Internal = 0,
434 .InternalHigh = 0,
435 .DUMMYUNIONNAME = .{
436 .DUMMYSTRUCTNAME = .{
437 .Offset = 0,
438 .OffsetHigh = 0,
439 },
440 },
441 .hEvent = null,
442 },
443 },
444 };
445
446 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
447
448 global_event_loop.beginOneEvent();
449 defer global_event_loop.finishOneEvent();
450
451 while (!self.os_data.cancelled) main_loop: {
452 suspend {
453 _ = windows.kernel32.ReadDirectoryChangesW(
454 dir.dir_handle,
455 &event_buf,
456 event_buf.len,
457 windows.FALSE, // watch subtree
458 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
459 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
460 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
461 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
462 null, // number of bytes transferred (unused for async)
463 &resume_node.base.overlapped,
464 null, // completion routine - unused because we use IOCP
465 );
466 }
467
468 var bytes_transferred: windows.DWORD = undefined;
469 if (windows.kernel32.GetOverlappedResult(
470 dir.dir_handle,
471 &resume_node.base.overlapped,
472 &bytes_transferred,
473 windows.FALSE,
474 ) == 0) {
475 const potential_error = windows.kernel32.GetLastError();
476 const err = switch (potential_error) {
477 .OPERATION_ABORTED, .IO_INCOMPLETE => err_blk: {
478 if (self.os_data.cancelled)
479 break :main_loop
480 else
481 break :err_blk windows.unexpectedError(potential_error);
482 },
483 else => |err| windows.unexpectedError(err),
484 };
485 self.channel.put(err);
486 } else {
487 var ptr: [*]u8 = &event_buf;
488 const end_ptr = ptr + bytes_transferred;
489 while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
490 const ev = @as(*const windows.FILE_NOTIFY_INFORMATION, @ptrCast(ptr));
491 const emit = switch (ev.Action) {
492 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
493 windows.FILE_ACTION_MODIFIED => .CloseWrite,
494 else => null,
495 };
496 if (emit) |id| {
497 const basename_ptr = @as([*]u16, @ptrCast(ptr + @sizeOf(windows.FILE_NOTIFY_INFORMATION)));
498 const basename_utf16le = basename_ptr[0 .. ev.FileNameLength / 2];
499 var basename_data: [std.fs.MAX_PATH_BYTES]u8 = undefined;
500 const basename = basename_data[0 .. std.unicode.utf16leToUtf8(&basename_data, basename_utf16le) catch unreachable];
501
502 if (dir.file_table.getEntry(basename)) |entry| {
503 self.channel.put(Event{
504 .id = id,
505 .data = entry.value_ptr.*,
506 .dirname = dirname,
507 .basename = entry.key_ptr.*,
508 });
509 }
510 }
511
512 if (ev.NextEntryOffset == 0) break;
513 ptr = @alignCast(ptr + ev.NextEntryOffset);
514 }
515 }
516 }
517 }
518
519 pub fn removeFile(self: *Self, file_path: []const u8) !?V {
520 switch (builtin.os.tag) {
521 .linux => {
522 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
523 const basename = std.fs.path.basename(file_path);
524
525 const held = self.os_data.table_lock.acquire();
526 defer held.release();
527
528 const dir = self.os_data.wd_table.get(dirname) orelse return null;
529 if (dir.file_table.fetchRemove(basename)) |file_entry| {
530 self.allocator.free(file_entry.key);
531 return file_entry.value;
532 }
533 return null;
534 },
535 .windows => {
536 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
537 const basename = std.fs.path.basename(file_path);
538
539 const held = self.os_data.table_lock.acquire();
540 defer held.release();
541
542 const dir = self.os_data.dir_table.get(dirname) orelse return null;
543 if (dir.file_table.fetchRemove(basename)) |file_entry| {
544 self.allocator.free(file_entry.key);
545 return file_entry.value;
546 }
547 return null;
548 },
549 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
550 var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
551 const realpath = try os.realpath(file_path, &realpath_buf);
552
553 const held = self.os_data.table_lock.acquire();
554 defer held.release();
555
556 const entry = self.os_data.file_table.getEntry(realpath) orelse return null;
557 entry.value_ptr.cancelled = true;
558 // @TODO Close the fd here?
559 await entry.value_ptr.putter_frame;
560 self.allocator.free(entry.key_ptr.*);
561 self.allocator.destroy(entry.value_ptr.*);
562
563 assert(self.os_data.file_table.remove(realpath));
564 },
565 else => @compileError("Unsupported OS"),
566 }
567 }
568
569 fn linuxEventPutter(self: *Self) void {
570 global_event_loop.beginOneEvent();
571
572 defer {
573 std.debug.assert(self.os_data.wd_table.count() == 0);
574 self.os_data.wd_table.deinit(self.allocator);
575 os.close(self.os_data.inotify_fd);
576 self.allocator.free(self.channel.buffer_nodes);
577 self.channel.deinit();
578 global_event_loop.finishOneEvent();
579 }
580
581 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
582
583 while (!self.os_data.cancelled) {
584 const bytes_read = global_event_loop.read(self.os_data.inotify_fd, &event_buf, false) catch unreachable;
585
586 var ptr: [*]u8 = &event_buf;
587 const end_ptr = ptr + bytes_read;
588 while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
589 const ev = @as(*const os.linux.inotify_event, @ptrCast(ptr));
590 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
591 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
592 const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
593
594 const dir = &self.os_data.wd_table.get(ev.wd).?;
595 if (dir.file_table.getEntry(basename)) |file_value| {
596 self.channel.put(Event{
597 .id = .CloseWrite,
598 .data = file_value.value_ptr.*,
599 .dirname = dir.dirname,
600 .basename = file_value.key_ptr.*,
601 });
602 }
603 } else if (ev.mask & os.linux.IN_IGNORED == os.linux.IN_IGNORED) {
604 // Directory watch was removed
605 const held = self.os_data.table_lock.acquire();
606 defer held.release();
607 if (self.os_data.wd_table.fetchRemove(ev.wd)) |wd_entry| {
608 var file_it = wd_entry.value.file_table.keyIterator();
609 while (file_it.next()) |file_entry| {
610 self.allocator.free(file_entry.*);
611 }
612 self.allocator.free(wd_entry.value.dirname);
613 wd_entry.value.file_table.deinit(self.allocator);
614 }
615 } else if (ev.mask & os.linux.IN_DELETE == os.linux.IN_DELETE) {
616 // File or directory was removed or deleted
617 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
618 const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
619
620 const dir = &self.os_data.wd_table.get(ev.wd).?;
621 if (dir.file_table.getEntry(basename)) |file_value| {
622 self.channel.put(Event{
623 .id = .Delete,
624 .data = file_value.value_ptr.*,
625 .dirname = dir.dirname,
626 .basename = file_value.key_ptr.*,
627 });
628 }
629 }
630
631 ptr = @alignCast(ptr + @sizeOf(os.linux.inotify_event) + ev.len);
632 }
633 }
634 }
635 };
636}
637
638const test_tmp_dir = "std_event_fs_test";
639
640test "write a file, watch it, write it again, delete it" {
641 if (!std.io.is_async) return error.SkipZigTest;
642 // TODO https://github.com/ziglang/zig/issues/1908
643 if (builtin.single_threaded) return error.SkipZigTest;
644
645 try std.fs.cwd().makePath(test_tmp_dir);
646 defer std.fs.cwd().deleteTree(test_tmp_dir) catch {};
647
648 return testWriteWatchWriteDelete(std.testing.allocator);
649}
650
651fn testWriteWatchWriteDelete(allocator: Allocator) !void {
652 const file_path = try std.fs.path.join(allocator, &[_][]const u8{ test_tmp_dir, "file.txt" });
653 defer allocator.free(file_path);
654
655 const contents =
656 \\line 1
657 \\line 2
658 ;
659 const line2_offset = 7;
660
661 // first just write then read the file
662 try std.fs.cwd().writeFile(file_path, contents);
663
664 const read_contents = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
665 defer allocator.free(read_contents);
666 try testing.expectEqualSlices(u8, contents, read_contents);
667
668 // now watch the file
669 var watch = try Watch(void).init(allocator, 0);
670 defer watch.deinit();
671
672 try testing.expect((try watch.addFile(file_path, {})) == null);
673
674 var ev = async watch.channel.get();
675 var ev_consumed = false;
676 defer if (!ev_consumed) {
677 _ = await ev;
678 };
679
680 // overwrite line 2
681 const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_write });
682 {
683 defer file.close();
684 const write_contents = "lorem ipsum";
685 var iovec = [_]os.iovec_const{.{
686 .iov_base = write_contents,
687 .iov_len = write_contents.len,
688 }};
689 _ = try file.pwritevAll(&iovec, line2_offset);
690 }
691
692 switch ((try await ev).id) {
693 .CloseWrite => {
694 ev_consumed = true;
695 },
696 .Delete => @panic("wrong event"),
697 }
698
699 const contents_updated = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
700 defer allocator.free(contents_updated);
701
702 try testing.expectEqualSlices(u8,
703 \\line 1
704 \\lorem ipsum
705 , contents_updated);
706
707 ev = async watch.channel.get();
708 ev_consumed = false;
709
710 try std.fs.cwd().deleteFile(file_path);
711 switch ((try await ev).id) {
712 .Delete => {
713 ev_consumed = true;
714 },
715 .CloseWrite => @panic("wrong event"),
716 }
717}
718
719// TODO Test: Add another file watch, remove the old file watch, get an event in the new
lib/std/io.zig+3-37
......@@ -12,22 +12,6 @@ const meta = std.meta;
1212const File = std.fs.File;
1313const Allocator = std.mem.Allocator;
1414
15pub const Mode = enum {
16 /// I/O operates normally, waiting for the operating system syscalls to complete.
17 blocking,
18
19 /// I/O functions are generated async and rely on a global event loop. Event-based I/O.
20 evented,
21};
22
23const mode = std.options.io_mode;
24pub const is_async = mode != .blocking;
25
26/// This is an enum value to use for I/O mode at runtime, since it takes up zero bytes at runtime,
27/// and makes expressions comptime-known when `is_async` is `false`.
28pub const ModeOverride = if (is_async) Mode else enum { blocking };
29pub const default_mode: ModeOverride = if (is_async) Mode.evented else .blocking;
30
3115fn getStdOutHandle() os.fd_t {
3216 if (builtin.os.tag == .windows) {
3317 if (builtin.zig_backend == .stage2_aarch64) {
......@@ -44,14 +28,8 @@ fn getStdOutHandle() os.fd_t {
4428 return os.STDOUT_FILENO;
4529}
4630
47/// TODO: async stdout on windows without a dedicated thread.
48/// https://github.com/ziglang/zig/pull/4816#issuecomment-604521023
4931pub fn getStdOut() File {
50 return File{
51 .handle = getStdOutHandle(),
52 .capable_io_mode = .blocking,
53 .intended_io_mode = default_mode,
54 };
32 return File{ .handle = getStdOutHandle() };
5533}
5634
5735fn getStdErrHandle() os.fd_t {
......@@ -70,14 +48,8 @@ fn getStdErrHandle() os.fd_t {
7048 return os.STDERR_FILENO;
7149}
7250
73/// This returns a `File` that is configured to block with every write, in order
74/// to facilitate better debugging. This can be changed by modifying the `intended_io_mode` field.
7551pub fn getStdErr() File {
76 return File{
77 .handle = getStdErrHandle(),
78 .capable_io_mode = .blocking,
79 .intended_io_mode = .blocking,
80 };
52 return File{ .handle = getStdErrHandle() };
8153}
8254
8355fn getStdInHandle() os.fd_t {
......@@ -96,14 +68,8 @@ fn getStdInHandle() os.fd_t {
9668 return os.STDIN_FILENO;
9769}
9870
99/// TODO: async stdin on windows without a dedicated thread.
100/// https://github.com/ziglang/zig/pull/4816#issuecomment-604521023
10171pub fn getStdIn() File {
102 return File{
103 .handle = getStdInHandle(),
104 .capable_io_mode = .blocking,
105 .intended_io_mode = default_mode,
106 };
72 return File{ .handle = getStdInHandle() };
10773}
10874
10975pub fn GenericReader(
lib/std/log.zig+3-3
......@@ -18,12 +18,12 @@
1818//! ```
1919//! const std = @import("std");
2020//!
21//! pub const std_options = struct {
21//! pub const std_options = .{
2222//! // Set the log level to info
23//! pub const log_level = .info;
23//! .log_level = .info,
2424//!
2525//! // Define logFn to override the std implementation
26//! pub const logFn = myLogFn;
26//! .logFn = myLogFn,
2727//! };
2828//!
2929//! pub fn myLogFn(
lib/std/net.zig+16-63
......@@ -651,7 +651,7 @@ pub const Ip6Address = extern struct {
651651};
652652
653653pub fn connectUnixSocket(path: []const u8) !Stream {
654 const opt_non_block = if (std.io.is_async) os.SOCK.NONBLOCK else 0;
654 const opt_non_block = 0;
655655 const sockfd = try os.socket(
656656 os.AF.UNIX,
657657 os.SOCK.STREAM | os.SOCK.CLOEXEC | opt_non_block,
......@@ -660,17 +660,9 @@ pub fn connectUnixSocket(path: []const u8) !Stream {
660660 errdefer os.closeSocket(sockfd);
661661
662662 var addr = try std.net.Address.initUnix(path);
663 try os.connect(sockfd, &addr.any, addr.getOsSockLen());
663664
664 if (std.io.is_async) {
665 const loop = std.event.Loop.instance orelse return error.WouldBlock;
666 try loop.connect(sockfd, &addr.any, addr.getOsSockLen());
667 } else {
668 try os.connect(sockfd, &addr.any, addr.getOsSockLen());
669 }
670
671 return Stream{
672 .handle = sockfd,
673 };
665 return Stream{ .handle = sockfd };
674666}
675667
676668fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 {
......@@ -742,18 +734,13 @@ pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) T
742734pub const TcpConnectToAddressError = std.os.SocketError || std.os.ConnectError;
743735
744736pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
745 const nonblock = if (std.io.is_async) os.SOCK.NONBLOCK else 0;
737 const nonblock = 0;
746738 const sock_flags = os.SOCK.STREAM | nonblock |
747739 (if (builtin.target.os.tag == .windows) 0 else os.SOCK.CLOEXEC);
748740 const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO.TCP);
749741 errdefer os.closeSocket(sockfd);
750742
751 if (std.io.is_async) {
752 const loop = std.event.Loop.instance orelse return error.WouldBlock;
753 try loop.connect(sockfd, &address.any, address.getOsSockLen());
754 } else {
755 try os.connect(sockfd, &address.any, address.getOsSockLen());
756 }
743 try os.connect(sockfd, &address.any, address.getOsSockLen());
757744
758745 return Stream{ .handle = sockfd };
759746}
......@@ -1618,11 +1605,7 @@ fn resMSendRc(
16181605 if (answers[i].len == 0) {
16191606 var j: usize = 0;
16201607 while (j < ns.len) : (j += 1) {
1621 if (std.io.is_async) {
1622 _ = std.event.Loop.instance.?.sendto(fd, queries[i], os.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1623 } else {
1624 _ = os.sendto(fd, queries[i], os.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1625 }
1608 _ = os.sendto(fd, queries[i], os.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
16261609 }
16271610 }
16281611 }
......@@ -1637,10 +1620,7 @@ fn resMSendRc(
16371620
16381621 while (true) {
16391622 var sl_copy = sl;
1640 const rlen = if (std.io.is_async)
1641 std.event.Loop.instance.?.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break
1642 else
1643 os.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
1623 const rlen = os.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
16441624
16451625 // Ignore non-identifiable packets
16461626 if (rlen < 4) continue;
......@@ -1666,11 +1646,7 @@ fn resMSendRc(
16661646 0, 3 => {},
16671647 2 => if (servfail_retry != 0) {
16681648 servfail_retry -= 1;
1669 if (std.io.is_async) {
1670 _ = std.event.Loop.instance.?.sendto(fd, queries[i], os.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1671 } else {
1672 _ = os.sendto(fd, queries[i], os.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1673 }
1649 _ = os.sendto(fd, queries[i], os.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
16741650 },
16751651 else => continue,
16761652 }
......@@ -1778,14 +1754,10 @@ pub const Stream = struct {
17781754
17791755 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
17801756 if (builtin.os.tag == .windows) {
1781 return os.windows.ReadFile(self.handle, buffer, null, io.default_mode);
1757 return os.windows.ReadFile(self.handle, buffer, null);
17821758 }
17831759
1784 if (std.io.is_async) {
1785 return std.event.Loop.instance.?.read(self.handle, buffer, false);
1786 } else {
1787 return os.read(self.handle, buffer);
1788 }
1760 return os.read(self.handle, buffer);
17891761 }
17901762
17911763 pub fn readv(s: Stream, iovecs: []const os.iovec) ReadError!usize {
......@@ -1793,7 +1765,7 @@ pub const Stream = struct {
17931765 // TODO improve this to use ReadFileScatter
17941766 if (iovecs.len == 0) return @as(usize, 0);
17951767 const first = iovecs[0];
1796 return os.windows.ReadFile(s.handle, first.iov_base[0..first.iov_len], null, io.default_mode);
1768 return os.windows.ReadFile(s.handle, first.iov_base[0..first.iov_len], null);
17971769 }
17981770
17991771 return os.readv(s.handle, iovecs);
......@@ -1827,14 +1799,10 @@ pub const Stream = struct {
18271799 /// use non-blocking I/O.
18281800 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
18291801 if (builtin.os.tag == .windows) {
1830 return os.windows.WriteFile(self.handle, buffer, null, io.default_mode);
1802 return os.windows.WriteFile(self.handle, buffer, null);
18311803 }
18321804
1833 if (std.io.is_async) {
1834 return std.event.Loop.instance.?.write(self.handle, buffer, false);
1835 } else {
1836 return os.write(self.handle, buffer);
1837 }
1805 return os.write(self.handle, buffer);
18381806 }
18391807
18401808 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
......@@ -1847,15 +1815,7 @@ pub const Stream = struct {
18471815 /// See https://github.com/ziglang/zig/issues/7699
18481816 /// See equivalent function: `std.fs.File.writev`.
18491817 pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize {
1850 if (std.io.is_async) {
1851 // TODO improve to actually take advantage of writev syscall, if available.
1852 if (iovecs.len == 0) return 0;
1853 const first_buffer = iovecs[0].iov_base[0..iovecs[0].iov_len];
1854 try self.write(first_buffer);
1855 return first_buffer.len;
1856 } else {
1857 return os.writev(self.handle, iovecs);
1858 }
1818 return os.writev(self.handle, iovecs);
18591819 }
18601820
18611821 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
......@@ -1927,7 +1887,7 @@ pub const StreamServer = struct {
19271887 }
19281888
19291889 pub fn listen(self: *StreamServer, address: Address) !void {
1930 const nonblock = if (std.io.is_async) os.SOCK.NONBLOCK else 0;
1890 const nonblock = 0;
19311891 const sock_flags = os.SOCK.STREAM | os.SOCK.CLOEXEC | nonblock;
19321892 var use_sock_flags: u32 = sock_flags;
19331893 if (self.force_nonblocking) use_sock_flags |= os.SOCK.NONBLOCK;
......@@ -2016,14 +1976,7 @@ pub const StreamServer = struct {
20161976 pub fn accept(self: *StreamServer) AcceptError!Connection {
20171977 var accepted_addr: Address = undefined;
20181978 var adr_len: os.socklen_t = @sizeOf(Address);
2019 const accept_result = blk: {
2020 if (std.io.is_async) {
2021 const loop = std.event.Loop.instance orelse return error.UnexpectedError;
2022 break :blk loop.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK.CLOEXEC);
2023 } else {
2024 break :blk os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK.CLOEXEC);
2025 }
2026 };
1979 const accept_result = os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, os.SOCK.CLOEXEC);
20271980
20281981 if (accept_result) |fd| {
20291982 return Connection{
lib/std/net/test.zig-48
......@@ -207,54 +207,6 @@ test "listen on a port, send bytes, receive bytes" {
207207 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
208208}
209209
210test "listen on a port, send bytes, receive bytes, async-only" {
211 if (!std.io.is_async) return error.SkipZigTest;
212
213 if (builtin.os.tag != .linux and !builtin.os.tag.isDarwin()) {
214 // TODO build abstractions for other operating systems
215 return error.SkipZigTest;
216 }
217
218 // TODO doing this at comptime crashed the compiler
219 const localhost = try net.Address.parseIp("127.0.0.1", 0);
220
221 var server = net.StreamServer.init(net.StreamServer.Options{});
222 defer server.deinit();
223 try server.listen(localhost);
224
225 var server_frame = async testServer(&server);
226 var client_frame = async testClient(server.listen_address);
227
228 try await server_frame;
229 try await client_frame;
230}
231
232test "listen on ipv4 try connect on ipv6 then ipv4" {
233 if (!std.io.is_async) return error.SkipZigTest;
234
235 if (builtin.os.tag != .linux and !builtin.os.tag.isDarwin()) {
236 // TODO build abstractions for other operating systems
237 return error.SkipZigTest;
238 }
239
240 // TODO doing this at comptime crashed the compiler
241 const localhost = try net.Address.parseIp("127.0.0.1", 0);
242
243 var server = net.StreamServer.init(net.StreamServer.Options{});
244 defer server.deinit();
245 try server.listen(localhost);
246
247 var server_frame = async testServer(&server);
248 var client_frame = async testClientToHost(
249 testing.allocator,
250 "localhost",
251 server.listen_address.getPort(),
252 );
253
254 try await server_frame;
255 try await client_frame;
256}
257
258210test "listen on an in use port" {
259211 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin()) {
260212 // TODO build abstractions for other operating systems
lib/std/os.zig+6-29
......@@ -683,11 +683,7 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
683683 return error.NoDevice;
684684 }
685685
686 const file = std.fs.File{
687 .handle = fd,
688 .capable_io_mode = .blocking,
689 .intended_io_mode = .blocking,
690 };
686 const file = std.fs.File{ .handle = fd };
691687 const stream = file.reader();
692688 stream.readNoEof(buf) catch return error.Unexpected;
693689}
......@@ -856,7 +852,7 @@ pub const ReadError = error{
856852pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
857853 if (buf.len == 0) return 0;
858854 if (builtin.os.tag == .windows) {
859 return windows.ReadFile(fd, buf, null, std.io.default_mode);
855 return windows.ReadFile(fd, buf, null);
860856 }
861857 if (builtin.os.tag == .wasi and !builtin.link_libc) {
862858 const iovs = [1]iovec{iovec{
......@@ -995,7 +991,7 @@ pub const PReadError = ReadError || error{Unseekable};
995991pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
996992 if (buf.len == 0) return 0;
997993 if (builtin.os.tag == .windows) {
998 return windows.ReadFile(fd, buf, offset, std.io.default_mode);
994 return windows.ReadFile(fd, buf, offset);
999995 }
1000996 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1001997 const iovs = [1]iovec{iovec{
......@@ -1257,7 +1253,7 @@ pub const WriteError = error{
12571253pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
12581254 if (bytes.len == 0) return 0;
12591255 if (builtin.os.tag == .windows) {
1260 return windows.WriteFile(fd, bytes, null, std.io.default_mode);
1256 return windows.WriteFile(fd, bytes, null);
12611257 }
12621258
12631259 if (builtin.os.tag == .wasi and !builtin.link_libc) {
......@@ -1415,7 +1411,7 @@ pub const PWriteError = WriteError || error{Unseekable};
14151411pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
14161412 if (bytes.len == 0) return 0;
14171413 if (builtin.os.tag == .windows) {
1418 return windows.WriteFile(fd, bytes, offset, std.io.default_mode);
1414 return windows.WriteFile(fd, bytes, offset);
14191415 }
14201416 if (builtin.os.tag == .wasi and !builtin.link_libc) {
14211417 const ciovs = [1]iovec_const{iovec_const{
......@@ -1711,7 +1707,6 @@ fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions {
17111707
17121708 return .{
17131709 .access_mask = access_mask,
1714 .io_mode = .blocking,
17151710 .creation = creation,
17161711 .filter = filter,
17171712 .follow_symlinks = follow_symlinks,
......@@ -2797,7 +2792,6 @@ pub fn renameatW(
27972792 .dir = old_dir_fd,
27982793 .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE,
27992794 .creation = windows.FILE_OPEN,
2800 .io_mode = .blocking,
28012795 .filter = .any, // This function is supposed to rename both files and directories.
28022796 .follow_symlinks = false,
28032797 }) catch |err| switch (err) {
......@@ -2962,7 +2956,6 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!v
29622956 .dir = dir_fd,
29632957 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
29642958 .creation = windows.FILE_CREATE,
2965 .io_mode = .blocking,
29662959 .filter = .dir_only,
29672960 }) catch |err| switch (err) {
29682961 error.IsDir => unreachable,
......@@ -3042,7 +3035,6 @@ pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
30423035 .dir = std.fs.cwd().fd,
30433036 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
30443037 .creation = windows.FILE_CREATE,
3045 .io_mode = .blocking,
30463038 .filter = .dir_only,
30473039 }) catch |err| switch (err) {
30483040 error.IsDir => unreachable,
......@@ -5440,7 +5432,6 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
54405432 .access_mask = access_mask,
54415433 .share_access = share_access,
54425434 .creation = creation,
5443 .io_mode = .blocking,
54445435 .filter = .any,
54455436 }) catch |err| switch (err) {
54465437 error.WouldBlock => unreachable,
......@@ -6404,12 +6395,7 @@ pub fn sendfile(
64046395 // manually, the same as ENOSYS.
64056396 break :sf;
64066397 },
6407 .AGAIN => if (std.event.Loop.instance) |loop| {
6408 loop.waitUntilFdWritable(out_fd);
6409 continue;
6410 } else {
6411 return error.WouldBlock;
6412 },
6398 .AGAIN => return error.WouldBlock,
64136399 .IO => return error.InputOutput,
64146400 .PIPE => return error.BrokenPipe,
64156401 .NOMEM => return error.SystemResources,
......@@ -6476,18 +6462,12 @@ pub fn sendfile(
64766462
64776463 .AGAIN => if (amt != 0) {
64786464 return amt;
6479 } else if (std.event.Loop.instance) |loop| {
6480 loop.waitUntilFdWritable(out_fd);
6481 continue;
64826465 } else {
64836466 return error.WouldBlock;
64846467 },
64856468
64866469 .BUSY => if (amt != 0) {
64876470 return amt;
6488 } else if (std.event.Loop.instance) |loop| {
6489 loop.waitUntilFdReadable(in_fd);
6490 continue;
64916471 } else {
64926472 return error.WouldBlock;
64936473 },
......@@ -6550,9 +6530,6 @@ pub fn sendfile(
65506530
65516531 .AGAIN => if (amt != 0) {
65526532 return amt;
6553 } else if (std.event.Loop.instance) |loop| {
6554 loop.waitUntilFdWritable(out_fd);
6555 continue;
65566533 } else {
65576534 return error.WouldBlock;
65586535 },
lib/std/os/windows.zig+50-150
......@@ -49,7 +49,6 @@ pub const OpenFileOptions = struct {
4949 sa: ?*SECURITY_ATTRIBUTES = null,
5050 share_access: ULONG = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
5151 creation: ULONG,
52 io_mode: std.io.ModeOverride,
5352 /// If true, tries to open path as a directory.
5453 /// Defaults to false.
5554 filter: Filter = .file_only,
......@@ -95,7 +94,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
9594 .SecurityQualityOfService = null,
9695 };
9796 var io: IO_STATUS_BLOCK = undefined;
98 const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
97 const blocking_flag: ULONG = FILE_SYNCHRONOUS_IO_NONALERT;
9998 const file_or_dir_flag: ULONG = switch (options.filter) {
10099 .file_only => FILE_NON_DIRECTORY_FILE,
101100 .dir_only => FILE_DIRECTORY_FILE,
......@@ -119,12 +118,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
119118 0,
120119 );
121120 switch (rc) {
122 .SUCCESS => {
123 if (std.io.is_async and options.io_mode == .evented) {
124 _ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined;
125 }
126 return result;
127 },
121 .SUCCESS => return result,
128122 .OBJECT_NAME_INVALID => unreachable,
129123 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
130124 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
......@@ -457,81 +451,36 @@ pub const ReadFileError = error{
457451
458452/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
459453/// multiple non-atomic reads.
460pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64, io_mode: std.io.ModeOverride) ReadFileError!usize {
461 if (io_mode != .blocking) {
462 const loop = std.event.Loop.instance.?;
463 // TODO make getting the file position non-blocking
464 const off = if (offset) |o| o else try SetFilePointerEx_CURRENT_get(in_hFile);
465 var resume_node = std.event.Loop.ResumeNode.Basic{
466 .base = .{
467 .id = .Basic,
468 .handle = @frame(),
469 .overlapped = OVERLAPPED{
470 .Internal = 0,
471 .InternalHigh = 0,
472 .DUMMYUNIONNAME = .{
473 .DUMMYSTRUCTNAME = .{
474 .Offset = @as(u32, @truncate(off)),
475 .OffsetHigh = @as(u32, @truncate(off >> 32)),
476 },
454pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
455 while (true) {
456 const want_read_count: DWORD = @min(@as(DWORD, maxInt(DWORD)), buffer.len);
457 var amt_read: DWORD = undefined;
458 var overlapped_data: OVERLAPPED = undefined;
459 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
460 overlapped_data = .{
461 .Internal = 0,
462 .InternalHigh = 0,
463 .DUMMYUNIONNAME = .{
464 .DUMMYSTRUCTNAME = .{
465 .Offset = @as(u32, @truncate(off)),
466 .OffsetHigh = @as(u32, @truncate(off >> 32)),
477467 },
478 .hEvent = null,
479468 },
480 },
481 };
482 loop.beginOneEvent();
483 suspend {
484 // TODO handle buffer bigger than DWORD can hold
485 _ = kernel32.ReadFile(in_hFile, buffer.ptr, @as(DWORD, @intCast(buffer.len)), null, &resume_node.base.overlapped);
486 }
487 var bytes_transferred: DWORD = undefined;
488 if (kernel32.GetOverlappedResult(in_hFile, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
469 .hEvent = null,
470 };
471 break :blk &overlapped_data;
472 } else null;
473 if (kernel32.ReadFile(in_hFile, buffer.ptr, want_read_count, &amt_read, overlapped) == 0) {
489474 switch (kernel32.GetLastError()) {
490475 .IO_PENDING => unreachable,
491 .OPERATION_ABORTED => return error.OperationAborted,
492 .BROKEN_PIPE => return error.BrokenPipe,
476 .OPERATION_ABORTED => continue,
477 .BROKEN_PIPE => return 0,
478 .HANDLE_EOF => return 0,
493479 .NETNAME_DELETED => return error.NetNameDeleted,
494 .HANDLE_EOF => return @as(usize, bytes_transferred),
495480 else => |err| return unexpectedError(err),
496481 }
497482 }
498 if (offset == null) {
499 // TODO make setting the file position non-blocking
500 const new_off = off + bytes_transferred;
501 try SetFilePointerEx_CURRENT(in_hFile, @as(i64, @bitCast(new_off)));
502 }
503 return @as(usize, bytes_transferred);
504 } else {
505 while (true) {
506 const want_read_count: DWORD = @min(@as(DWORD, maxInt(DWORD)), buffer.len);
507 var amt_read: DWORD = undefined;
508 var overlapped_data: OVERLAPPED = undefined;
509 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
510 overlapped_data = .{
511 .Internal = 0,
512 .InternalHigh = 0,
513 .DUMMYUNIONNAME = .{
514 .DUMMYSTRUCTNAME = .{
515 .Offset = @as(u32, @truncate(off)),
516 .OffsetHigh = @as(u32, @truncate(off >> 32)),
517 },
518 },
519 .hEvent = null,
520 };
521 break :blk &overlapped_data;
522 } else null;
523 if (kernel32.ReadFile(in_hFile, buffer.ptr, want_read_count, &amt_read, overlapped) == 0) {
524 switch (kernel32.GetLastError()) {
525 .IO_PENDING => unreachable,
526 .OPERATION_ABORTED => continue,
527 .BROKEN_PIPE => return 0,
528 .HANDLE_EOF => return 0,
529 .NETNAME_DELETED => return error.NetNameDeleted,
530 else => |err| return unexpectedError(err),
531 }
532 }
533 return amt_read;
534 }
483 return amt_read;
535484 }
536485}
537486
......@@ -550,85 +499,38 @@ pub fn WriteFile(
550499 handle: HANDLE,
551500 bytes: []const u8,
552501 offset: ?u64,
553 io_mode: std.io.ModeOverride,
554502) WriteFileError!usize {
555 if (std.event.Loop.instance != null and io_mode != .blocking) {
556 const loop = std.event.Loop.instance.?;
557 // TODO make getting the file position non-blocking
558 const off = if (offset) |o| o else try SetFilePointerEx_CURRENT_get(handle);
559 var resume_node = std.event.Loop.ResumeNode.Basic{
560 .base = .{
561 .id = .Basic,
562 .handle = @frame(),
563 .overlapped = OVERLAPPED{
564 .Internal = 0,
565 .InternalHigh = 0,
566 .DUMMYUNIONNAME = .{
567 .DUMMYSTRUCTNAME = .{
568 .Offset = @as(u32, @truncate(off)),
569 .OffsetHigh = @as(u32, @truncate(off >> 32)),
570 },
571 },
572 .hEvent = null,
503 var bytes_written: DWORD = undefined;
504 var overlapped_data: OVERLAPPED = undefined;
505 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
506 overlapped_data = .{
507 .Internal = 0,
508 .InternalHigh = 0,
509 .DUMMYUNIONNAME = .{
510 .DUMMYSTRUCTNAME = .{
511 .Offset = @as(u32, @truncate(off)),
512 .OffsetHigh = @as(u32, @truncate(off >> 32)),
573513 },
574514 },
515 .hEvent = null,
575516 };
576 loop.beginOneEvent();
577 suspend {
578 const adjusted_len = math.cast(DWORD, bytes.len) orelse maxInt(DWORD);
579 _ = kernel32.WriteFile(handle, bytes.ptr, adjusted_len, null, &resume_node.base.overlapped);
580 }
581 var bytes_transferred: DWORD = undefined;
582 if (kernel32.GetOverlappedResult(handle, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
583 switch (kernel32.GetLastError()) {
584 .IO_PENDING => unreachable,
585 .INVALID_USER_BUFFER => return error.SystemResources,
586 .NOT_ENOUGH_MEMORY => return error.SystemResources,
587 .OPERATION_ABORTED => return error.OperationAborted,
588 .NOT_ENOUGH_QUOTA => return error.SystemResources,
589 .BROKEN_PIPE => return error.BrokenPipe,
590 else => |err| return unexpectedError(err),
591 }
592 }
593 if (offset == null) {
594 // TODO make setting the file position non-blocking
595 const new_off = off + bytes_transferred;
596 try SetFilePointerEx_CURRENT(handle, @as(i64, @bitCast(new_off)));
597 }
598 return bytes_transferred;
599 } else {
600 var bytes_written: DWORD = undefined;
601 var overlapped_data: OVERLAPPED = undefined;
602 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
603 overlapped_data = .{
604 .Internal = 0,
605 .InternalHigh = 0,
606 .DUMMYUNIONNAME = .{
607 .DUMMYSTRUCTNAME = .{
608 .Offset = @as(u32, @truncate(off)),
609 .OffsetHigh = @as(u32, @truncate(off >> 32)),
610 },
611 },
612 .hEvent = null,
613 };
614 break :blk &overlapped_data;
615 } else null;
616 const adjusted_len = math.cast(u32, bytes.len) orelse maxInt(u32);
617 if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) {
618 switch (kernel32.GetLastError()) {
619 .INVALID_USER_BUFFER => return error.SystemResources,
620 .NOT_ENOUGH_MEMORY => return error.SystemResources,
621 .OPERATION_ABORTED => return error.OperationAborted,
622 .NOT_ENOUGH_QUOTA => return error.SystemResources,
623 .IO_PENDING => unreachable,
624 .BROKEN_PIPE => return error.BrokenPipe,
625 .INVALID_HANDLE => return error.NotOpenForWriting,
626 .LOCK_VIOLATION => return error.LockViolation,
627 else => |err| return unexpectedError(err),
628 }
517 break :blk &overlapped_data;
518 } else null;
519 const adjusted_len = math.cast(u32, bytes.len) orelse maxInt(u32);
520 if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) {
521 switch (kernel32.GetLastError()) {
522 .INVALID_USER_BUFFER => return error.SystemResources,
523 .NOT_ENOUGH_MEMORY => return error.SystemResources,
524 .OPERATION_ABORTED => return error.OperationAborted,
525 .NOT_ENOUGH_QUOTA => return error.SystemResources,
526 .IO_PENDING => unreachable,
527 .BROKEN_PIPE => return error.BrokenPipe,
528 .INVALID_HANDLE => return error.NotOpenForWriting,
529 .LOCK_VIOLATION => return error.LockViolation,
530 else => |err| return unexpectedError(err),
629531 }
630 return bytes_written;
631532 }
533 return bytes_written;
632534}
633535
634536pub const SetCurrentDirectoryError = error{
......@@ -732,7 +634,6 @@ pub fn CreateSymbolicLink(
732634 .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
733635 .dir = dir,
734636 .creation = FILE_CREATE,
735 .io_mode = .blocking,
736637 .filter = if (is_directory) .dir_only else .file_only,
737638 }) catch |err| switch (err) {
738639 error.IsDir => return error.PathAlreadyExists,
......@@ -1256,7 +1157,6 @@ pub fn GetFinalPathNameByHandle(
12561157 .access_mask = SYNCHRONIZE,
12571158 .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
12581159 .creation = FILE_OPEN,
1259 .io_mode = .blocking,
12601160 }) catch |err| switch (err) {
12611161 error.IsDir => unreachable,
12621162 error.NotDir => unreachable,
lib/std/pdb.zig+1-1
......@@ -513,7 +513,7 @@ pub const Pdb = struct {
513513 };
514514
515515 pub fn init(allocator: mem.Allocator, path: []const u8) !Pdb {
516 const file = try fs.cwd().openFile(path, .{ .intended_io_mode = .blocking });
516 const file = try fs.cwd().openFile(path, .{});
517517 errdefer file.close();
518518
519519 return Pdb{
lib/std/start.zig+8-82
......@@ -347,7 +347,7 @@ fn WinStartup() callconv(std.os.windows.WINAPI) noreturn {
347347
348348 std.debug.maybeEnableSegfaultHandler();
349349
350 std.os.windows.ntdll.RtlExitUserProcess(initEventLoopAndCallMain());
350 std.os.windows.ntdll.RtlExitUserProcess(callMain());
351351}
352352
353353fn wWinMainCRTStartup() callconv(std.os.windows.WINAPI) noreturn {
......@@ -358,7 +358,7 @@ fn wWinMainCRTStartup() callconv(std.os.windows.WINAPI) noreturn {
358358
359359 std.debug.maybeEnableSegfaultHandler();
360360
361 const result: std.os.windows.INT = initEventLoopAndCallWinMain();
361 const result: std.os.windows.INT = call_wWinMain();
362362 std.os.windows.ntdll.RtlExitUserProcess(@as(std.os.windows.UINT, @bitCast(result)));
363363}
364364
......@@ -422,7 +422,7 @@ fn posixCallMainAndExit() callconv(.C) noreturn {
422422 expandStackSize(phdrs);
423423 }
424424
425 std.os.exit(@call(.always_inline, callMainWithArgs, .{ argc, argv, envp }));
425 std.os.exit(callMainWithArgs(argc, argv, envp));
426426}
427427
428428fn expandStackSize(phdrs: []elf.Phdr) void {
......@@ -459,14 +459,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
459459 }
460460}
461461
462fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
462inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
463463 std.os.argv = argv[0..argc];
464464 std.os.environ = envp;
465465
466466 std.debug.maybeEnableSegfaultHandler();
467467 std.os.maybeIgnoreSigpipe();
468468
469 return initEventLoopAndCallMain();
469 return callMain();
470470}
471471
472472fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.C) c_int {
......@@ -481,92 +481,18 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
481481 expandStackSize(phdrs);
482482 }
483483
484 return @call(.always_inline, callMainWithArgs, .{ @as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), envp });
484 return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), envp);
485485}
486486
487487fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.C) c_int {
488488 std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@as(usize, @intCast(c_argc))];
489 return @call(.always_inline, callMain, .{});
489 return callMain();
490490}
491491
492492// General error message for a malformed return type
493493const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'";
494494
495// This is marked inline because for some reason LLVM in release mode fails to inline it,
496// and we want fewer call frames in stack traces.
497inline fn initEventLoopAndCallMain() u8 {
498 if (std.event.Loop.instance) |loop| {
499 if (loop == std.event.Loop.default_instance) {
500 loop.init() catch |err| {
501 std.log.err("{s}", .{@errorName(err)});
502 if (@errorReturnTrace()) |trace| {
503 std.debug.dumpStackTrace(trace.*);
504 }
505 return 1;
506 };
507 defer loop.deinit();
508
509 var result: u8 = undefined;
510 var frame: @Frame(callMainAsync) = undefined;
511 _ = @asyncCall(&frame, &result, callMainAsync, .{loop});
512 loop.run();
513 return result;
514 }
515 }
516
517 // This is marked inline because for some reason LLVM in release mode fails to inline it,
518 // and we want fewer call frames in stack traces.
519 return @call(.always_inline, callMain, .{});
520}
521
522// This is marked inline because for some reason LLVM in release mode fails to inline it,
523// and we want fewer call frames in stack traces.
524// TODO This function is duplicated from initEventLoopAndCallMain instead of using generics
525// because it is working around stage1 compiler bugs.
526inline fn initEventLoopAndCallWinMain() std.os.windows.INT {
527 if (std.event.Loop.instance) |loop| {
528 if (loop == std.event.Loop.default_instance) {
529 loop.init() catch |err| {
530 std.log.err("{s}", .{@errorName(err)});
531 if (@errorReturnTrace()) |trace| {
532 std.debug.dumpStackTrace(trace.*);
533 }
534 return 1;
535 };
536 defer loop.deinit();
537
538 var result: std.os.windows.INT = undefined;
539 var frame: @Frame(callWinMainAsync) = undefined;
540 _ = @asyncCall(&frame, &result, callWinMainAsync, .{loop});
541 loop.run();
542 return result;
543 }
544 }
545
546 // This is marked inline because for some reason LLVM in release mode fails to inline it,
547 // and we want fewer call frames in stack traces.
548 return @call(.always_inline, call_wWinMain, .{});
549}
550
551fn callMainAsync(loop: *std.event.Loop) callconv(.Async) u8 {
552 // This prevents the event loop from terminating at least until main() has returned.
553 // TODO This shouldn't be needed here; it should be in the event loop code.
554 loop.beginOneEvent();
555 defer loop.finishOneEvent();
556 return callMain();
557}
558
559fn callWinMainAsync(loop: *std.event.Loop) callconv(.Async) std.os.windows.INT {
560 // This prevents the event loop from terminating at least until main() has returned.
561 // TODO This shouldn't be needed here; it should be in the event loop code.
562 loop.beginOneEvent();
563 defer loop.finishOneEvent();
564 return call_wWinMain();
565}
566
567// This is not marked inline because it is called with @asyncCall when
568// there is an event loop.
569pub fn callMain() u8 {
495pub inline fn callMain() u8 {
570496 switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
571497 .NoReturn => {
572498 root.main();
lib/std/std.zig+21-81
......@@ -92,9 +92,6 @@ pub const elf = @import("elf.zig");
9292/// Enum-related metaprogramming helpers.
9393pub const enums = @import("enums.zig");
9494
95/// Evented I/O data structures.
96pub const event = @import("event.zig");
97
9895/// First in, first out data structures.
9996pub const fifo = @import("fifo.zig");
10097
......@@ -198,79 +195,35 @@ pub const zig = @import("zig.zig");
198195pub const start = @import("start.zig");
199196
200197const root = @import("root");
201const options_override = if (@hasDecl(root, "std_options")) root.std_options else struct {};
202198
203199/// Stdlib-wide options that can be overridden by the root file.
204pub const options = struct {
205 pub const enable_segfault_handler: bool = if (@hasDecl(options_override, "enable_segfault_handler"))
206 options_override.enable_segfault_handler
207 else
208 debug.default_enable_segfault_handler;
200pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{};
201
202pub const Options = struct {
203 enable_segfault_handler: bool = debug.default_enable_segfault_handler,
209204
210205 /// Function used to implement `std.fs.cwd` for WASI.
211 pub const wasiCwd: fn () fs.Dir = if (@hasDecl(options_override, "wasiCwd"))
212 options_override.wasiCwd
213 else
214 fs.defaultWasiCwd;
215
216 /// The application's chosen I/O mode.
217 pub const io_mode: io.Mode = if (@hasDecl(options_override, "io_mode"))
218 options_override.io_mode
219 else if (@hasDecl(options_override, "event_loop"))
220 .evented
221 else
222 .blocking;
223
224 pub const event_loop: event.Loop.Instance = if (@hasDecl(options_override, "event_loop"))
225 options_override.event_loop
226 else
227 event.Loop.default_instance;
228
229 pub const event_loop_mode: event.Loop.Mode = if (@hasDecl(options_override, "event_loop_mode"))
230 options_override.event_loop_mode
231 else
232 event.Loop.default_mode;
206 wasiCwd: fn () os.wasi.fd_t = fs.defaultWasiCwd,
233207
234208 /// The current log level.
235 pub const log_level: log.Level = if (@hasDecl(options_override, "log_level"))
236 options_override.log_level
237 else
238 log.default_level;
209 log_level: log.Level = log.default_level,
239210
240 pub const log_scope_levels: []const log.ScopeLevel = if (@hasDecl(options_override, "log_scope_levels"))
241 options_override.log_scope_levels
242 else
243 &.{};
211 log_scope_levels: []const log.ScopeLevel = &.{},
244212
245 pub const logFn: fn (
213 logFn: fn (
246214 comptime message_level: log.Level,
247215 comptime scope: @TypeOf(.enum_literal),
248216 comptime format: []const u8,
249217 args: anytype,
250 ) void = if (@hasDecl(options_override, "logFn"))
251 options_override.logFn
252 else
253 log.defaultLog;
254
255 pub const fmt_max_depth = if (@hasDecl(options_override, "fmt_max_depth"))
256 options_override.fmt_max_depth
257 else
258 fmt.default_max_depth;
259
260 pub const cryptoRandomSeed: fn (buffer: []u8) void = if (@hasDecl(options_override, "cryptoRandomSeed"))
261 options_override.cryptoRandomSeed
262 else
263 @import("crypto/tlcsprng.zig").defaultRandomSeed;
264
265 pub const crypto_always_getrandom: bool = if (@hasDecl(options_override, "crypto_always_getrandom"))
266 options_override.crypto_always_getrandom
267 else
268 false;
269
270 pub const crypto_fork_safety: bool = if (@hasDecl(options_override, "crypto_fork_safety"))
271 options_override.crypto_fork_safety
272 else
273 true;
218 ) void = log.defaultLog,
219
220 fmt_max_depth: usize = fmt.default_max_depth,
221
222 cryptoRandomSeed: fn (buffer: []u8) void = @import("crypto/tlcsprng.zig").defaultRandomSeed,
223
224 crypto_always_getrandom: bool = false,
225
226 crypto_fork_safety: bool = true,
274227
275228 /// By default Zig disables SIGPIPE by setting a "no-op" handler for it. Set this option
276229 /// to `true` to prevent that.
......@@ -283,35 +236,22 @@ pub const options = struct {
283236 /// cases it's unclear why the process was terminated. By capturing SIGPIPE instead, functions that
284237 /// write to broken pipes will return the EPIPE error (error.BrokenPipe) and the program can handle
285238 /// it like any other error.
286 pub const keep_sigpipe: bool = if (@hasDecl(options_override, "keep_sigpipe"))
287 options_override.keep_sigpipe
288 else
289 false;
239 keep_sigpipe: bool = false,
290240
291241 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
292242 /// disable TLS support.
293243 ///
294244 /// This will likely reduce the size of the binary, but it will also make it impossible to
295245 /// make a HTTPS connection.
296 pub const http_disable_tls = if (@hasDecl(options_override, "http_disable_tls"))
297 options_override.http_disable_tls
298 else
299 false;
300
301 pub const side_channels_mitigations: crypto.SideChannelsMitigations = if (@hasDecl(options_override, "side_channels_mitigations"))
302 options_override.side_channels_mitigations
303 else
304 crypto.default_side_channels_mitigations;
246 http_disable_tls: bool = false,
247
248 side_channels_mitigations: crypto.SideChannelsMitigations = crypto.default_side_channels_mitigations,
305249};
306250
307251// This forces the start.zig file to be imported, and the comptime logic inside that
308252// file decides whether to export any appropriate start symbols, and call main.
309253comptime {
310254 _ = start;
311
312 for (@typeInfo(options_override).Struct.decls) |decl| {
313 if (!@hasDecl(options, decl.name)) @compileError("no option named " ++ decl.name);
314 }
315255}
316256
317257test {
lib/std/time.zig-5
......@@ -9,11 +9,6 @@ pub const epoch = @import("time/epoch.zig");
99
1010/// Spurious wakeups are possible and no precision of timing is guaranteed.
1111pub fn sleep(nanoseconds: u64) void {
12 // TODO: opting out of async sleeping?
13 if (std.io.is_async) {
14 return std.event.Loop.instance.?.sleep(nanoseconds);
15 }
16
1712 if (builtin.os.tag == .windows) {
1813 const big_ms_from_ns = nanoseconds / ns_per_ms;
1914 const ms = math.cast(os.windows.DWORD, big_ms_from_ns) orelse math.maxInt(os.windows.DWORD);
lib/std/zig/Server.zig+2-7
......@@ -38,8 +38,6 @@ pub const Message = struct {
3838 /// Trailing:
3939 /// * name: [tests_len]u32
4040 /// - null-terminated string_bytes index
41 /// * async_frame_len: [tests_len]u32,
42 /// - 0 means not async
4341 /// * expected_panic_msg: [tests_len]u32,
4442 /// - null-terminated string_bytes index
4543 /// - 0 means does not expect pani
......@@ -210,7 +208,6 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
210208
211209pub const TestMetadata = struct {
212210 names: []u32,
213 async_frame_sizes: []u32,
214211 expected_panic_msgs: []u32,
215212 string_bytes: []const u8,
216213};
......@@ -220,17 +217,16 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
220217 .tests_len = bswap(@as(u32, @intCast(test_metadata.names.len))),
221218 .string_bytes_len = bswap(@as(u32, @intCast(test_metadata.string_bytes.len))),
222219 };
220 const trailing = 2;
223221 const bytes_len = @sizeOf(OutMessage.TestMetadata) +
224 3 * 4 * test_metadata.names.len + test_metadata.string_bytes.len;
222 trailing * @sizeOf(u32) * test_metadata.names.len + test_metadata.string_bytes.len;
225223
226224 if (need_bswap) {
227225 bswap_u32_array(test_metadata.names);
228 bswap_u32_array(test_metadata.async_frame_sizes);
229226 bswap_u32_array(test_metadata.expected_panic_msgs);
230227 }
231228 defer if (need_bswap) {
232229 bswap_u32_array(test_metadata.names);
233 bswap_u32_array(test_metadata.async_frame_sizes);
234230 bswap_u32_array(test_metadata.expected_panic_msgs);
235231 };
236232
......@@ -241,7 +237,6 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
241237 std.mem.asBytes(&header),
242238 // TODO: implement @ptrCast between slices changing the length
243239 std.mem.sliceAsBytes(test_metadata.names),
244 std.mem.sliceAsBytes(test_metadata.async_frame_sizes),
245240 std.mem.sliceAsBytes(test_metadata.expected_panic_msgs),
246241 test_metadata.string_bytes,
247242 });
lib/std/zig/system/linux.zig+1-1
......@@ -328,7 +328,7 @@ fn CpuinfoParser(comptime impl: anytype) type {
328328}
329329
330330pub fn detectNativeCpuAndFeatures() ?Target.Cpu {
331 var f = fs.openFileAbsolute("/proc/cpuinfo", .{ .intended_io_mode = .blocking }) catch |err| switch (err) {
331 var f = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) {
332332 else => return null,
333333 };
334334 defer f.close();
lib/test_runner.zig+4-28
......@@ -3,9 +3,8 @@ const std = @import("std");
33const io = std.io;
44const builtin = @import("builtin");
55
6pub const std_options = struct {
7 pub const io_mode: io.Mode = builtin.test_io_mode;
8 pub const logFn = log;
6pub const std_options = .{
7 .logFn = log,
98};
109
1110var log_err_count: usize = 0;
......@@ -65,24 +64,19 @@ fn mainServer() !void {
6564 const test_fns = builtin.test_functions;
6665 const names = try std.testing.allocator.alloc(u32, test_fns.len);
6766 defer std.testing.allocator.free(names);
68 const async_frame_sizes = try std.testing.allocator.alloc(u32, test_fns.len);
69 defer std.testing.allocator.free(async_frame_sizes);
7067 const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);
7168 defer std.testing.allocator.free(expected_panic_msgs);
7269
73 for (test_fns, names, async_frame_sizes, expected_panic_msgs) |test_fn, *name, *async_frame_size, *expected_panic_msg| {
70 for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| {
7471 name.* = @as(u32, @intCast(string_bytes.items.len));
7572 try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);
7673 string_bytes.appendSliceAssumeCapacity(test_fn.name);
7774 string_bytes.appendAssumeCapacity(0);
78
79 async_frame_size.* = @as(u32, @intCast(test_fn.async_frame_size orelse 0));
8075 expected_panic_msg.* = 0;
8176 }
8277
8378 try server.serveTestMetadata(.{
8479 .names = names,
85 .async_frame_sizes = async_frame_sizes,
8680 .expected_panic_msgs = expected_panic_msgs,
8781 .string_bytes = string_bytes.items,
8882 });
......@@ -93,8 +87,6 @@ fn mainServer() !void {
9387 log_err_count = 0;
9488 const index = try server.receiveBody_u32();
9589 const test_fn = builtin.test_functions[index];
96 if (test_fn.async_frame_size != null)
97 @panic("TODO test runner implement async tests");
9890 var fail = false;
9991 var skip = false;
10092 var leak = false;
......@@ -163,23 +155,7 @@ fn mainTerminal() void {
163155 if (!have_tty) {
164156 std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name });
165157 }
166 const result = if (test_fn.async_frame_size) |size| switch (std.options.io_mode) {
167 .evented => blk: {
168 if (async_frame_buffer.len < size) {
169 std.heap.page_allocator.free(async_frame_buffer);
170 async_frame_buffer = std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size) catch @panic("out of memory");
171 }
172 const casted_fn = @as(fn () callconv(.Async) anyerror!void, @ptrCast(test_fn.func));
173 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn, .{});
174 },
175 .blocking => {
176 skip_count += 1;
177 test_node.end();
178 progress.log("SKIP (async test)\n", .{});
179 continue;
180 },
181 } else test_fn.func();
182 if (result) |_| {
158 if (test_fn.func()) |_| {
183159 ok_count += 1;
184160 test_node.end();
185161 if (!have_tty) std.debug.print("OK\n", .{});
src/Builtin.zig-12
......@@ -3,7 +3,6 @@ zig_backend: std.builtin.CompilerBackend,
33output_mode: std.builtin.OutputMode,
44link_mode: std.builtin.LinkMode,
55is_test: bool,
6test_evented_io: bool,
76single_threaded: bool,
87link_libc: bool,
98link_libcpp: bool,
......@@ -222,17 +221,6 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void {
222221 \\pub var test_functions: []const std.builtin.TestFn = undefined; // overwritten later
223222 \\
224223 );
225 if (opts.test_evented_io) {
226 try buffer.appendSlice(
227 \\pub const test_io_mode = .evented;
228 \\
229 );
230 } else {
231 try buffer.appendSlice(
232 \\pub const test_io_mode = .blocking;
233 \\
234 );
235 }
236224 }
237225}
238226
src/Compilation.zig-2
......@@ -1613,7 +1613,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
16131613 hash.add(options.config.use_lib_llvm);
16141614 hash.add(options.config.dll_export_fns);
16151615 hash.add(options.config.is_test);
1616 hash.add(options.config.test_evented_io);
16171616 hash.addOptionalBytes(options.test_filter);
16181617 hash.addOptionalBytes(options.test_name_prefix);
16191618 hash.add(options.skip_linker_dependencies);
......@@ -2476,7 +2475,6 @@ fn addNonIncrementalStuffToCacheManifest(
24762475 try addModuleTableToCacheHash(gpa, arena, &man.hash, mod.root_mod, mod.main_mod, .{ .files = man });
24772476
24782477 // Synchronize with other matching comments: ZigOnlyHashStuff
2479 man.hash.add(comp.config.test_evented_io);
24802478 man.hash.addOptionalBytes(comp.test_filter);
24812479 man.hash.addOptionalBytes(comp.test_name_prefix);
24822480 man.hash.add(comp.skip_linker_dependencies);
src/Compilation/Config.zig-3
......@@ -54,7 +54,6 @@ import_memory: bool,
5454export_memory: bool,
5555shared_memory: bool,
5656is_test: bool,
57test_evented_io: bool,
5857debug_format: DebugFormat,
5958root_strip: bool,
6059root_error_tracing: bool,
......@@ -104,7 +103,6 @@ pub const Options = struct {
104103 import_memory: ?bool = null,
105104 export_memory: ?bool = null,
106105 shared_memory: ?bool = null,
107 test_evented_io: bool = false,
108106 debug_format: ?DebugFormat = null,
109107 dll_export_fns: ?bool = null,
110108 rdynamic: ?bool = null,
......@@ -477,7 +475,6 @@ pub fn resolve(options: Options) ResolveError!Config {
477475 .output_mode = options.output_mode,
478476 .have_zcu = options.have_zcu,
479477 .is_test = options.is_test,
480 .test_evented_io = options.test_evented_io,
481478 .link_mode = link_mode,
482479 .link_libc = link_libc,
483480 .link_libcpp = link_libcpp,
src/Module.zig-13
......@@ -5620,10 +5620,6 @@ pub fn populateTestFunctions(
56205620 }
56215621 const decl = mod.declPtr(decl_index);
56225622 const test_fn_ty = decl.ty.slicePtrFieldType(mod).childType(mod);
5623 const null_usize = try mod.intern(.{ .opt = .{
5624 .ty = try mod.intern(.{ .opt_type = .usize_type }),
5625 .val = .none,
5626 } });
56275623
56285624 const array_decl_index = d: {
56295625 // Add mod.test_functions to an array decl then make the test_functions
......@@ -5631,11 +5627,6 @@ pub fn populateTestFunctions(
56315627 const test_fn_vals = try gpa.alloc(InternPool.Index, mod.test_functions.count());
56325628 defer gpa.free(test_fn_vals);
56335629
5634 // Add a dependency on each test name and function pointer.
5635 var array_decl_dependencies = std.ArrayListUnmanaged(Decl.Index){};
5636 defer array_decl_dependencies.deinit(gpa);
5637 try array_decl_dependencies.ensureUnusedCapacity(gpa, test_fn_vals.len * 2);
5638
56395630 for (test_fn_vals, mod.test_functions.keys()) |*test_fn_val, test_decl_index| {
56405631 const test_decl = mod.declPtr(test_decl_index);
56415632 // TODO: write something like getCoercedInts to avoid needing to dupe
......@@ -5655,8 +5646,6 @@ pub fn populateTestFunctions(
56555646 });
56565647 break :n test_name_decl_index;
56575648 };
5658 array_decl_dependencies.appendAssumeCapacity(test_decl_index);
5659 array_decl_dependencies.appendAssumeCapacity(test_name_decl_index);
56605649 try mod.linkerUpdateDecl(test_name_decl_index);
56615650
56625651 const test_fn_fields = .{
......@@ -5682,8 +5671,6 @@ pub fn populateTestFunctions(
56825671 } }),
56835672 .addr = .{ .decl = test_decl_index },
56845673 } }),
5685 // async_frame_size
5686 null_usize,
56875674 };
56885675 test_fn_val.* = try mod.intern(.{ .aggregate = .{
56895676 .ty = test_fn_ty.toIntern(),
src/Package/Module.zig-1
......@@ -349,7 +349,6 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
349349 .output_mode = options.global.output_mode,
350350 .link_mode = options.global.link_mode,
351351 .is_test = options.global.is_test,
352 .test_evented_io = options.global.test_evented_io,
353352 .single_threaded = single_threaded,
354353 .link_libc = options.global.link_libc,
355354 .link_libcpp = options.global.link_libcpp,
src/main.zig+8-10
......@@ -29,27 +29,27 @@ const AstGen = @import("AstGen.zig");
2929const mingw = @import("mingw.zig");
3030const Server = std.zig.Server;
3131
32pub const std_options = struct {
33 pub const wasiCwd = wasi_cwd;
34 pub const logFn = log;
35 pub const enable_segfault_handler = false;
32pub const std_options = .{
33 .wasiCwd = wasi_cwd,
34 .logFn = log,
35 .enable_segfault_handler = false,
3636
37 pub const log_level: std.log.Level = switch (builtin.mode) {
37 .log_level = switch (builtin.mode) {
3838 .Debug => .debug,
3939 .ReleaseSafe, .ReleaseFast => .info,
4040 .ReleaseSmall => .err,
41 };
41 },
4242};
4343
4444// Crash report needs to override the panic handler
4545pub const panic = crash_report.panic;
4646
4747var wasi_preopens: fs.wasi.Preopens = undefined;
48pub fn wasi_cwd() fs.Dir {
48pub fn wasi_cwd() std.os.wasi.fd_t {
4949 // Expect the first preopen to be current working directory.
5050 const cwd_fd: std.os.fd_t = 3;
5151 assert(mem.eql(u8, wasi_preopens.names[cwd_fd], "."));
52 return .{ .fd = cwd_fd };
52 return cwd_fd;
5353}
5454
5555fn getWasiPreopen(name: []const u8) Compilation.Directory {
......@@ -1335,8 +1335,6 @@ fn buildOutputType(
13351335 create_module.each_lib_rpath = false;
13361336 } else if (mem.eql(u8, arg, "--test-cmd-bin")) {
13371337 try test_exec_args.append(null);
1338 } else if (mem.eql(u8, arg, "--test-evented-io")) {
1339 create_module.opts.test_evented_io = true;
13401338 } else if (mem.eql(u8, arg, "--test-no-exec")) {
13411339 test_no_exec = true;
13421340 } else if (mem.eql(u8, arg, "-ftime-report")) {
test/cases/compile_errors/missing_main_fn_in_executable.zig-1
......@@ -7,4 +7,3 @@
77// : note: struct declared here
88// : note: called from here
99// : note: called from here
10// : note: called from here
test/cases/compile_errors/private_main_fn.zig-1
......@@ -9,4 +9,3 @@ fn main() void {}
99// :1:1: note: declared here
1010// : note: called from here
1111// : note: called from here
12// : note: called from here
test/compare_output.zig+8-8
......@@ -440,14 +440,14 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
440440 cases.add("std.log per scope log level override",
441441 \\const std = @import("std");
442442 \\
443 \\pub const std_options = struct {
444 \\ pub const log_level: std.log.Level = .debug;
443 \\pub const std_options = .{
444 \\ .log_level = .debug,
445445 \\
446 \\ pub const log_scope_levels = &[_]std.log.ScopeLevel{
446 \\ .log_scope_levels = &.{
447447 \\ .{ .scope = .a, .level = .warn },
448448 \\ .{ .scope = .c, .level = .err },
449 \\ };
450 \\ pub const logFn = log;
449 \\ },
450 \\ .logFn = log,
451451 \\};
452452 \\
453453 \\const loga = std.log.scoped(.a);
......@@ -497,9 +497,9 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
497497 cases.add("std.heap.LoggingAllocator logs to std.log",
498498 \\const std = @import("std");
499499 \\
500 \\pub const std_options = struct {
501 \\ pub const log_level: std.log.Level = .debug;
502 \\ pub const logFn = log;
500 \\pub const std_options = .{
501 \\ .log_level = .debug,
502 \\ .logFn = log,
503503 \\};
504504 \\
505505 \\pub fn main() !void {
test/src/Cases.zig+2-2
......@@ -1207,8 +1207,8 @@ const WaitGroup = std.Thread.WaitGroup;
12071207const build_options = @import("build_options");
12081208const Package = @import("../../src/Package.zig");
12091209
1210pub const std_options = struct {
1211 pub const log_level: std.log.Level = .err;
1210pub const std_options = .{
1211 .log_level = .err,
12121212};
12131213
12141214var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{
test/standalone/http.zig+2-2
......@@ -7,8 +7,8 @@ const Client = http.Client;
77const mem = std.mem;
88const testing = std.testing;
99
10pub const std_options = struct {
11 pub const http_disable_tls = true;
10pub const std_options = .{
11 .http_disable_tls = true,
1212};
1313
1414const max_header_size = 8192;
test/standalone/issue_7030.zig+2-2
......@@ -1,7 +1,7 @@
11const std = @import("std");
22
3pub const std_options = struct {
4 pub const logFn = log;
3pub const std_options = .{
4 .logFn = log,
55};
66
77pub fn log(
test/standalone/issue_9693/main.zig deleted-4
......@@ -1,4 +0,0 @@
1pub const std_options = struct {
2 pub const io_mode = .evented;
3};
4pub fn main() void {}
test/standalone/sigpipe/breakpipe.zig+5-5
......@@ -1,11 +1,11 @@
11const std = @import("std");
22const build_options = @import("build_options");
33
4pub const std_options = if (build_options.keep_sigpipe) struct {
5 pub const keep_sigpipe = true;
6} else struct {
7 // intentionally not setting keep_sigpipe to ensure the default behavior is equivalent to false
8};
4pub usingnamespace if (build_options.keep_sigpipe) struct {
5 pub const std_options = .{
6 .keep_sigpipe = true,
7 };
8} else struct {};
99
1010pub fn main() !void {
1111 const pipe = try std.os.pipe();