authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-07-25 23:16:13-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-07-30 13:44:36-04:00
logcc4552733351390aecab9ae900beb822237d6041
tree575c7ad933f2ea04c4125704a55dc823839af57e
parent5d4a02c350a18a70cf1f92f6638b5d26689c16b4

introduce std.event.fs for async file system functions

only works on linux so far

10 files changed, 559 insertions(+), 97 deletions(-)

CMakeLists.txt+1
......@@ -460,6 +460,7 @@ set(ZIG_STD_FILES
460460 "empty.zig"
461461 "event.zig"
462462 "event/channel.zig"
463 "event/fs.zig"
463464 "event/future.zig"
464465 "event/group.zig"
465466 "event/lock.zig"
std/build.zig+2-2
......@@ -603,10 +603,10 @@ pub const Builder = struct {
603603 }
604604
605605 fn copyFile(self: *Builder, source_path: []const u8, dest_path: []const u8) !void {
606 return self.copyFileMode(source_path, dest_path, os.default_file_mode);
606 return self.copyFileMode(source_path, dest_path, os.File.default_mode);
607607 }
608608
609 fn copyFileMode(self: *Builder, source_path: []const u8, dest_path: []const u8, mode: os.FileMode) !void {
609 fn copyFileMode(self: *Builder, source_path: []const u8, dest_path: []const u8, mode: os.File.Mode) !void {
610610 if (self.verbose) {
611611 warn("cp {} {}\n", source_path, dest_path);
612612 }
std/debug/index.zig+1-5
......@@ -672,14 +672,10 @@ fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, comptime T: type
672672
673673const ParseFormValueError = error{
674674 EndOfStream,
675 Io,
676 BadFd,
677 Unexpected,
678675 InvalidDebugInfo,
679676 EndOfFile,
680 IsDir,
681677 OutOfMemory,
682};
678} || std.os.File.ReadError;
683679
684680fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) ParseFormValueError!FormValue {
685681 return switch (form_id) {
std/event.zig+10-8
......@@ -1,17 +1,19 @@
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 Lock = @import("event/lock.zig").Lock;
15pub const Locked = @import("event/locked.zig").Locked;
26pub const Loop = @import("event/loop.zig").Loop;
3pub const Lock = @import("event/lock.zig").Lock;
7pub const fs = @import("event/fs.zig");
48pub const tcp = @import("event/tcp.zig");
5pub const Channel = @import("event/channel.zig").Channel;
6pub const Group = @import("event/group.zig").Group;
7pub const Future = @import("event/future.zig").Future;
89
910test "import event tests" {
11 _ = @import("event/channel.zig");
12 _ = @import("event/fs.zig");
13 _ = @import("event/future.zig");
14 _ = @import("event/group.zig");
15 _ = @import("event/lock.zig");
1016 _ = @import("event/locked.zig");
1117 _ = @import("event/loop.zig");
12 _ = @import("event/lock.zig");
1318 _ = @import("event/tcp.zig");
14 _ = @import("event/channel.zig");
15 _ = @import("event/group.zig");
16 _ = @import("event/future.zig");
1719}
std/event/fs.zig created+343
......@@ -0,0 +1,343 @@
1const std = @import("../index.zig");
2const event = std.event;
3const assert = std.debug.assert;
4const os = std.os;
5const mem = std.mem;
6
7pub const RequestNode = std.atomic.Queue(Request).Node;
8
9pub const Request = struct {
10 msg: Msg,
11 finish: Finish,
12
13 pub const Finish = union(enum) {
14 TickNode: event.Loop.NextTickNode,
15 DeallocCloseOperation: *CloseOperation,
16 NoAction,
17 };
18
19 pub const Msg = union(enum) {
20 PWriteV: PWriteV,
21 PReadV: PReadV,
22 OpenRead: OpenRead,
23 Close: Close,
24 WriteFile: WriteFile,
25 End, // special - means the fs thread should exit
26
27 pub const PWriteV = struct {
28 fd: os.FileHandle,
29 data: []const []const u8,
30 offset: usize,
31 result: Error!void,
32
33 pub const Error = error{};
34 };
35
36 pub const PReadV = struct {
37 fd: os.FileHandle,
38 iov: []os.linux.iovec,
39 offset: usize,
40 result: Error!usize,
41
42 pub const Error = os.File.ReadError;
43 };
44
45 pub const OpenRead = struct {
46 /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265
47 path: []const u8,
48 result: Error!os.FileHandle,
49
50 pub const Error = os.File.OpenError;
51 };
52
53 pub const WriteFile = struct {
54 /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265
55 path: []const u8,
56 contents: []const u8,
57 mode: os.File.Mode,
58 result: Error!void,
59
60 pub const Error = os.File.OpenError || os.File.WriteError;
61 };
62
63 pub const Close = struct {
64 fd: os.FileHandle,
65 };
66 };
67};
68
69/// data - both the outer and inner references - must live until pwritev promise completes.
70pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []const u8) !void {
71 //const data_dupe = try mem.dupe(loop.allocator, []const u8, data);
72 //defer loop.allocator.free(data_dupe);
73
74 // workaround for https://github.com/ziglang/zig/issues/1194
75 var my_handle: promise = undefined;
76 suspend |p| {
77 my_handle = p;
78 resume p;
79 }
80
81 var req_node = RequestNode{
82 .next = undefined,
83 .data = Request{
84 .msg = Request.Msg{
85 .PWriteV = Request.Msg.PWriteV{
86 .fd = fd,
87 .data = data,
88 .offset = offset,
89 .result = undefined,
90 },
91 },
92 .finish = Request.Finish{
93 .TickNode = event.Loop.NextTickNode{
94 .next = undefined,
95 .data = my_handle,
96 },
97 },
98 },
99 };
100
101 suspend |_| {
102 loop.linuxFsRequest(&req_node);
103 }
104
105 return req_node.data.msg.PWriteV.result;
106}
107
108/// data - just the inner references - must live until pwritev promise completes.
109pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []u8) !usize {
110 //const data_dupe = try mem.dupe(loop.allocator, []const u8, data);
111 //defer loop.allocator.free(data_dupe);
112
113 // workaround for https://github.com/ziglang/zig/issues/1194
114 var my_handle: promise = undefined;
115 suspend |p| {
116 my_handle = p;
117 resume p;
118 }
119
120 const iovecs = try loop.allocator.alloc(os.linux.iovec, data.len);
121 defer loop.allocator.free(iovecs);
122
123 for (data) |buf, i| {
124 iovecs[i] = os.linux.iovec{
125 .iov_base = buf.ptr,
126 .iov_len = buf.len,
127 };
128 }
129
130 var req_node = RequestNode{
131 .next = undefined,
132 .data = Request{
133 .msg = Request.Msg{
134 .PReadV = Request.Msg.PReadV{
135 .fd = fd,
136 .iov = iovecs,
137 .offset = offset,
138 .result = undefined,
139 },
140 },
141 .finish = Request.Finish{
142 .TickNode = event.Loop.NextTickNode{
143 .next = undefined,
144 .data = my_handle,
145 },
146 },
147 },
148 };
149
150 suspend |_| {
151 loop.linuxFsRequest(&req_node);
152 }
153
154 return req_node.data.msg.PReadV.result;
155}
156
157pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.FileHandle {
158 // workaround for https://github.com/ziglang/zig/issues/1194
159 var my_handle: promise = undefined;
160 suspend |p| {
161 my_handle = p;
162 resume p;
163 }
164
165 var req_node = RequestNode{
166 .next = undefined,
167 .data = Request{
168 .msg = Request.Msg{
169 .OpenRead = Request.Msg.OpenRead{
170 .path = path,
171 .result = undefined,
172 },
173 },
174 .finish = Request.Finish{
175 .TickNode = event.Loop.NextTickNode{
176 .next = undefined,
177 .data = my_handle,
178 },
179 },
180 },
181 };
182
183 suspend |_| {
184 loop.linuxFsRequest(&req_node);
185 }
186
187 return req_node.data.msg.OpenRead.result;
188}
189
190/// This abstraction helps to close file handles in defer expressions
191/// without suspending. Start a CloseOperation before opening a file.
192pub const CloseOperation = struct {
193 loop: *event.Loop,
194 have_fd: bool,
195 close_req_node: RequestNode,
196
197 pub fn create(loop: *event.Loop) (error{OutOfMemory}!*CloseOperation) {
198 const self = try loop.allocator.createOne(CloseOperation);
199 self.* = CloseOperation{
200 .loop = loop,
201 .have_fd = false,
202 .close_req_node = RequestNode{
203 .next = undefined,
204 .data = Request{
205 .msg = Request.Msg{
206 .Close = Request.Msg.Close{ .fd = undefined },
207 },
208 .finish = Request.Finish{ .DeallocCloseOperation = self },
209 },
210 },
211 };
212 return self;
213 }
214
215 /// Defer this after creating.
216 pub fn deinit(self: *CloseOperation) void {
217 if (self.have_fd) {
218 self.loop.linuxFsRequest(&self.close_req_node);
219 } else {
220 self.loop.allocator.destroy(self);
221 }
222 }
223
224 pub fn setHandle(self: *CloseOperation, handle: os.FileHandle) void {
225 self.close_req_node.data.msg.Close.fd = handle;
226 self.have_fd = true;
227 }
228};
229
230/// contents must remain alive until writeFile completes.
231pub async fn writeFile(loop: *event.Loop, path: []const u8, contents: []const u8) !void {
232 return await (async writeFileMode(loop, path, contents, os.File.default_mode) catch unreachable);
233}
234
235/// contents must remain alive until writeFile completes.
236pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []const u8, mode: os.File.Mode) !void {
237 // workaround for https://github.com/ziglang/zig/issues/1194
238 var my_handle: promise = undefined;
239 suspend |p| {
240 my_handle = p;
241 resume p;
242 }
243
244 const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
245 defer loop.allocator.free(path_with_null);
246
247 var req_node = RequestNode{
248 .next = undefined,
249 .data = Request{
250 .msg = Request.Msg{
251 .WriteFile = Request.Msg.WriteFile{
252 .path = path_with_null[0..path.len],
253 .contents = contents,
254 .mode = mode,
255 .result = undefined,
256 },
257 },
258 .finish = Request.Finish{
259 .TickNode = event.Loop.NextTickNode{
260 .next = undefined,
261 .data = my_handle,
262 },
263 },
264 },
265 };
266
267 suspend |_| {
268 loop.linuxFsRequest(&req_node);
269 }
270
271 return req_node.data.msg.WriteFile.result;
272}
273
274/// The promise resumes when the last data has been confirmed written, but before the file handle
275/// is closed.
276pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) ![]u8 {
277 var close_op = try CloseOperation.create(loop);
278 defer close_op.deinit();
279
280 const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path);
281 defer loop.allocator.free(path_with_null);
282
283 const fd = try await (async openRead(loop, path_with_null[0..file_path.len]) catch unreachable);
284 close_op.setHandle(fd);
285
286 var list = std.ArrayList(u8).init(loop.allocator);
287 defer list.deinit();
288
289 while (true) {
290 try list.ensureCapacity(list.len + os.page_size);
291 const buf = list.items[list.len..];
292 const buf_array = [][]u8{buf};
293 const amt = try await (async preadv(loop, fd, list.len, buf_array) catch unreachable);
294 list.len += amt;
295 if (amt < buf.len) {
296 return list.toOwnedSlice();
297 }
298 }
299}
300
301const test_tmp_dir = "std_event_fs_test";
302
303test "write a file, watch it, write it again" {
304 var da = std.heap.DirectAllocator.init();
305 defer da.deinit();
306
307 const allocator = &da.allocator;
308
309 // TODO move this into event loop too
310 try os.makePath(allocator, test_tmp_dir);
311 defer os.deleteTree(allocator, test_tmp_dir) catch {};
312
313 var loop: event.Loop = undefined;
314 try loop.initMultiThreaded(allocator);
315 defer loop.deinit();
316
317 var result: error!void = undefined;
318 const handle = try async<allocator> testFsWatchCantFail(&loop, &result);
319 defer cancel handle;
320
321 loop.run();
322 return result;
323}
324
325async fn testFsWatchCantFail(loop: *event.Loop, result: *(error!void)) void {
326 result.* = await async testFsWatch(loop) catch unreachable;
327}
328
329async fn testFsWatch(loop: *event.Loop) !void {
330 const file_path = try os.path.join(loop.allocator, test_tmp_dir, "file.txt");
331 defer loop.allocator.free(file_path);
332
333 const contents =
334 \\line 1
335 \\line 2
336 ;
337
338 // first just write then read the file
339 try await try async writeFile(loop, file_path, contents);
340
341 const read_contents = try await try async readFile(loop, file_path, 1024 * 1024);
342 assert(mem.eql(u8, read_contents, contents));
343}
std/event/loop.zig+136-51
......@@ -2,10 +2,12 @@ const std = @import("../index.zig");
22const builtin = @import("builtin");
33const assert = std.debug.assert;
44const mem = std.mem;
5const posix = std.os.posix;
6const windows = std.os.windows;
75const AtomicRmwOp = builtin.AtomicRmwOp;
86const AtomicOrder = builtin.AtomicOrder;
7const fs = std.event.fs;
8const os = std.os;
9const posix = os.posix;
10const windows = os.windows;
911
1012pub const Loop = struct {
1113 allocator: *mem.Allocator,
......@@ -13,7 +15,7 @@ pub const Loop = struct {
1315 os_data: OsData,
1416 final_resume_node: ResumeNode,
1517 pending_event_count: usize,
16 extra_threads: []*std.os.Thread,
18 extra_threads: []*os.Thread,
1719
1820 // pre-allocated eventfds. all permanently active.
1921 // this is how we send promises to be resumed on other threads.
......@@ -65,7 +67,7 @@ pub const Loop = struct {
6567 /// TODO copy elision / named return values so that the threads referencing *Loop
6668 /// have the correct pointer value.
6769 pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void {
68 const core_count = try std.os.cpuCount(allocator);
70 const core_count = try os.cpuCount(allocator);
6971 return self.initInternal(allocator, core_count);
7072 }
7173
......@@ -92,7 +94,7 @@ pub const Loop = struct {
9294 );
9395 errdefer self.allocator.free(self.eventfd_resume_nodes);
9496
95 self.extra_threads = try self.allocator.alloc(*std.os.Thread, extra_thread_count);
97 self.extra_threads = try self.allocator.alloc(*os.Thread, extra_thread_count);
9698 errdefer self.allocator.free(self.extra_threads);
9799
98100 try self.initOsData(extra_thread_count);
......@@ -104,17 +106,34 @@ pub const Loop = struct {
104106 self.allocator.free(self.extra_threads);
105107 }
106108
107 const InitOsDataError = std.os.LinuxEpollCreateError || mem.Allocator.Error || std.os.LinuxEventFdError ||
108 std.os.SpawnThreadError || std.os.LinuxEpollCtlError || std.os.BsdKEventError ||
109 std.os.WindowsCreateIoCompletionPortError;
109 const InitOsDataError = os.LinuxEpollCreateError || mem.Allocator.Error || os.LinuxEventFdError ||
110 os.SpawnThreadError || os.LinuxEpollCtlError || os.BsdKEventError ||
111 os.WindowsCreateIoCompletionPortError;
110112
111113 const wakeup_bytes = []u8{0x1} ** 8;
112114
113115 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
114116 switch (builtin.os) {
115117 builtin.Os.linux => {
118 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
119 self.os_data.fs_queue_len = 0;
120 // we need another thread for the file system because Linux does not have an async
121 // file system I/O API.
122 self.os_data.fs_end_request = fs.RequestNode{
123 .next = undefined,
124 .data = fs.Request{
125 .msg = fs.Request.Msg.End,
126 .finish = fs.Request.Finish.NoAction,
127 },
128 };
129 self.os_data.fs_thread = try os.spawnThread(self, linuxFsRun);
116130 errdefer {
117 while (self.available_eventfd_resume_nodes.pop()) |node| std.os.close(node.data.eventfd);
131 self.linuxFsRequest(&self.os_data.fs_end_request);
132 self.os_data.fs_thread.wait();
133 }
134
135 errdefer {
136 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
118137 }
119138 for (self.eventfd_resume_nodes) |*eventfd_node| {
120139 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
......@@ -123,7 +142,7 @@ pub const Loop = struct {
123142 .id = ResumeNode.Id.EventFd,
124143 .handle = undefined,
125144 },
126 .eventfd = try std.os.linuxEventFd(1, posix.EFD_CLOEXEC | posix.EFD_NONBLOCK),
145 .eventfd = try os.linuxEventFd(1, posix.EFD_CLOEXEC | posix.EFD_NONBLOCK),
127146 .epoll_op = posix.EPOLL_CTL_ADD,
128147 },
129148 .next = undefined,
......@@ -131,17 +150,17 @@ pub const Loop = struct {
131150 self.available_eventfd_resume_nodes.push(eventfd_node);
132151 }
133152
134 self.os_data.epollfd = try std.os.linuxEpollCreate(posix.EPOLL_CLOEXEC);
135 errdefer std.os.close(self.os_data.epollfd);
153 self.os_data.epollfd = try os.linuxEpollCreate(posix.EPOLL_CLOEXEC);
154 errdefer os.close(self.os_data.epollfd);
136155
137 self.os_data.final_eventfd = try std.os.linuxEventFd(0, posix.EFD_CLOEXEC | posix.EFD_NONBLOCK);
138 errdefer std.os.close(self.os_data.final_eventfd);
156 self.os_data.final_eventfd = try os.linuxEventFd(0, posix.EFD_CLOEXEC | posix.EFD_NONBLOCK);
157 errdefer os.close(self.os_data.final_eventfd);
139158
140159 self.os_data.final_eventfd_event = posix.epoll_event{
141160 .events = posix.EPOLLIN,
142161 .data = posix.epoll_data{ .ptr = @ptrToInt(&self.final_resume_node) },
143162 };
144 try std.os.linuxEpollCtl(
163 try os.linuxEpollCtl(
145164 self.os_data.epollfd,
146165 posix.EPOLL_CTL_ADD,
147166 self.os_data.final_eventfd,
......@@ -151,19 +170,19 @@ pub const Loop = struct {
151170 var extra_thread_index: usize = 0;
152171 errdefer {
153172 // writing 8 bytes to an eventfd cannot fail
154 std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
173 os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
155174 while (extra_thread_index != 0) {
156175 extra_thread_index -= 1;
157176 self.extra_threads[extra_thread_index].wait();
158177 }
159178 }
160179 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
161 self.extra_threads[extra_thread_index] = try std.os.spawnThread(self, workerRun);
180 self.extra_threads[extra_thread_index] = try os.spawnThread(self, workerRun);
162181 }
163182 },
164183 builtin.Os.macosx => {
165 self.os_data.kqfd = try std.os.bsdKQueue();
166 errdefer std.os.close(self.os_data.kqfd);
184 self.os_data.kqfd = try os.bsdKQueue();
185 errdefer os.close(self.os_data.kqfd);
167186
168187 self.os_data.kevents = try self.allocator.alloc(posix.Kevent, extra_thread_count);
169188 errdefer self.allocator.free(self.os_data.kevents);
......@@ -191,7 +210,7 @@ pub const Loop = struct {
191210 };
192211 self.available_eventfd_resume_nodes.push(eventfd_node);
193212 const kevent_array = (*[1]posix.Kevent)(&eventfd_node.data.kevent);
194 _ = try std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null);
213 _ = try os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null);
195214 eventfd_node.data.kevent.flags = posix.EV_CLEAR | posix.EV_ENABLE;
196215 eventfd_node.data.kevent.fflags = posix.NOTE_TRIGGER;
197216 // this one is for waiting for events
......@@ -216,30 +235,30 @@ pub const Loop = struct {
216235 .udata = @ptrToInt(&self.final_resume_node),
217236 };
218237 const kevent_array = (*[1]posix.Kevent)(&self.os_data.final_kevent);
219 _ = try std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null);
238 _ = try os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null);
220239 self.os_data.final_kevent.flags = posix.EV_ENABLE;
221240 self.os_data.final_kevent.fflags = posix.NOTE_TRIGGER;
222241
223242 var extra_thread_index: usize = 0;
224243 errdefer {
225 _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch unreachable;
244 _ = os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch unreachable;
226245 while (extra_thread_index != 0) {
227246 extra_thread_index -= 1;
228247 self.extra_threads[extra_thread_index].wait();
229248 }
230249 }
231250 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
232 self.extra_threads[extra_thread_index] = try std.os.spawnThread(self, workerRun);
251 self.extra_threads[extra_thread_index] = try os.spawnThread(self, workerRun);
233252 }
234253 },
235254 builtin.Os.windows => {
236 self.os_data.io_port = try std.os.windowsCreateIoCompletionPort(
255 self.os_data.io_port = try os.windowsCreateIoCompletionPort(
237256 windows.INVALID_HANDLE_VALUE,
238257 null,
239258 undefined,
240259 undefined,
241260 );
242 errdefer std.os.close(self.os_data.io_port);
261 errdefer os.close(self.os_data.io_port);
243262
244263 for (self.eventfd_resume_nodes) |*eventfd_node, i| {
245264 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
......@@ -262,7 +281,7 @@ pub const Loop = struct {
262281 while (i < extra_thread_index) : (i += 1) {
263282 while (true) {
264283 const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
265 std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
284 os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
266285 break;
267286 }
268287 }
......@@ -272,7 +291,7 @@ pub const Loop = struct {
272291 }
273292 }
274293 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
275 self.extra_threads[extra_thread_index] = try std.os.spawnThread(self, workerRun);
294 self.extra_threads[extra_thread_index] = try os.spawnThread(self, workerRun);
276295 }
277296 },
278297 else => {},
......@@ -282,17 +301,17 @@ pub const Loop = struct {
282301 fn deinitOsData(self: *Loop) void {
283302 switch (builtin.os) {
284303 builtin.Os.linux => {
285 std.os.close(self.os_data.final_eventfd);
286 while (self.available_eventfd_resume_nodes.pop()) |node| std.os.close(node.data.eventfd);
287 std.os.close(self.os_data.epollfd);
304 os.close(self.os_data.final_eventfd);
305 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
306 os.close(self.os_data.epollfd);
288307 self.allocator.free(self.eventfd_resume_nodes);
289308 },
290309 builtin.Os.macosx => {
291310 self.allocator.free(self.os_data.kevents);
292 std.os.close(self.os_data.kqfd);
311 os.close(self.os_data.kqfd);
293312 },
294313 builtin.Os.windows => {
295 std.os.close(self.os_data.io_port);
314 os.close(self.os_data.io_port);
296315 },
297316 else => {},
298317 }
......@@ -307,17 +326,17 @@ pub const Loop = struct {
307326 try self.modFd(
308327 fd,
309328 posix.EPOLL_CTL_ADD,
310 std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT | std.os.linux.EPOLLET,
329 os.linux.EPOLLIN | os.linux.EPOLLOUT | os.linux.EPOLLET,
311330 resume_node,
312331 );
313332 }
314333
315334 pub fn modFd(self: *Loop, fd: i32, op: u32, events: u32, resume_node: *ResumeNode) !void {
316 var ev = std.os.linux.epoll_event{
335 var ev = os.linux.epoll_event{
317336 .events = events,
318 .data = std.os.linux.epoll_data{ .ptr = @ptrToInt(resume_node) },
337 .data = os.linux.epoll_data{ .ptr = @ptrToInt(resume_node) },
319338 };
320 try std.os.linuxEpollCtl(self.os_data.epollfd, op, fd, &ev);
339 try os.linuxEpollCtl(self.os_data.epollfd, op, fd, &ev);
321340 }
322341
323342 pub fn removeFd(self: *Loop, fd: i32) void {
......@@ -326,7 +345,7 @@ pub const Loop = struct {
326345 }
327346
328347 fn removeFdNoCounter(self: *Loop, fd: i32) void {
329 std.os.linuxEpollCtl(self.os_data.epollfd, std.os.linux.EPOLL_CTL_DEL, fd, undefined) catch {};
348 os.linuxEpollCtl(self.os_data.epollfd, os.linux.EPOLL_CTL_DEL, fd, undefined) catch {};
330349 }
331350
332351 pub async fn waitFd(self: *Loop, fd: i32) !void {
......@@ -353,7 +372,7 @@ pub const Loop = struct {
353372 builtin.Os.macosx => {
354373 const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent);
355374 const eventlist = ([*]posix.Kevent)(undefined)[0..0];
356 _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch {
375 _ = os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch {
357376 self.next_tick_queue.unget(next_tick_node);
358377 self.available_eventfd_resume_nodes.push(resume_stack_node);
359378 return;
......@@ -361,8 +380,8 @@ pub const Loop = struct {
361380 },
362381 builtin.Os.linux => {
363382 // the pending count is already accounted for
364 const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT |
365 std.os.linux.EPOLLET;
383 const epoll_events = posix.EPOLLONESHOT | os.linux.EPOLLIN | os.linux.EPOLLOUT |
384 os.linux.EPOLLET;
366385 self.modFd(
367386 eventfd_node.eventfd,
368387 eventfd_node.epoll_op,
......@@ -379,7 +398,7 @@ pub const Loop = struct {
379398 // the consumer code can decide whether to read the completion key.
380399 // it has to do this for normal I/O, so we match that behavior here.
381400 const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
382 std.os.windowsPostQueuedCompletionStatus(
401 os.windowsPostQueuedCompletionStatus(
383402 self.os_data.io_port,
384403 undefined,
385404 eventfd_node.completion_key,
......@@ -406,6 +425,9 @@ pub const Loop = struct {
406425 self.finishOneEvent(); // the reference we start with
407426
408427 self.workerRun();
428
429 self.os_data.fs_thread.wait();
430
409431 for (self.extra_threads) |extra_thread| {
410432 extra_thread.wait();
411433 }
......@@ -453,15 +475,16 @@ pub const Loop = struct {
453475 // cause all the threads to stop
454476 switch (builtin.os) {
455477 builtin.Os.linux => {
478 self.linuxFsRequest(&self.os_data.fs_end_request);
456479 // writing 8 bytes to an eventfd cannot fail
457 std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
480 os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
458481 return;
459482 },
460483 builtin.Os.macosx => {
461484 const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent);
462485 const eventlist = ([*]posix.Kevent)(undefined)[0..0];
463486 // cannot fail because we already added it and this just enables it
464 _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable;
487 _ = os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable;
465488 return;
466489 },
467490 builtin.Os.windows => {
......@@ -469,7 +492,7 @@ pub const Loop = struct {
469492 while (i < self.extra_threads.len + 1) : (i += 1) {
470493 while (true) {
471494 const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
472 std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
495 os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
473496 break;
474497 }
475498 }
......@@ -492,8 +515,8 @@ pub const Loop = struct {
492515 switch (builtin.os) {
493516 builtin.Os.linux => {
494517 // only process 1 event so we don't steal from other threads
495 var events: [1]std.os.linux.epoll_event = undefined;
496 const count = std.os.linuxEpollWait(self.os_data.epollfd, events[0..], -1);
518 var events: [1]os.linux.epoll_event = undefined;
519 const count = os.linuxEpollWait(self.os_data.epollfd, events[0..], -1);
497520 for (events[0..count]) |ev| {
498521 const resume_node = @intToPtr(*ResumeNode, ev.data.ptr);
499522 const handle = resume_node.handle;
......@@ -516,7 +539,7 @@ pub const Loop = struct {
516539 },
517540 builtin.Os.macosx => {
518541 var eventlist: [1]posix.Kevent = undefined;
519 const count = std.os.bsdKEvent(self.os_data.kqfd, self.os_data.kevents, eventlist[0..], null) catch unreachable;
542 const count = os.bsdKEvent(self.os_data.kqfd, self.os_data.kevents, eventlist[0..], null) catch unreachable;
520543 for (eventlist[0..count]) |ev| {
521544 const resume_node = @intToPtr(*ResumeNode, ev.udata);
522545 const handle = resume_node.handle;
......@@ -541,9 +564,9 @@ pub const Loop = struct {
541564 while (true) {
542565 var nbytes: windows.DWORD = undefined;
543566 var overlapped: ?*windows.OVERLAPPED = undefined;
544 switch (std.os.windowsGetQueuedCompletionStatus(self.os_data.io_port, &nbytes, &completion_key, &overlapped, windows.INFINITE)) {
545 std.os.WindowsWaitResult.Aborted => return,
546 std.os.WindowsWaitResult.Normal => {},
567 switch (os.windowsGetQueuedCompletionStatus(self.os_data.io_port, &nbytes, &completion_key, &overlapped, windows.INFINITE)) {
568 os.WindowsWaitResult.Aborted => return,
569 os.WindowsWaitResult.Normal => {},
547570 }
548571 if (overlapped != null) break;
549572 }
......@@ -569,11 +592,73 @@ pub const Loop = struct {
569592 }
570593 }
571594
595 fn linuxFsRequest(self: *Loop, request_node: *fs.RequestNode) void {
596 _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
597 self.os_data.fs_queue.put(request_node);
598 _ = @atomicRmw(i32, &self.os_data.fs_queue_len, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); // let this wrap
599 const rc = os.linux.futex_wake(@ptrToInt(&self.os_data.fs_queue_len), os.linux.FUTEX_WAKE, 1);
600 switch (os.linux.getErrno(rc)) {
601 0 => {},
602 posix.EINVAL => unreachable,
603 else => unreachable,
604 }
605 }
606
607 fn linuxFsRun(self: *Loop) void {
608 var processed_count: i32 = 0; // we let this wrap
609 while (true) {
610 while (self.os_data.fs_queue.get()) |node| {
611 processed_count +%= 1;
612 switch (node.data.msg) {
613 @TagType(fs.Request.Msg).PWriteV => @panic("TODO"),
614 @TagType(fs.Request.Msg).PReadV => |*msg| {
615 msg.result = os.posix_preadv(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset);
616 },
617 @TagType(fs.Request.Msg).OpenRead => |*msg| {
618 const flags = posix.O_LARGEFILE | posix.O_RDONLY;
619 msg.result = os.posixOpenC(msg.path.ptr, flags, 0);
620 },
621 @TagType(fs.Request.Msg).Close => |*msg| os.close(msg.fd),
622 @TagType(fs.Request.Msg).WriteFile => |*msg| blk: {
623 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT |
624 posix.O_CLOEXEC | posix.O_TRUNC;
625 const fd = os.posixOpenC(msg.path.ptr, flags, msg.mode) catch |err| {
626 msg.result = err;
627 break :blk;
628 };
629 defer os.close(fd);
630 msg.result = os.posixWrite(fd, msg.contents);
631 },
632 @TagType(fs.Request.Msg).End => return,
633 }
634 switch (node.data.finish) {
635 @TagType(fs.Request.Finish).TickNode => |*tick_node| self.onNextTick(tick_node),
636 @TagType(fs.Request.Finish).DeallocCloseOperation => |close_op| {
637 self.allocator.destroy(close_op);
638 },
639 @TagType(fs.Request.Finish).NoAction => {},
640 }
641 self.finishOneEvent();
642 }
643 const rc = os.linux.futex_wait(@ptrToInt(&self.os_data.fs_queue_len), os.linux.FUTEX_WAIT, processed_count, null);
644 switch (os.linux.getErrno(rc)) {
645 0 => continue,
646 posix.EINTR => continue,
647 posix.EAGAIN => continue,
648 else => unreachable,
649 }
650 }
651 }
652
572653 const OsData = switch (builtin.os) {
573654 builtin.Os.linux => struct {
574655 epollfd: i32,
575656 final_eventfd: i32,
576 final_eventfd_event: std.os.linux.epoll_event,
657 final_eventfd_event: os.linux.epoll_event,
658 fs_thread: *os.Thread,
659 fs_queue_len: i32, // we let this wrap
660 fs_queue: std.atomic.Queue(fs.Request),
661 fs_end_request: fs.RequestNode,
577662 },
578663 builtin.Os.macosx => MacOsData,
579664 builtin.Os.windows => struct {
std/io.zig+7-9
......@@ -415,13 +415,12 @@ pub fn PeekStream(comptime buffer_size: usize, comptime InStreamError: type) typ
415415 self.at_end = (read < left);
416416 return pos + read;
417417 }
418
419418 };
420419}
421420
422421pub const SliceInStream = struct {
423422 const Self = this;
424 pub const Error = error { };
423 pub const Error = error{};
425424 pub const Stream = InStream(Error);
426425
427426 pub stream: Stream,
......@@ -481,13 +480,12 @@ pub const SliceOutStream = struct {
481480
482481 assert(self.pos <= self.slice.len);
483482
484 const n =
485 if (self.pos + bytes.len <= self.slice.len)
486 bytes.len
487 else
488 self.slice.len - self.pos;
483 const n = if (self.pos + bytes.len <= self.slice.len)
484 bytes.len
485 else
486 self.slice.len - self.pos;
489487
490 std.mem.copy(u8, self.slice[self.pos..self.pos + n], bytes[0..n]);
488 std.mem.copy(u8, self.slice[self.pos .. self.pos + n], bytes[0..n]);
491489 self.pos += n;
492490
493491 if (n < bytes.len) {
......@@ -586,7 +584,7 @@ pub const BufferedAtomicFile = struct {
586584 });
587585 errdefer allocator.destroy(self);
588586
589 self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.default_file_mode);
587 self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.File.default_mode);
590588 errdefer self.atomic_file.deinit();
591589
592590 self.file_stream = FileOutStream.init(&self.atomic_file.file);
std/os/file.zig+29-10
......@@ -15,10 +15,21 @@ pub const File = struct {
1515 /// The OS-specific file descriptor or file handle.
1616 handle: os.FileHandle,
1717
18 pub const Mode = switch (builtin.os) {
19 Os.windows => void,
20 else => u32,
21 };
22
23 pub const default_mode = switch (builtin.os) {
24 Os.windows => {},
25 else => 0o666,
26 };
27
1828 pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
1929
2030 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
2131 /// Call close to clean up.
32 /// TODO deprecated, just use open
2233 pub fn openRead(allocator: *mem.Allocator, path: []const u8) OpenError!File {
2334 if (is_posix) {
2435 const flags = posix.O_LARGEFILE | posix.O_RDONLY;
......@@ -39,16 +50,18 @@ pub const File = struct {
3950 }
4051 }
4152
42 /// Calls `openWriteMode` with os.default_file_mode for the mode.
53 /// Calls `openWriteMode` with os.File.default_mode for the mode.
54 /// TODO deprecated, just use open
4355 pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {
44 return openWriteMode(allocator, path, os.default_file_mode);
56 return openWriteMode(allocator, path, os.File.default_mode);
4557 }
4658
4759 /// If the path does not exist it will be created.
4860 /// If a file already exists in the destination it will be truncated.
4961 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
5062 /// Call close to clean up.
51 pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
63 /// TODO deprecated, just use open
64 pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {
5265 if (is_posix) {
5366 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
5467 const fd = try os.posixOpen(allocator, path, flags, file_mode);
......@@ -72,7 +85,8 @@ pub const File = struct {
7285 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
7386 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
7487 /// Call close to clean up.
75 pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
88 /// TODO deprecated, just use open
89 pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {
7690 if (is_posix) {
7791 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
7892 const fd = try os.posixOpen(allocator, path, flags, file_mode);
......@@ -282,7 +296,7 @@ pub const File = struct {
282296 Unexpected,
283297 };
284298
285 pub fn mode(self: *File) ModeError!os.FileMode {
299 pub fn mode(self: *File) ModeError!Mode {
286300 if (is_posix) {
287301 var stat: posix.Stat = undefined;
288302 const err = posix.getErrno(posix.fstat(self.handle, &stat));
......@@ -296,7 +310,7 @@ pub const File = struct {
296310
297311 // TODO: we should be able to cast u16 to ModeError!u32, making this
298312 // explicit cast not necessary
299 return os.FileMode(stat.mode);
313 return Mode(stat.mode);
300314 } else if (is_windows) {
301315 return {};
302316 } else {
......@@ -305,9 +319,11 @@ pub const File = struct {
305319 }
306320
307321 pub const ReadError = error{
308 BadFd,
309 Io,
322 FileClosed,
323 InputOutput,
310324 IsDir,
325 WouldBlock,
326 SystemResources,
311327
312328 Unexpected,
313329 };
......@@ -323,9 +339,12 @@ pub const File = struct {
323339 posix.EINTR => continue,
324340 posix.EINVAL => unreachable,
325341 posix.EFAULT => unreachable,
326 posix.EBADF => return error.BadFd,
327 posix.EIO => return error.Io,
342 posix.EAGAIN => return error.WouldBlock,
343 posix.EBADF => return error.FileClosed,
344 posix.EIO => return error.InputOutput,
328345 posix.EISDIR => return error.IsDir,
346 posix.ENOBUFS => return error.SystemResources,
347 posix.ENOMEM => return error.SystemResources,
329348 else => return os.unexpectedErrorPosix(read_err),
330349 }
331350 }
std/os/index.zig+22-12
......@@ -38,16 +38,6 @@ pub const path = @import("path.zig");
3838pub const File = @import("file.zig").File;
3939pub const time = @import("time.zig");
4040
41pub const FileMode = switch (builtin.os) {
42 Os.windows => void,
43 else => u32,
44};
45
46pub const default_file_mode = switch (builtin.os) {
47 Os.windows => {},
48 else => 0o666,
49};
50
5141pub const page_size = 4 * 1024;
5242
5343pub const UserInfo = @import("get_user_id.zig").UserInfo;
......@@ -256,6 +246,26 @@ pub fn posixRead(fd: i32, buf: []u8) !void {
256246 }
257247}
258248
249pub fn posix_preadv(fd: i32, iov: [*]const posix.iovec, count: usize, offset: u64) !usize {
250 while (true) {
251 const rc = posix.preadv(fd, iov, count, offset);
252 const err = posix.getErrno(rc);
253 switch (err) {
254 0 => return rc,
255 posix.EINTR => continue,
256 posix.EINVAL => unreachable,
257 posix.EFAULT => unreachable,
258 posix.EAGAIN => return error.WouldBlock,
259 posix.EBADF => return error.FileClosed,
260 posix.EIO => return error.InputOutput,
261 posix.EISDIR => return error.IsDir,
262 posix.ENOBUFS => return error.SystemResources,
263 posix.ENOMEM => return error.SystemResources,
264 else => return unexpectedErrorPosix(err),
265 }
266 }
267}
268
259269pub const PosixWriteError = error{
260270 WouldBlock,
261271 FileClosed,
......@@ -853,7 +863,7 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
853863/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
854864/// merged and readily available,
855865/// there is a possibility of power loss or application termination leaving temporary files present
856pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: FileMode) !void {
866pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
857867 var in_file = try os.File.openRead(allocator, source_path);
858868 defer in_file.close();
859869
......@@ -879,7 +889,7 @@ pub const AtomicFile = struct {
879889
880890 /// dest_path must remain valid for the lifetime of AtomicFile
881891 /// call finish to atomically replace dest_path with contents
882 pub fn init(allocator: *Allocator, dest_path: []const u8, mode: FileMode) !AtomicFile {
892 pub fn init(allocator: *Allocator, dest_path: []const u8, mode: File.Mode) !AtomicFile {
883893 const dirname = os.path.dirname(dest_path);
884894
885895 var rand_buf: [12]u8 = undefined;
std/os/linux/index.zig+8
......@@ -692,6 +692,10 @@ pub fn futex_wait(uaddr: usize, futex_op: u32, val: i32, timeout: ?*timespec) us
692692 return syscall4(SYS_futex, uaddr, futex_op, @bitCast(u32, val), @ptrToInt(timeout));
693693}
694694
695pub fn futex_wake(uaddr: usize, futex_op: u32, val: i32) usize {
696 return syscall3(SYS_futex, uaddr, futex_op, @bitCast(u32, val));
697}
698
695699pub fn getcwd(buf: [*]u8, size: usize) usize {
696700 return syscall2(SYS_getcwd, @ptrToInt(buf), size);
697701}
......@@ -742,6 +746,10 @@ pub fn read(fd: i32, buf: [*]u8, count: usize) usize {
742746 return syscall3(SYS_read, @intCast(usize, fd), @ptrToInt(buf), count);
743747}
744748
749pub fn preadv(fd: i32, iov: [*]const iovec, count: usize, offset: u64) usize {
750 return syscall4(SYS_preadv, @intCast(usize, fd), @ptrToInt(iov), count, offset);
751}
752
745753// TODO https://github.com/ziglang/zig/issues/265
746754pub fn rmdir(path: [*]const u8) usize {
747755 return syscall1(SYS_rmdir, @ptrToInt(path));