authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-08-07 21:06:21-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-08-07 21:06:21-04:00
log5cbfe392beb26520554e7ec6ae7c67df47cc7e04
treeb1a0091b0827ddba0acb5050ebcbfa889fada870
parent1a28f09684679ebc7006a4ba690f318be810c163

implement std.event.fs.Watch for macos


2 files changed, 300 insertions(+), 148 deletions(-)

std/event/fs.zig+234-110
...@@ -1,8 +1,10 @@...@@ -1,8 +1,10 @@
1const builtin = @import("builtin");
1const std = @import("../index.zig");2const std = @import("../index.zig");
2const event = std.event;3const event = std.event;
3const assert = std.debug.assert;4const assert = std.debug.assert;
4const os = std.os;5const os = std.os;
5const mem = std.mem;6const mem = std.mem;
7const posix = os.posix;
68
7pub const RequestNode = std.atomic.Queue(Request).Node;9pub const RequestNode = std.atomic.Queue(Request).Node;
810
...@@ -19,8 +21,7 @@ pub const Request = struct {...@@ -19,8 +21,7 @@ pub const Request = struct {
19 pub const Msg = union(enum) {21 pub const Msg = union(enum) {
20 PWriteV: PWriteV,22 PWriteV: PWriteV,
21 PReadV: PReadV,23 PReadV: PReadV,
22 OpenRead: OpenRead,24 Open: Open,
23 OpenRW: OpenRW,
24 Close: Close,25 Close: Close,
25 WriteFile: WriteFile,26 WriteFile: WriteFile,
26 End, // special - means the fs thread should exit27 End, // special - means the fs thread should exit
...@@ -43,19 +44,12 @@ pub const Request = struct {...@@ -43,19 +44,12 @@ pub const Request = struct {
43 pub const Error = os.File.ReadError;44 pub const Error = os.File.ReadError;
44 };45 };
4546
46 pub const OpenRead = struct {47 pub const Open = struct {
47 /// must be null terminated. TODO https://github.com/ziglang/zig/issues/26548 /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265
48 path: []const u8,49 path: []const u8,
49 result: Error!os.FileHandle,50 flags: u32,
50
51 pub const Error = os.File.OpenError;
52 };
53
54 pub const OpenRW = struct {
55 /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265
56 path: []const u8,
57 result: Error!os.FileHandle,
58 mode: os.File.Mode,51 mode: os.File.Mode,
52 result: Error!os.FileHandle,
5953
60 pub const Error = os.File.OpenError;54 pub const Error = os.File.OpenError;
61 };55 };
...@@ -77,7 +71,7 @@ pub const Request = struct {...@@ -77,7 +71,7 @@ pub const Request = struct {
77};71};
7872
79/// data - just the inner references - must live until pwritev promise completes.73/// data - just the inner references - must live until pwritev promise completes.
80pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []const u8) !void {74pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {
81 // workaround for https://github.com/ziglang/zig/issues/119475 // workaround for https://github.com/ziglang/zig/issues/1194
82 suspend {76 suspend {
83 resume @handle();77 resume @handle();
...@@ -94,8 +88,8 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:...@@ -94,8 +88,8 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:
94 }88 }
9589
96 var req_node = RequestNode{90 var req_node = RequestNode{
97 .prev = undefined,91 .prev = null,
98 .next = undefined,92 .next = null,
99 .data = Request{93 .data = Request{
100 .msg = Request.Msg{94 .msg = Request.Msg{
101 .PWriteV = Request.Msg.PWriteV{95 .PWriteV = Request.Msg.PWriteV{
...@@ -107,14 +101,16 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:...@@ -107,14 +101,16 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:
107 },101 },
108 .finish = Request.Finish{102 .finish = Request.Finish{
109 .TickNode = event.Loop.NextTickNode{103 .TickNode = event.Loop.NextTickNode{
110 .prev = undefined,104 .prev = null,
111 .next = undefined,105 .next = null,
112 .data = @handle(),106 .data = @handle(),
113 },107 },
114 },108 },
115 },109 },
116 };110 };
117111
112 errdefer loop.posixFsCancel(&req_node);
113
118 suspend {114 suspend {
119 loop.posixFsRequest(&req_node);115 loop.posixFsRequest(&req_node);
120 }116 }
...@@ -123,7 +119,7 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:...@@ -123,7 +119,7 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:
123}119}
124120
125/// data - just the inner references - must live until pwritev promise completes.121/// data - just the inner references - must live until pwritev promise completes.
126pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []u8) !usize {122pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, data: []const []u8, offset: usize) !usize {
127 //const data_dupe = try mem.dupe(loop.allocator, []const u8, data);123 //const data_dupe = try mem.dupe(loop.allocator, []const u8, data);
128 //defer loop.allocator.free(data_dupe);124 //defer loop.allocator.free(data_dupe);
129125
...@@ -143,8 +139,8 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [...@@ -143,8 +139,8 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [
143 }139 }
144140
145 var req_node = RequestNode{141 var req_node = RequestNode{
146 .prev = undefined,142 .prev = null,
147 .next = undefined,143 .next = null,
148 .data = Request{144 .data = Request{
149 .msg = Request.Msg{145 .msg = Request.Msg{
150 .PReadV = Request.Msg.PReadV{146 .PReadV = Request.Msg.PReadV{
...@@ -156,14 +152,16 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [...@@ -156,14 +152,16 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [
156 },152 },
157 .finish = Request.Finish{153 .finish = Request.Finish{
158 .TickNode = event.Loop.NextTickNode{154 .TickNode = event.Loop.NextTickNode{
159 .prev = undefined,155 .prev = null,
160 .next = undefined,156 .next = null,
161 .data = @handle(),157 .data = @handle(),
162 },158 },
163 },159 },
164 },160 },
165 };161 };
166162
163 errdefer loop.posixFsCancel(&req_node);
164
167 suspend {165 suspend {
168 loop.posixFsRequest(&req_node);166 loop.posixFsRequest(&req_node);
169 }167 }
...@@ -171,7 +169,9 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [...@@ -171,7 +169,9 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [
171 return req_node.data.msg.PReadV.result;169 return req_node.data.msg.PReadV.result;
172}170}
173171
174pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.FileHandle {172pub async fn open(
173 loop: *event.Loop, path: []const u8, flags: u32, mode: os.File.Mode,
174) os.File.OpenError!os.FileHandle {
175 // workaround for https://github.com/ziglang/zig/issues/1194175 // workaround for https://github.com/ziglang/zig/issues/1194
176 suspend {176 suspend {
177 resume @handle();177 resume @handle();
...@@ -181,30 +181,39 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os....@@ -181,30 +181,39 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.
181 defer loop.allocator.free(path_with_null);181 defer loop.allocator.free(path_with_null);
182182
183 var req_node = RequestNode{183 var req_node = RequestNode{
184 .prev = undefined,184 .prev = null,
185 .next = undefined,185 .next = null,
186 .data = Request{186 .data = Request{
187 .msg = Request.Msg{187 .msg = Request.Msg{
188 .OpenRead = Request.Msg.OpenRead{188 .Open = Request.Msg.Open{
189 .path = path_with_null[0..path.len],189 .path = path_with_null[0..path.len],
190 .flags = flags,
191 .mode = mode,
190 .result = undefined,192 .result = undefined,
191 },193 },
192 },194 },
193 .finish = Request.Finish{195 .finish = Request.Finish{
194 .TickNode = event.Loop.NextTickNode{196 .TickNode = event.Loop.NextTickNode{
195 .prev = undefined,197 .prev = null,
196 .next = undefined,198 .next = null,
197 .data = @handle(),199 .data = @handle(),
198 },200 },
199 },201 },
200 },202 },
201 };203 };
202204
205 errdefer loop.posixFsCancel(&req_node);
206
203 suspend {207 suspend {
204 loop.posixFsRequest(&req_node);208 loop.posixFsRequest(&req_node);
205 }209 }
206210
207 return req_node.data.msg.OpenRead.result;211 return req_node.data.msg.Open.result;
212}
213
214pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.FileHandle {
215 const flags = posix.O_LARGEFILE | posix.O_RDONLY | posix.O_CLOEXEC;
216 return await (async open(loop, path, flags, 0) catch unreachable);
208}217}
209218
210/// Creates if does not exist. Does not truncate.219/// Creates if does not exist. Does not truncate.
...@@ -213,59 +222,29 @@ pub async fn openReadWrite(...@@ -213,59 +222,29 @@ pub async fn openReadWrite(
213 path: []const u8,222 path: []const u8,
214 mode: os.File.Mode,223 mode: os.File.Mode,
215) os.File.OpenError!os.FileHandle {224) os.File.OpenError!os.FileHandle {
216 // workaround for https://github.com/ziglang/zig/issues/1194225 const flags = posix.O_LARGEFILE | posix.O_RDWR | posix.O_CREAT | posix.O_CLOEXEC;
217 suspend {226 return await (async open(loop, path, flags, mode) catch unreachable);
218 resume @handle();
219 }
220
221 const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
222 defer loop.allocator.free(path_with_null);
223
224 var req_node = RequestNode{
225 .prev = undefined,
226 .next = undefined,
227 .data = Request{
228 .msg = Request.Msg{
229 .OpenRW = Request.Msg.OpenRW{
230 .path = path_with_null[0..path.len],
231 .mode = mode,
232 .result = undefined,
233 },
234 },
235 .finish = Request.Finish{
236 .TickNode = event.Loop.NextTickNode{
237 .prev = undefined,
238 .next = undefined,
239 .data = @handle(),
240 },
241 },
242 },
243 };
244
245 suspend {
246 loop.posixFsRequest(&req_node);
247 }
248
249 return req_node.data.msg.OpenRW.result;
250}227}
251228
252/// This abstraction helps to close file handles in defer expressions229/// This abstraction helps to close file handles in defer expressions
253/// without the possibility of failure and without the use of suspend points.230/// without the possibility of failure and without the use of suspend points.
254/// Start a `CloseOperation` before opening a file, so that you can defer231/// Start a `CloseOperation` before opening a file, so that you can defer
255/// `CloseOperation.deinit`.232/// `CloseOperation.finish`.
233/// If you call `setHandle` then finishing will close the fd; otherwise finishing
234/// will deallocate the `CloseOperation`.
256pub const CloseOperation = struct {235pub const CloseOperation = struct {
257 loop: *event.Loop,236 loop: *event.Loop,
258 have_fd: bool,237 have_fd: bool,
259 close_req_node: RequestNode,238 close_req_node: RequestNode,
260239
261 pub fn create(loop: *event.Loop) (error{OutOfMemory}!*CloseOperation) {240 pub fn start(loop: *event.Loop) (error{OutOfMemory}!*CloseOperation) {
262 const self = try loop.allocator.createOne(CloseOperation);241 const self = try loop.allocator.createOne(CloseOperation);
263 self.* = CloseOperation{242 self.* = CloseOperation{
264 .loop = loop,243 .loop = loop,
265 .have_fd = false,244 .have_fd = false,
266 .close_req_node = RequestNode{245 .close_req_node = RequestNode{
267 .prev = undefined,246 .prev = null,
268 .next = undefined,247 .next = null,
269 .data = Request{248 .data = Request{
270 .msg = Request.Msg{249 .msg = Request.Msg{
271 .Close = Request.Msg.Close{ .fd = undefined },250 .Close = Request.Msg.Close{ .fd = undefined },
...@@ -278,7 +257,7 @@ pub const CloseOperation = struct {...@@ -278,7 +257,7 @@ pub const CloseOperation = struct {
278 }257 }
279258
280 /// Defer this after creating.259 /// Defer this after creating.
281 pub fn deinit(self: *CloseOperation) void {260 pub fn finish(self: *CloseOperation) void {
282 if (self.have_fd) {261 if (self.have_fd) {
283 self.loop.posixFsRequest(&self.close_req_node);262 self.loop.posixFsRequest(&self.close_req_node);
284 } else {263 } else {
...@@ -290,6 +269,16 @@ pub const CloseOperation = struct {...@@ -290,6 +269,16 @@ pub const CloseOperation = struct {
290 self.close_req_node.data.msg.Close.fd = handle;269 self.close_req_node.data.msg.Close.fd = handle;
291 self.have_fd = true;270 self.have_fd = true;
292 }271 }
272
273 /// Undo a `setHandle`.
274 pub fn clearHandle(self: *CloseOperation) void {
275 self.have_fd = false;
276 }
277
278 pub fn getHandle(self: *CloseOperation) os.FileHandle {
279 assert(self.have_fd);
280 return self.close_req_node.data.msg.Close.fd;
281 }
293};282};
294283
295/// contents must remain alive until writeFile completes.284/// contents must remain alive until writeFile completes.
...@@ -308,8 +297,8 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons...@@ -308,8 +297,8 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons
308 defer loop.allocator.free(path_with_null);297 defer loop.allocator.free(path_with_null);
309298
310 var req_node = RequestNode{299 var req_node = RequestNode{
311 .prev = undefined,300 .prev = null,
312 .next = undefined,301 .next = null,
313 .data = Request{302 .data = Request{
314 .msg = Request.Msg{303 .msg = Request.Msg{
315 .WriteFile = Request.Msg.WriteFile{304 .WriteFile = Request.Msg.WriteFile{
...@@ -321,14 +310,16 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons...@@ -321,14 +310,16 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons
321 },310 },
322 .finish = Request.Finish{311 .finish = Request.Finish{
323 .TickNode = event.Loop.NextTickNode{312 .TickNode = event.Loop.NextTickNode{
324 .prev = undefined,313 .prev = null,
325 .next = undefined,314 .next = null,
326 .data = @handle(),315 .data = @handle(),
327 },316 },
328 },317 },
329 },318 },
330 };319 };
331320
321 errdefer loop.posixFsCancel(&req_node);
322
332 suspend {323 suspend {
333 loop.posixFsRequest(&req_node);324 loop.posixFsRequest(&req_node);
334 }325 }
...@@ -340,8 +331,8 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons...@@ -340,8 +331,8 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons
340/// is closed.331/// is closed.
341/// Caller owns returned memory.332/// Caller owns returned memory.
342pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) ![]u8 {333pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) ![]u8 {
343 var close_op = try CloseOperation.create(loop);334 var close_op = try CloseOperation.start(loop);
344 defer close_op.deinit();335 defer close_op.finish();
345336
346 const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path);337 const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path);
347 defer loop.allocator.free(path_with_null);338 defer loop.allocator.free(path_with_null);
...@@ -356,7 +347,7 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize)...@@ -356,7 +347,7 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize)
356 try list.ensureCapacity(list.len + os.page_size);347 try list.ensureCapacity(list.len + os.page_size);
357 const buf = list.items[list.len..];348 const buf = list.items[list.len..];
358 const buf_array = [][]u8{buf};349 const buf_array = [][]u8{buf};
359 const amt = try await (async preadv(loop, fd, list.len, buf_array) catch unreachable);350 const amt = try await (async preadv(loop, fd, buf_array, list.len) catch unreachable);
360 list.len += amt;351 list.len += amt;
361 if (list.len > max_size) {352 if (list.len > max_size) {
362 return error.FileTooBig;353 return error.FileTooBig;
...@@ -370,19 +361,38 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize)...@@ -370,19 +361,38 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize)
370pub fn Watch(comptime V: type) type {361pub fn Watch(comptime V: type) type {
371 return struct {362 return struct {
372 channel: *event.Channel(Event),363 channel: *event.Channel(Event),
373 putter: promise,364 os_data: OsData,
374 wd_table: WdTable,365
375 table_lock: event.Lock,366 const OsData = switch (builtin.os) {
376 inotify_fd: i32,367 builtin.Os.macosx => struct{
368 file_table: FileTable,
369 table_lock: event.Lock,
370
371 const FileTable = std.AutoHashMap([]const u8, *Put);
372 const Put = struct {
373 putter: promise,
374 value_ptr: *V,
375 };
376 },
377 builtin.Os.linux => struct {
378 putter: promise,
379 inotify_fd: i32,
380 wd_table: WdTable,
381 table_lock: event.Lock,
382
383 const FileTable = std.AutoHashMap([]const u8, V);
384 },
385 else => @compileError("Unsupported OS"),
386 };
377387
378 const WdTable = std.AutoHashMap(i32, Dir);388 const WdTable = std.AutoHashMap(i32, Dir);
379 const FileTable = std.AutoHashMap([]const u8, V);389 const FileToHandle = std.AutoHashMap([]const u8, promise);
380390
381 const Self = this;391 const Self = this;
382392
383 const Dir = struct {393 const Dir = struct {
384 dirname: []const u8,394 dirname: []const u8,
385 file_table: FileTable,395 file_table: OsData.FileTable,
386 };396 };
387397
388 pub const Event = union(enum) {398 pub const Event = union(enum) {
...@@ -392,26 +402,140 @@ pub fn Watch(comptime V: type) type {...@@ -392,26 +402,140 @@ pub fn Watch(comptime V: type) type {
392 pub const Error = error{402 pub const Error = error{
393 UserResourceLimitReached,403 UserResourceLimitReached,
394 SystemResources,404 SystemResources,
405 AccessDenied,
395 };406 };
396 };407 };
397408
398 pub fn create(loop: *event.Loop, event_buf_count: usize) !*Self {409 pub fn create(loop: *event.Loop, event_buf_count: usize) !*Self {
399 const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
400 errdefer os.close(inotify_fd);
401
402 const channel = try event.Channel(Self.Event).create(loop, event_buf_count);410 const channel = try event.Channel(Self.Event).create(loop, event_buf_count);
403 errdefer channel.destroy();411 errdefer channel.destroy();
404412
405 var result: *Self = undefined;413 switch (builtin.os) {
406 _ = try async<loop.allocator> eventPutter(inotify_fd, channel, &result);414 builtin.Os.linux => {
407 return result;415 const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
416 errdefer os.close(inotify_fd);
417
418 var result: *Self = undefined;
419 _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result);
420 return result;
421 },
422 builtin.Os.macosx => {
423 const self = try loop.allocator.createOne(Self);
424 errdefer loop.allocator.destroy(self);
425
426 self.* = Self{
427 .channel = channel,
428 .os_data = OsData{
429 .table_lock = event.Lock.init(loop),
430 .file_table = OsData.FileTable.init(loop.allocator),
431 },
432 };
433 return self;
434 },
435 else => @compileError("Unsupported OS"),
436 }
408 }437 }
409438
410 pub fn destroy(self: *Self) void {439 pub fn destroy(self: *Self) void {
411 cancel self.putter;440 switch (builtin.os) {
441 builtin.Os.macosx => {
442 self.os_data.table_lock.deinit();
443 var it = self.os_data.file_table.iterator();
444 while (it.next()) |entry| {
445 cancel entry.value.putter;
446 self.channel.loop.allocator.free(entry.key);
447 }
448 self.channel.destroy();
449 },
450 builtin.Os.linux => cancel self.os_data.putter,
451 else => @compileError("Unsupported OS"),
452 }
412 }453 }
413454
414 pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {455 pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
456 switch (builtin.os) {
457 builtin.Os.macosx => return await (async addFileMacosx(self, file_path, value) catch unreachable),
458 builtin.Os.linux => return await (async addFileLinux(self, file_path, value) catch unreachable),
459 else => @compileError("Unsupported OS"),
460 }
461 }
462
463 async fn addFileMacosx(self: *Self, file_path: []const u8, value: V) !?V {
464 const resolved_path = try os.path.resolve(self.channel.loop.allocator, file_path);
465 var resolved_path_consumed = false;
466 defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path);
467
468 var close_op = try CloseOperation.start(self.channel.loop);
469 var close_op_consumed = false;
470 defer if (!close_op_consumed) close_op.finish();
471
472 const flags = posix.O_SYMLINK|posix.O_EVTONLY;
473 const mode = 0;
474 const fd = try await (async open(self.channel.loop, resolved_path, flags, mode) catch unreachable);
475 close_op.setHandle(fd);
476
477 var put_data: *OsData.Put = undefined;
478 const putter = try async self.kqPutEvents(close_op, value, &put_data);
479 close_op_consumed = true;
480 errdefer cancel putter;
481
482 const result = blk: {
483 const held = await (async self.os_data.table_lock.acquire() catch unreachable);
484 defer held.release();
485
486 const gop = try self.os_data.file_table.getOrPut(resolved_path);
487 if (gop.found_existing) {
488 const prev_value = gop.kv.value.value_ptr.*;
489 cancel gop.kv.value.putter;
490 gop.kv.value = put_data;
491 break :blk prev_value;
492 } else {
493 resolved_path_consumed = true;
494 gop.kv.value = put_data;
495 break :blk null;
496 }
497 };
498
499 return result;
500 }
501
502 async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void {
503 // TODO https://github.com/ziglang/zig/issues/1194
504 suspend {
505 resume @handle();
506 }
507
508 var value_copy = value;
509 var put = OsData.Put{
510 .putter = @handle(),
511 .value_ptr = &value_copy,
512 };
513 out_put.* = &put;
514 self.channel.loop.beginOneEvent();
515
516 defer {
517 close_op.finish();
518 self.channel.loop.finishOneEvent();
519 }
520
521 while (true) {
522 (await (async self.channel.loop.bsdWaitKev(
523 @intCast(usize, close_op.getHandle()), posix.EVFILT_VNODE, posix.NOTE_WRITE,
524 ) catch unreachable)) catch |err| switch (err) {
525 error.EventNotFound => unreachable,
526 error.ProcessNotFound => unreachable,
527 error.AccessDenied, error.SystemResources => {
528 // TODO https://github.com/ziglang/zig/issues/769
529 const casted_err = @errSetCast(error{AccessDenied,SystemResources}, err);
530 await (async self.channel.put(Self.Event{ .Err = casted_err }) catch unreachable);
531 },
532 };
533
534 await (async self.channel.put(Self.Event{ .CloseWrite = value_copy }) catch unreachable);
535 }
536 }
537
538 async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
415 const dirname = os.path.dirname(file_path) orelse ".";539 const dirname = os.path.dirname(file_path) orelse ".";
416 const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);540 const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);
417 var dirname_with_null_consumed = false;541 var dirname_with_null_consumed = false;
...@@ -423,20 +547,20 @@ pub fn Watch(comptime V: type) type {...@@ -423,20 +547,20 @@ pub fn Watch(comptime V: type) type {
423 defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);547 defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);
424548
425 const wd = try os.linuxINotifyAddWatchC(549 const wd = try os.linuxINotifyAddWatchC(
426 self.inotify_fd,550 self.os_data.inotify_fd,
427 dirname_with_null.ptr,551 dirname_with_null.ptr,
428 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,552 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
429 );553 );
430 // wd is either a newly created watch or an existing one.554 // wd is either a newly created watch or an existing one.
431555
432 const held = await (async self.table_lock.acquire() catch unreachable);556 const held = await (async self.os_data.table_lock.acquire() catch unreachable);
433 defer held.release();557 defer held.release();
434558
435 const gop = try self.wd_table.getOrPut(wd);559 const gop = try self.os_data.wd_table.getOrPut(wd);
436 if (!gop.found_existing) {560 if (!gop.found_existing) {
437 gop.kv.value = Dir{561 gop.kv.value = Dir{
438 .dirname = dirname_with_null,562 .dirname = dirname_with_null,
439 .file_table = FileTable.init(self.channel.loop.allocator),563 .file_table = OsData.FileTable.init(self.channel.loop.allocator),
440 };564 };
441 dirname_with_null_consumed = true;565 dirname_with_null_consumed = true;
442 }566 }
...@@ -458,7 +582,7 @@ pub fn Watch(comptime V: type) type {...@@ -458,7 +582,7 @@ pub fn Watch(comptime V: type) type {
458 @panic("TODO");582 @panic("TODO");
459 }583 }
460584
461 async fn eventPutter(inotify_fd: i32, channel: *event.Channel(Event), out_watch: **Self) void {585 async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event), out_watch: **Self) void {
462 // TODO https://github.com/ziglang/zig/issues/1194586 // TODO https://github.com/ziglang/zig/issues/1194
463 suspend {587 suspend {
464 resume @handle();588 resume @handle();
...@@ -467,27 +591,27 @@ pub fn Watch(comptime V: type) type {...@@ -467,27 +591,27 @@ pub fn Watch(comptime V: type) type {
467 const loop = channel.loop;591 const loop = channel.loop;
468592
469 var watch = Self{593 var watch = Self{
470 .putter = @handle(),
471 .channel = channel,594 .channel = channel,
472 .wd_table = WdTable.init(loop.allocator),595 .os_data = OsData{
473 .table_lock = event.Lock.init(loop),596 .putter = @handle(),
474 .inotify_fd = inotify_fd,597 .inotify_fd = inotify_fd,
598 .wd_table = WdTable.init(loop.allocator),
599 .table_lock = event.Lock.init(loop),
600 },
475 };601 };
476 out_watch.* = &watch;602 out_watch.* = &watch;
477603
478 loop.beginOneEvent();604 loop.beginOneEvent();
479605
480 defer {606 defer {
481 watch.table_lock.deinit();607 watch.os_data.table_lock.deinit();
482 {608 var wd_it = watch.os_data.wd_table.iterator();
483 var wd_it = watch.wd_table.iterator();609 while (wd_it.next()) |wd_entry| {
484 while (wd_it.next()) |wd_entry| {610 var file_it = wd_entry.value.file_table.iterator();
485 var file_it = wd_entry.value.file_table.iterator();611 while (file_it.next()) |file_entry| {
486 while (file_it.next()) |file_entry| {612 loop.allocator.free(file_entry.key);
487 loop.allocator.free(file_entry.key);
488 }
489 loop.allocator.free(wd_entry.value.dirname);
490 }613 }
614 loop.allocator.free(wd_entry.value.dirname);
491 }615 }
492 loop.finishOneEvent();616 loop.finishOneEvent();
493 os.close(inotify_fd);617 os.close(inotify_fd);
...@@ -511,10 +635,10 @@ pub fn Watch(comptime V: type) type {...@@ -511,10 +635,10 @@ pub fn Watch(comptime V: type) type {
511 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);635 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
512 const basename_with_null = basename_ptr[0 .. std.cstr.len(basename_ptr) + 1];636 const basename_with_null = basename_ptr[0 .. std.cstr.len(basename_ptr) + 1];
513 const user_value = blk: {637 const user_value = blk: {
514 const held = await (async watch.table_lock.acquire() catch unreachable);638 const held = await (async watch.os_data.table_lock.acquire() catch unreachable);
515 defer held.release();639 defer held.release();
516640
517 const dir = &watch.wd_table.get(ev.wd).?.value;641 const dir = &watch.os_data.wd_table.get(ev.wd).?.value;
518 if (dir.file_table.get(basename_with_null)) |entry| {642 if (dir.file_table.get(basename_with_null)) |entry| {
519 break :blk entry.value;643 break :blk entry.value;
520 } else {644 } else {
...@@ -572,7 +696,7 @@ test "write a file, watch it, write it again" {...@@ -572,7 +696,7 @@ test "write a file, watch it, write it again" {
572 try loop.initMultiThreaded(allocator);696 try loop.initMultiThreaded(allocator);
573 defer loop.deinit();697 defer loop.deinit();
574698
575 var result: error!void = undefined;699 var result: error!void = error.ResultNeverWritten;
576 const handle = try async<allocator> testFsWatchCantFail(&loop, &result);700 const handle = try async<allocator> testFsWatchCantFail(&loop, &result);
577 defer cancel handle;701 defer cancel handle;
578702
...@@ -615,7 +739,7 @@ async fn testFsWatch(loop: *event.Loop) !void {...@@ -615,7 +739,7 @@ async fn testFsWatch(loop: *event.Loop) !void {
615 {739 {
616 defer os.close(fd);740 defer os.close(fd);
617741
618 try await try async pwritev(loop, fd, line2_offset, []const []const u8{"lorem ipsum"});742 try await try async pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset);
619 }743 }
620744
621 ev_consumed = true;745 ev_consumed = true;
std/event/loop.zig+66-38
...@@ -116,7 +116,7 @@ pub const Loop = struct {...@@ -116,7 +116,7 @@ pub const Loop = struct {
116 switch (builtin.os) {116 switch (builtin.os) {
117 builtin.Os.linux => {117 builtin.Os.linux => {
118 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();118 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
119 self.os_data.fs_queue_len = 0;119 self.os_data.fs_queue_item = 0;
120 // we need another thread for the file system because Linux does not have an async120 // we need another thread for the file system because Linux does not have an async
121 // file system I/O API.121 // file system I/O API.
122 self.os_data.fs_end_request = fs.RequestNode{122 self.os_data.fs_end_request = fs.RequestNode{
...@@ -201,9 +201,6 @@ pub const Loop = struct {...@@ -201,9 +201,6 @@ pub const Loop = struct {
201 },201 },
202 };202 };
203203
204 self.os_data.kevents = try self.allocator.alloc(posix.Kevent, extra_thread_count);
205 errdefer self.allocator.free(self.os_data.kevents);
206
207 const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];204 const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
208205
209 for (self.eventfd_resume_nodes) |*eventfd_node, i| {206 for (self.eventfd_resume_nodes) |*eventfd_node, i| {
...@@ -230,15 +227,6 @@ pub const Loop = struct {...@@ -230,15 +227,6 @@ pub const Loop = struct {
230 _ = try os.bsdKEvent(self.os_data.kqfd, kevent_array, empty_kevs, null);227 _ = try os.bsdKEvent(self.os_data.kqfd, kevent_array, empty_kevs, null);
231 eventfd_node.data.kevent.flags = posix.EV_CLEAR | posix.EV_ENABLE;228 eventfd_node.data.kevent.flags = posix.EV_CLEAR | posix.EV_ENABLE;
232 eventfd_node.data.kevent.fflags = posix.NOTE_TRIGGER;229 eventfd_node.data.kevent.fflags = posix.NOTE_TRIGGER;
233 // this one is for waiting for events
234 self.os_data.kevents[i] = posix.Kevent{
235 .ident = i,
236 .filter = posix.EVFILT_USER,
237 .flags = 0,
238 .fflags = 0,
239 .data = 0,
240 .udata = @ptrToInt(&eventfd_node.data.base),
241 };
242 }230 }
243231
244 // Pre-add so that we cannot get error.SystemResources232 // Pre-add so that we cannot get error.SystemResources
...@@ -257,16 +245,16 @@ pub const Loop = struct {...@@ -257,16 +245,16 @@ pub const Loop = struct {
257 self.os_data.final_kevent.fflags = posix.NOTE_TRIGGER;245 self.os_data.final_kevent.fflags = posix.NOTE_TRIGGER;
258246
259 self.os_data.fs_kevent_wake = posix.Kevent{247 self.os_data.fs_kevent_wake = posix.Kevent{
260 .ident = extra_thread_count + 1,248 .ident = 0,
261 .filter = posix.EVFILT_USER,249 .filter = posix.EVFILT_USER,
262 .flags = posix.EV_ADD,250 .flags = posix.EV_ADD|posix.EV_ENABLE,
263 .fflags = posix.NOTE_TRIGGER,251 .fflags = posix.NOTE_TRIGGER,
264 .data = 0,252 .data = 0,
265 .udata = undefined,253 .udata = undefined,
266 };254 };
267255
268 self.os_data.fs_kevent_wait = posix.Kevent{256 self.os_data.fs_kevent_wait = posix.Kevent{
269 .ident = extra_thread_count + 1,257 .ident = 0,
270 .filter = posix.EVFILT_USER,258 .filter = posix.EVFILT_USER,
271 .flags = posix.EV_ADD|posix.EV_CLEAR,259 .flags = posix.EV_ADD|posix.EV_CLEAR,
272 .fflags = 0,260 .fflags = 0,
...@@ -349,7 +337,6 @@ pub const Loop = struct {...@@ -349,7 +337,6 @@ pub const Loop = struct {
349 self.allocator.free(self.eventfd_resume_nodes);337 self.allocator.free(self.eventfd_resume_nodes);
350 },338 },
351 builtin.Os.macosx => {339 builtin.Os.macosx => {
352 self.allocator.free(self.os_data.kevents);
353 os.close(self.os_data.kqfd);340 os.close(self.os_data.kqfd);
354 os.close(self.os_data.fs_kqfd);341 os.close(self.os_data.fs_kqfd);
355 },342 },
...@@ -384,12 +371,8 @@ pub const Loop = struct {...@@ -384,12 +371,8 @@ pub const Loop = struct {
384 }371 }
385372
386 pub fn linuxRemoveFd(self: *Loop, fd: i32) void {373 pub fn linuxRemoveFd(self: *Loop, fd: i32) void {
387 self.linuxRemoveFdNoCounter(fd);
388 self.finishOneEvent();
389 }
390
391 fn linuxRemoveFdNoCounter(self: *Loop, fd: i32) void {
392 os.linuxEpollCtl(self.os_data.epollfd, os.linux.EPOLL_CTL_DEL, fd, undefined) catch {};374 os.linuxEpollCtl(self.os_data.epollfd, os.linux.EPOLL_CTL_DEL, fd, undefined) catch {};
375 self.finishOneEvent();
393 }376 }
394377
395 pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {378 pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
...@@ -404,6 +387,50 @@ pub const Loop = struct {...@@ -404,6 +387,50 @@ pub const Loop = struct {
404 }387 }
405 }388 }
406389
390 pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !void {
391 defer self.bsdRemoveKev(ident, filter);
392 suspend {
393 // TODO explicitly put this memory in the coroutine frame #1194
394 var resume_node = ResumeNode{
395 .id = ResumeNode.Id.Basic,
396 .handle = @handle(),
397 };
398 try self.bsdAddKev(&resume_node, ident, filter, fflags);
399 }
400 }
401
402 /// resume_node must live longer than the promise that it holds a reference to.
403 pub fn bsdAddKev(self: *Loop, resume_node: *ResumeNode, ident: usize, filter: i16, fflags: u32) !void {
404 self.beginOneEvent();
405 errdefer self.finishOneEvent();
406 var kev = posix.Kevent{
407 .ident = ident,
408 .filter = filter,
409 .flags = posix.EV_ADD|posix.EV_ENABLE|posix.EV_CLEAR,
410 .fflags = fflags,
411 .data = 0,
412 .udata = @ptrToInt(resume_node),
413 };
414 const kevent_array = (*[1]posix.Kevent)(&kev);
415 const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
416 _ = try os.bsdKEvent(self.os_data.kqfd, kevent_array, empty_kevs, null);
417 }
418
419 pub fn bsdRemoveKev(self: *Loop, ident: usize, filter: i16) void {
420 var kev = posix.Kevent{
421 .ident = ident,
422 .filter = filter,
423 .flags = posix.EV_DELETE,
424 .fflags = 0,
425 .data = 0,
426 .udata = 0,
427 };
428 const kevent_array = (*[1]posix.Kevent)(&kev);
429 const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
430 _ = os.bsdKEvent(self.os_data.kqfd, kevent_array, empty_kevs, null) catch undefined;
431 self.finishOneEvent();
432 }
433
407 fn dispatch(self: *Loop) void {434 fn dispatch(self: *Loop) void {
408 while (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| {435 while (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| {
409 const next_tick_node = self.next_tick_queue.get() orelse {436 const next_tick_node = self.next_tick_queue.get() orelse {
...@@ -598,7 +625,8 @@ pub const Loop = struct {...@@ -598,7 +625,8 @@ pub const Loop = struct {
598 },625 },
599 builtin.Os.macosx => {626 builtin.Os.macosx => {
600 var eventlist: [1]posix.Kevent = undefined;627 var eventlist: [1]posix.Kevent = undefined;
601 const count = os.bsdKEvent(self.os_data.kqfd, self.os_data.kevents, eventlist[0..], null) catch unreachable;628 const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
629 const count = os.bsdKEvent(self.os_data.kqfd, empty_kevs, eventlist[0..], null) catch unreachable;
602 for (eventlist[0..count]) |ev| {630 for (eventlist[0..count]) |ev| {
603 const resume_node = @intToPtr(*ResumeNode, ev.udata);631 const resume_node = @intToPtr(*ResumeNode, ev.udata);
604 const handle = resume_node.handle;632 const handle = resume_node.handle;
...@@ -617,7 +645,6 @@ pub const Loop = struct {...@@ -617,7 +645,6 @@ pub const Loop = struct {
617 self.finishOneEvent();645 self.finishOneEvent();
618 }646 }
619 }647 }
620 break;
621 },648 },
622 builtin.Os.windows => {649 builtin.Os.windows => {
623 var completion_key: usize = undefined;650 var completion_key: usize = undefined;
...@@ -662,8 +689,8 @@ pub const Loop = struct {...@@ -662,8 +689,8 @@ pub const Loop = struct {
662 _ = os.bsdKEvent(self.os_data.fs_kqfd, fs_kevs, empty_kevs, null) catch unreachable;689 _ = os.bsdKEvent(self.os_data.fs_kqfd, fs_kevs, empty_kevs, null) catch unreachable;
663 },690 },
664 builtin.Os.linux => {691 builtin.Os.linux => {
665 _ = @atomicRmw(i32, &self.os_data.fs_queue_len, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); // let this wrap692 _ = @atomicRmw(u8, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
666 const rc = os.linux.futex_wake(@ptrToInt(&self.os_data.fs_queue_len), os.linux.FUTEX_WAKE, 1);693 const rc = os.linux.futex_wake(@ptrToInt(&self.os_data.fs_queue_item), os.linux.FUTEX_WAKE, 1);
667 switch (os.linux.getErrno(rc)) {694 switch (os.linux.getErrno(rc)) {
668 0 => {},695 0 => {},
669 posix.EINVAL => unreachable,696 posix.EINVAL => unreachable,
...@@ -674,11 +701,18 @@ pub const Loop = struct {...@@ -674,11 +701,18 @@ pub const Loop = struct {
674 }701 }
675 }702 }
676703
704 fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void {
705 if (self.os_data.fs_queue.remove(request_node)) {
706 self.finishOneEvent();
707 }
708 }
709
677 fn posixFsRun(self: *Loop) void {710 fn posixFsRun(self: *Loop) void {
678 var processed_count: i32 = 0; // we let this wrap
679 while (true) {711 while (true) {
712 if (builtin.os == builtin.Os.linux) {
713 _ = @atomicRmw(u8, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
714 }
680 while (self.os_data.fs_queue.get()) |node| {715 while (self.os_data.fs_queue.get()) |node| {
681 processed_count +%= 1;
682 switch (node.data.msg) {716 switch (node.data.msg) {
683 @TagType(fs.Request.Msg).End => return,717 @TagType(fs.Request.Msg).End => return,
684 @TagType(fs.Request.Msg).PWriteV => |*msg| {718 @TagType(fs.Request.Msg).PWriteV => |*msg| {
...@@ -687,13 +721,8 @@ pub const Loop = struct {...@@ -687,13 +721,8 @@ pub const Loop = struct {
687 @TagType(fs.Request.Msg).PReadV => |*msg| {721 @TagType(fs.Request.Msg).PReadV => |*msg| {
688 msg.result = os.posix_preadv(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset);722 msg.result = os.posix_preadv(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset);
689 },723 },
690 @TagType(fs.Request.Msg).OpenRead => |*msg| {724 @TagType(fs.Request.Msg).Open => |*msg| {
691 const flags = posix.O_LARGEFILE | posix.O_RDONLY | posix.O_CLOEXEC;725 msg.result = os.posixOpenC(msg.path.ptr, msg.flags, msg.mode);
692 msg.result = os.posixOpenC(msg.path.ptr, flags, 0);
693 },
694 @TagType(fs.Request.Msg).OpenRW => |*msg| {
695 const flags = posix.O_LARGEFILE | posix.O_RDWR | posix.O_CREAT | posix.O_CLOEXEC;
696 msg.result = os.posixOpenC(msg.path.ptr, flags, msg.mode);
697 },726 },
698 @TagType(fs.Request.Msg).Close => |*msg| os.close(msg.fd),727 @TagType(fs.Request.Msg).Close => |*msg| os.close(msg.fd),
699 @TagType(fs.Request.Msg).WriteFile => |*msg| blk: {728 @TagType(fs.Request.Msg).WriteFile => |*msg| blk: {
...@@ -718,7 +747,7 @@ pub const Loop = struct {...@@ -718,7 +747,7 @@ pub const Loop = struct {
718 }747 }
719 switch (builtin.os) {748 switch (builtin.os) {
720 builtin.Os.linux => {749 builtin.Os.linux => {
721 const rc = os.linux.futex_wait(@ptrToInt(&self.os_data.fs_queue_len), os.linux.FUTEX_WAIT, processed_count, null);750 const rc = os.linux.futex_wait(@ptrToInt(&self.os_data.fs_queue_item), os.linux.FUTEX_WAIT, 0, null);
722 switch (os.linux.getErrno(rc)) {751 switch (os.linux.getErrno(rc)) {
723 0 => continue,752 0 => continue,
724 posix.EINTR => continue,753 posix.EINTR => continue,
...@@ -742,7 +771,7 @@ pub const Loop = struct {...@@ -742,7 +771,7 @@ pub const Loop = struct {
742 final_eventfd: i32,771 final_eventfd: i32,
743 final_eventfd_event: os.linux.epoll_event,772 final_eventfd_event: os.linux.epoll_event,
744 fs_thread: *os.Thread,773 fs_thread: *os.Thread,
745 fs_queue_len: i32, // we let this wrap774 fs_queue_item: u8,
746 fs_queue: std.atomic.Queue(fs.Request),775 fs_queue: std.atomic.Queue(fs.Request),
747 fs_end_request: fs.RequestNode,776 fs_end_request: fs.RequestNode,
748 },777 },
...@@ -757,7 +786,6 @@ pub const Loop = struct {...@@ -757,7 +786,6 @@ pub const Loop = struct {
757 const MacOsData = struct {786 const MacOsData = struct {
758 kqfd: i32,787 kqfd: i32,
759 final_kevent: posix.Kevent,788 final_kevent: posix.Kevent,
760 kevents: []posix.Kevent,
761 fs_kevent_wake: posix.Kevent,789 fs_kevent_wake: posix.Kevent,
762 fs_kevent_wait: posix.Kevent,790 fs_kevent_wait: posix.Kevent,
763 fs_thread: *os.Thread,791 fs_thread: *os.Thread,