authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-11-25 12:31:23-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2019-11-25 12:31:23-05:00
loga061ef42c15082b52ba027ac84f0c24b4d1b4a99
tree1d538cdd8f7028bacba5c79d1b32db6d16d3c297
parent5a98dd42b38b9188cfb96c9ab57dc91af923029f
parent7dba5ea9cfc197cb3a2bbc84d0bce20d8df5886a
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #3761 from Vexu/event.fs

Update event.fs to new event loop

1 files changed, 683 insertions(+), 709 deletions(-)

lib/std/event/fs.zig+683-709
......@@ -9,6 +9,12 @@ const windows = os.windows;
99const Loop = event.Loop;
1010const fd_t = os.fd_t;
1111const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14//! TODO mege this with `std.fs`
15
16const global_event_loop = Loop.instance orelse
17 @compileError("std.event.fs currently only works with event-based I/O");
1218
1319pub const RequestNode = std.atomic.Queue(Request).Node;
1420
......@@ -84,7 +90,7 @@ pub const Request = struct {
8490pub const PWriteVError = error{OutOfMemory} || File.WriteError;
8591
8692/// data - just the inner references - must live until pwritev frame completes.
87pub fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void {
93pub fn pwritev(allocator: *Allocator, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void {
8894 switch (builtin.os) {
8995 .macosx,
9096 .linux,
......@@ -92,8 +98,8 @@ pub fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) P
9298 .netbsd,
9399 .dragonfly,
94100 => {
95 const iovecs = try loop.allocator.alloc(os.iovec_const, data.len);
96 defer loop.allocator.free(iovecs);
101 const iovecs = try allocator.alloc(os.iovec_const, data.len);
102 defer allocator.free(iovecs);
97103
98104 for (data) |buf, i| {
99105 iovecs[i] = os.iovec_const{
......@@ -102,31 +108,31 @@ pub fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) P
102108 };
103109 }
104110
105 return pwritevPosix(loop, fd, iovecs, offset);
111 return pwritevPosix(fd, iovecs, offset);
106112 },
107113 .windows => {
108 const data_copy = try std.mem.dupe(loop.allocator, []const u8, data);
109 defer loop.allocator.free(data_copy);
110 return pwritevWindows(loop, fd, data, offset);
114 const data_copy = try std.mem.dupe(allocator, []const u8, data);
115 defer allocator.free(data_copy);
116 return pwritevWindows(fd, data, offset);
111117 },
112118 else => @compileError("Unsupported OS"),
113119 }
114120}
115121
116122/// data must outlive the returned frame
117pub fn pwritevWindows(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void {
123pub fn pwritevWindows(fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void {
118124 if (data.len == 0) return;
119 if (data.len == 1) return pwriteWindows(loop, fd, data[0], offset);
125 if (data.len == 1) return pwriteWindows(fd, data[0], offset);
120126
121127 // TODO do these in parallel
122128 var off = offset;
123129 for (data) |buf| {
124 try pwriteWindows(loop, fd, buf, off);
130 try pwriteWindows(fd, buf, off);
125131 off += buf.len;
126132 }
127133}
128134
129pub fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void {
135pub fn pwriteWindows(fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void {
130136 var resume_node = Loop.ResumeNode.Basic{
131137 .base = Loop.ResumeNode{
132138 .id = Loop.ResumeNode.Id.Basic,
......@@ -141,9 +147,9 @@ pub fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.Wi
141147 },
142148 };
143149 // TODO only call create io completion port once per fd
144 _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);
145 loop.beginOneEvent();
146 errdefer loop.finishOneEvent();
150 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined);
151 global_event_loop.beginOneEvent();
152 errdefer global_event_loop.finishOneEvent();
147153
148154 errdefer {
149155 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
......@@ -166,12 +172,7 @@ pub fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.Wi
166172}
167173
168174/// iovecs must live until pwritev frame completes.
169pub fn pwritevPosix(
170 loop: *Loop,
171 fd: fd_t,
172 iovecs: []const os.iovec_const,
173 offset: usize,
174) os.WriteError!void {
175pub fn pwritevPosix(fd: fd_t, iovecs: []const os.iovec_const, offset: usize) os.WriteError!void {
175176 var req_node = RequestNode{
176177 .prev = null,
177178 .next = null,
......@@ -194,21 +195,17 @@ pub fn pwritevPosix(
194195 },
195196 };
196197
197 errdefer loop.posixFsCancel(&req_node);
198 errdefer global_event_loop.posixFsCancel(&req_node);
198199
199200 suspend {
200 loop.posixFsRequest(&req_node);
201 global_event_loop.posixFsRequest(&req_node);
201202 }
202203
203204 return req_node.data.msg.PWriteV.result;
204205}
205206
206207/// iovecs must live until pwritev frame completes.
207pub fn writevPosix(
208 loop: *Loop,
209 fd: fd_t,
210 iovecs: []const os.iovec_const,
211) os.WriteError!void {
208pub fn writevPosix(fd: fd_t, iovecs: []const os.iovec_const) os.WriteError!void {
212209 var req_node = RequestNode{
213210 .prev = null,
214211 .next = null,
......@@ -231,7 +228,7 @@ pub fn writevPosix(
231228 };
232229
233230 suspend {
234 loop.posixFsRequest(&req_node);
231 global_event_loop.posixFsRequest(&req_node);
235232 }
236233
237234 return req_node.data.msg.WriteV.result;
......@@ -240,7 +237,7 @@ pub fn writevPosix(
240237pub const PReadVError = error{OutOfMemory} || File.ReadError;
241238
242239/// data - just the inner references - must live until preadv frame completes.
243pub fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize {
240pub fn preadv(allocator: *Allocator, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize {
244241 assert(data.len != 0);
245242 switch (builtin.os) {
246243 .macosx,
......@@ -249,8 +246,8 @@ pub fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVEr
249246 .netbsd,
250247 .dragonfly,
251248 => {
252 const iovecs = try loop.allocator.alloc(os.iovec, data.len);
253 defer loop.allocator.free(iovecs);
249 const iovecs = try allocator.alloc(os.iovec, data.len);
250 defer allocator.free(iovecs);
254251
255252 for (data) |buf, i| {
256253 iovecs[i] = os.iovec{
......@@ -259,21 +256,21 @@ pub fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVEr
259256 };
260257 }
261258
262 return preadvPosix(loop, fd, iovecs, offset);
259 return preadvPosix(fd, iovecs, offset);
263260 },
264261 .windows => {
265 const data_copy = try std.mem.dupe(loop.allocator, []u8, data);
266 defer loop.allocator.free(data_copy);
267 return preadvWindows(loop, fd, data_copy, offset);
262 const data_copy = try std.mem.dupe(allocator, []u8, data);
263 defer allocator.free(data_copy);
264 return preadvWindows(fd, data_copy, offset);
268265 },
269266 else => @compileError("Unsupported OS"),
270267 }
271268}
272269
273270/// data must outlive the returned frame
274pub fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !usize {
271pub fn preadvWindows(fd: fd_t, data: []const []u8, offset: u64) !usize {
275272 assert(data.len != 0);
276 if (data.len == 1) return preadWindows(loop, fd, data[0], offset);
273 if (data.len == 1) return preadWindows(fd, data[0], offset);
277274
278275 // TODO do these in parallel?
279276 var off: usize = 0;
......@@ -281,7 +278,7 @@ pub fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !us
281278 var inner_off: usize = 0;
282279 while (true) {
283280 const v = data[iov_i];
284 const amt_read = try preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off);
281 const amt_read = try preadWindows(fd, v[inner_off .. v.len - inner_off], offset + off);
285282 off += amt_read;
286283 inner_off += amt_read;
287284 if (inner_off == v.len) {
......@@ -295,7 +292,7 @@ pub fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !us
295292 }
296293}
297294
298pub fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize {
295pub fn preadWindows(fd: fd_t, data: []u8, offset: u64) !usize {
299296 var resume_node = Loop.ResumeNode.Basic{
300297 .base = Loop.ResumeNode{
301298 .id = Loop.ResumeNode.Id.Basic,
......@@ -310,9 +307,9 @@ pub fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize {
310307 },
311308 };
312309 // TODO only call create io completion port once per fd
313 _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined;
314 loop.beginOneEvent();
315 errdefer loop.finishOneEvent();
310 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined) catch undefined;
311 global_event_loop.beginOneEvent();
312 errdefer global_event_loop.finishOneEvent();
316313
317314 errdefer {
318315 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
......@@ -334,12 +331,7 @@ pub fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize {
334331}
335332
336333/// iovecs must live until preadv frame completes
337pub fn preadvPosix(
338 loop: *Loop,
339 fd: fd_t,
340 iovecs: []const os.iovec,
341 offset: usize,
342) os.ReadError!usize {
334pub fn preadvPosix(fd: fd_t, iovecs: []const os.iovec, offset: usize) os.ReadError!usize {
343335 var req_node = RequestNode{
344336 .prev = null,
345337 .next = null,
......@@ -362,21 +354,16 @@ pub fn preadvPosix(
362354 },
363355 };
364356
365 errdefer loop.posixFsCancel(&req_node);
357 errdefer global_event_loop.posixFsCancel(&req_node);
366358
367359 suspend {
368 loop.posixFsRequest(&req_node);
360 global_event_loop.posixFsRequest(&req_node);
369361 }
370362
371363 return req_node.data.msg.PReadV.result;
372364}
373365
374pub fn openPosix(
375 loop: *Loop,
376 path: []const u8,
377 flags: u32,
378 mode: File.Mode,
379) File.OpenError!fd_t {
366pub fn openPosix(path: []const u8, flags: u32, mode: File.Mode) File.OpenError!fd_t {
380367 const path_c = try std.os.toPosixPath(path);
381368
382369 var req_node = RequestNode{
......@@ -401,21 +388,21 @@ pub fn openPosix(
401388 },
402389 };
403390
404 errdefer loop.posixFsCancel(&req_node);
391 errdefer global_event_loop.posixFsCancel(&req_node);
405392
406393 suspend {
407 loop.posixFsRequest(&req_node);
394 global_event_loop.posixFsRequest(&req_node);
408395 }
409396
410397 return req_node.data.msg.Open.result;
411398}
412399
413pub fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t {
400pub fn openRead(path: []const u8) File.OpenError!fd_t {
414401 switch (builtin.os) {
415402 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
416403 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
417404 const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
418 return openPosix(loop, path, flags, File.default_mode);
405 return openPosix(path, flags, File.default_mode);
419406 },
420407
421408 .windows => return windows.CreateFile(
......@@ -434,12 +421,12 @@ pub fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t {
434421
435422/// Creates if does not exist. Truncates the file if it exists.
436423/// Uses the default mode.
437pub fn openWrite(loop: *Loop, path: []const u8) File.OpenError!fd_t {
438 return openWriteMode(loop, path, File.default_mode);
424pub fn openWrite(path: []const u8) File.OpenError!fd_t {
425 return openWriteMode(path, File.default_mode);
439426}
440427
441428/// Creates if does not exist. Truncates the file if it exists.
442pub fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenError!fd_t {
429pub fn openWriteMode(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
443430 switch (builtin.os) {
444431 .macosx,
445432 .linux,
......@@ -449,7 +436,7 @@ pub fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenEr
449436 => {
450437 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
451438 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC;
452 return openPosix(loop, path, flags, File.default_mode);
439 return openPosix(path, flags, File.default_mode);
453440 },
454441 .windows => return windows.CreateFile(
455442 path,
......@@ -465,16 +452,12 @@ pub fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenEr
465452}
466453
467454/// Creates if does not exist. Does not truncate.
468pub fn openReadWrite(
469 loop: *Loop,
470 path: []const u8,
471 mode: File.Mode,
472) File.OpenError!fd_t {
455pub fn openReadWrite(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
473456 switch (builtin.os) {
474457 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
475458 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
476459 const flags = O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC;
477 return openPosix(loop, path, flags, mode);
460 return openPosix(path, flags, mode);
478461 },
479462
480463 .windows => return windows.CreateFile(
......@@ -498,7 +481,7 @@ pub fn openReadWrite(
498481/// If you call `setHandle` then finishing will close the fd; otherwise finishing
499482/// will deallocate the `CloseOperation`.
500483pub const CloseOperation = struct {
501 loop: *Loop,
484 allocator: *Allocator,
502485 os_data: OsData,
503486
504487 const OsData = switch (builtin.os) {
......@@ -516,10 +499,10 @@ pub const CloseOperation = struct {
516499 close_req_node: RequestNode,
517500 };
518501
519 pub fn start(loop: *Loop) (error{OutOfMemory}!*CloseOperation) {
520 const self = try loop.allocator.create(CloseOperation);
502 pub fn start(allocator: *Allocator) (error{OutOfMemory}!*CloseOperation) {
503 const self = try allocator.create(CloseOperation);
521504 self.* = CloseOperation{
522 .loop = loop,
505 .allocator = allocator,
523506 .os_data = switch (builtin.os) {
524507 .linux, .macosx, .freebsd, .netbsd, .dragonfly => initOsDataPosix(self),
525508 .windows => OsData{ .handle = null },
......@@ -555,16 +538,16 @@ pub const CloseOperation = struct {
555538 .dragonfly,
556539 => {
557540 if (self.os_data.have_fd) {
558 self.loop.posixFsRequest(&self.os_data.close_req_node);
541 global_event_loop.posixFsRequest(&self.os_data.close_req_node);
559542 } else {
560 self.loop.allocator.destroy(self);
543 self.allocator.destroy(self);
561544 }
562545 },
563546 .windows => {
564547 if (self.os_data.handle) |handle| {
565548 os.close(handle);
566549 }
567 self.loop.allocator.destroy(self);
550 self.allocator.destroy(self);
568551 },
569552 else => @compileError("Unsupported OS"),
570553 }
......@@ -627,25 +610,25 @@ pub const CloseOperation = struct {
627610
628611/// contents must remain alive until writeFile completes.
629612/// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate
630pub fn writeFile(loop: *Loop, path: []const u8, contents: []const u8) !void {
631 return writeFileMode(loop, path, contents, File.default_mode);
613pub fn writeFile(allocator: *Allocator, path: []const u8, contents: []const u8) !void {
614 return writeFileMode(allocator, path, contents, File.default_mode);
632615}
633616
634617/// contents must remain alive until writeFile completes.
635pub fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void {
618pub fn writeFileMode(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
636619 switch (builtin.os) {
637620 .linux,
638621 .macosx,
639622 .freebsd,
640623 .netbsd,
641624 .dragonfly,
642 => return writeFileModeThread(loop, path, contents, mode),
643 .windows => return writeFileWindows(loop, path, contents),
625 => return writeFileModeThread(allocator, path, contents, mode),
626 .windows => return writeFileWindows(path, contents),
644627 else => @compileError("Unsupported OS"),
645628 }
646629}
647630
648fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {
631fn writeFileWindows(path: []const u8, contents: []const u8) !void {
649632 const handle = try windows.CreateFile(
650633 path,
651634 windows.GENERIC_WRITE,
......@@ -657,12 +640,12 @@ fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {
657640 );
658641 defer os.close(handle);
659642
660 try pwriteWindows(loop, handle, contents, 0);
643 try pwriteWindows(handle, contents, 0);
661644}
662645
663fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void {
664 const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
665 defer loop.allocator.free(path_with_null);
646fn writeFileModeThread(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
647 const path_with_null = try std.cstr.addNullByte(allocator, path);
648 defer allocator.free(path_with_null);
666649
667650 var req_node = RequestNode{
668651 .prev = null,
......@@ -686,10 +669,10 @@ fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode
686669 },
687670 };
688671
689 errdefer loop.posixFsCancel(&req_node);
672 errdefer global_event_loop.posixFsCancel(&req_node);
690673
691674 suspend {
692 loop.posixFsRequest(&req_node);
675 global_event_loop.posixFsRequest(&req_node);
693676 }
694677
695678 return req_node.data.msg.WriteFile.result;
......@@ -698,21 +681,21 @@ fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode
698681/// The frame resumes when the last data has been confirmed written, but before the file handle
699682/// is closed.
700683/// Caller owns returned memory.
701pub fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 {
702 var close_op = try CloseOperation.start(loop);
684pub fn readFile(allocator: *Allocator, file_path: []const u8, max_size: usize) ![]u8 {
685 var close_op = try CloseOperation.start(allocator);
703686 defer close_op.finish();
704687
705 const fd = try openRead(loop, file_path);
688 const fd = try openRead(file_path);
706689 close_op.setHandle(fd);
707690
708 var list = std.ArrayList(u8).init(loop.allocator);
691 var list = std.ArrayList(u8).init(allocator);
709692 defer list.deinit();
710693
711694 while (true) {
712695 try list.ensureCapacity(list.len + mem.page_size);
713696 const buf = list.items[list.len..];
714697 const buf_array = [_][]u8{buf};
715 const amt = try preadv(loop, fd, buf_array, list.len);
698 const amt = try preadv(allocator, fd, buf_array, list.len);
716699 list.len += amt;
717700 if (list.len > max_size) {
718701 return error.FileTooBig;
......@@ -738,610 +721,603 @@ fn hashString(s: []const u16) u32 {
738721 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
739722}
740723
741//pub const WatchEventError = error{
742// UserResourceLimitReached,
743// SystemResources,
744// AccessDenied,
745// Unexpected, // TODO remove this possibility
746//};
747//
748//pub fn Watch(comptime V: type) type {
749// return struct {
750// channel: *event.Channel(Event.Error!Event),
751// os_data: OsData,
752//
753// const OsData = switch (builtin.os) {
754// .macosx, .freebsd, .netbsd, .dragonfly => struct {
755// file_table: FileTable,
756// table_lock: event.Lock,
757//
758// const FileTable = std.StringHashmap(*Put);
759// const Put = struct {
760// putter: anyframe,
761// value_ptr: *V,
762// };
763// },
764//
765// .linux => LinuxOsData,
766// .windows => WindowsOsData,
767//
768// else => @compileError("Unsupported OS"),
769// };
770//
771// const WindowsOsData = struct {
772// table_lock: event.Lock,
773// dir_table: DirTable,
774// all_putters: std.atomic.Queue(anyframe),
775// ref_count: std.atomic.Int(usize),
776//
777// const DirTable = std.StringHashMap(*Dir);
778// const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
779//
780// const Dir = struct {
781// putter: anyframe,
782// file_table: FileTable,
783// table_lock: event.Lock,
784// };
785// };
786//
787// const LinuxOsData = struct {
788// putter: anyframe,
789// inotify_fd: i32,
790// wd_table: WdTable,
791// table_lock: event.Lock,
792//
793// const WdTable = std.AutoHashMap(i32, Dir);
794// const FileTable = std.StringHashMap(V);
795//
796// const Dir = struct {
797// dirname: []const u8,
798// file_table: FileTable,
799// };
800// };
801//
802// const FileToHandle = std.StringHashMap(anyframe);
803//
804// const Self = @This();
805//
806// pub const Event = struct {
807// id: Id,
808// data: V,
809//
810// pub const Id = WatchEventId;
811// pub const Error = WatchEventError;
812// };
813//
814// pub fn create(loop: *Loop, event_buf_count: usize) !*Self {
815// const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count);
816// errdefer channel.destroy();
817//
818// switch (builtin.os) {
819// .linux => {
820// const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
821// errdefer os.close(inotify_fd);
822//
823// var result: *Self = undefined;
824// _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result);
825// return result;
826// },
827//
828// .windows => {
829// const self = try loop.allocator.create(Self);
830// errdefer loop.allocator.destroy(self);
831// self.* = Self{
832// .channel = channel,
833// .os_data = OsData{
834// .table_lock = event.Lock.init(loop),
835// .dir_table = OsData.DirTable.init(loop.allocator),
836// .ref_count = std.atomic.Int(usize).init(1),
837// .all_putters = std.atomic.Queue(anyframe).init(),
838// },
839// };
840// return self;
841// },
842//
843// .macosx, .freebsd, .netbsd, .dragonfly => {
844// const self = try loop.allocator.create(Self);
845// errdefer loop.allocator.destroy(self);
846//
847// self.* = Self{
848// .channel = channel,
849// .os_data = OsData{
850// .table_lock = event.Lock.init(loop),
851// .file_table = OsData.FileTable.init(loop.allocator),
852// },
853// };
854// return self;
855// },
856// else => @compileError("Unsupported OS"),
857// }
858// }
859//
860// /// All addFile calls and removeFile calls must have completed.
861// pub fn destroy(self: *Self) void {
862// switch (builtin.os) {
863// .macosx, .freebsd, .netbsd, .dragonfly => {
864// // TODO we need to cancel the frames before destroying the lock
865// self.os_data.table_lock.deinit();
866// var it = self.os_data.file_table.iterator();
867// while (it.next()) |entry| {
868// cancel entry.value.putter;
869// self.channel.loop.allocator.free(entry.key);
870// }
871// self.channel.destroy();
872// },
873// .linux => cancel self.os_data.putter,
874// .windows => {
875// while (self.os_data.all_putters.get()) |putter_node| {
876// cancel putter_node.data;
877// }
878// self.deref();
879// },
880// else => @compileError("Unsupported OS"),
881// }
882// }
883//
884// fn ref(self: *Self) void {
885// _ = self.os_data.ref_count.incr();
886// }
887//
888// fn deref(self: *Self) void {
889// if (self.os_data.ref_count.decr() == 1) {
890// const allocator = self.channel.loop.allocator;
891// self.os_data.table_lock.deinit();
892// var it = self.os_data.dir_table.iterator();
893// while (it.next()) |entry| {
894// allocator.free(entry.key);
895// allocator.destroy(entry.value);
896// }
897// self.os_data.dir_table.deinit();
898// self.channel.destroy();
899// allocator.destroy(self);
900// }
901// }
902//
903// pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
904// switch (builtin.os) {
905// .macosx, .freebsd, .netbsd, .dragonfly => return await (async addFileKEvent(self, file_path, value) catch unreachable),
906// .linux => return await (async addFileLinux(self, file_path, value) catch unreachable),
907// .windows => return await (async addFileWindows(self, file_path, value) catch unreachable),
908// else => @compileError("Unsupported OS"),
909// }
910// }
911//
912// async fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
913// const resolved_path = try std.fs.path.resolve(self.channel.loop.allocator, [_][]const u8{file_path});
914// var resolved_path_consumed = false;
915// defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path);
916//
917// var close_op = try CloseOperation.start(self.channel.loop);
918// var close_op_consumed = false;
919// defer if (!close_op_consumed) close_op.finish();
920//
921// const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
922// const mode = 0;
923// const fd = try await (async openPosix(self.channel.loop, resolved_path, flags, mode) catch unreachable);
924// close_op.setHandle(fd);
925//
926// var put_data: *OsData.Put = undefined;
927// const putter = try async self.kqPutEvents(close_op, value, &put_data);
928// close_op_consumed = true;
929// errdefer cancel putter;
930//
931// const result = blk: {
932// const held = await (async self.os_data.table_lock.acquire() catch unreachable);
933// defer held.release();
934//
935// const gop = try self.os_data.file_table.getOrPut(resolved_path);
936// if (gop.found_existing) {
937// const prev_value = gop.kv.value.value_ptr.*;
938// cancel gop.kv.value.putter;
939// gop.kv.value = put_data;
940// break :blk prev_value;
941// } else {
942// resolved_path_consumed = true;
943// gop.kv.value = put_data;
944// break :blk null;
945// }
946// };
947//
948// return result;
949// }
950//
951// async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void {
952// var value_copy = value;
953// var put = OsData.Put{
954// .putter = @frame(),
955// .value_ptr = &value_copy,
956// };
957// out_put.* = &put;
958// self.channel.loop.beginOneEvent();
959//
960// defer {
961// close_op.finish();
962// self.channel.loop.finishOneEvent();
963// }
964//
965// while (true) {
966// if (await (async self.channel.loop.bsdWaitKev(
967// @intCast(usize, close_op.getHandle()),
968// os.EVFILT_VNODE,
969// os.NOTE_WRITE | os.NOTE_DELETE,
970// ) catch unreachable)) |kev| {
971// // TODO handle EV_ERROR
972// if (kev.fflags & os.NOTE_DELETE != 0) {
973// await (async self.channel.put(Self.Event{
974// .id = Event.Id.Delete,
975// .data = value_copy,
976// }) catch unreachable);
977// } else if (kev.fflags & os.NOTE_WRITE != 0) {
978// await (async self.channel.put(Self.Event{
979// .id = Event.Id.CloseWrite,
980// .data = value_copy,
981// }) catch unreachable);
982// }
983// } else |err| switch (err) {
984// error.EventNotFound => unreachable,
985// error.ProcessNotFound => unreachable,
986// error.Overflow => unreachable,
987// error.AccessDenied, error.SystemResources => |casted_err| {
988// await (async self.channel.put(casted_err) catch unreachable);
989// },
990// }
991// }
992// }
993//
994// async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
995// const value_copy = value;
996//
997// const dirname = std.fs.path.dirname(file_path) orelse ".";
998// const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);
999// var dirname_with_null_consumed = false;
1000// defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null);
1001//
1002// const basename = std.fs.path.basename(file_path);
1003// const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename);
1004// var basename_with_null_consumed = false;
1005// defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);
1006//
1007// const wd = try os.inotify_add_watchC(
1008// self.os_data.inotify_fd,
1009// dirname_with_null.ptr,
1010// os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
1011// );
1012// // wd is either a newly created watch or an existing one.
1013//
1014// const held = await (async self.os_data.table_lock.acquire() catch unreachable);
1015// defer held.release();
1016//
1017// const gop = try self.os_data.wd_table.getOrPut(wd);
1018// if (!gop.found_existing) {
1019// gop.kv.value = OsData.Dir{
1020// .dirname = dirname_with_null,
1021// .file_table = OsData.FileTable.init(self.channel.loop.allocator),
1022// };
1023// dirname_with_null_consumed = true;
1024// }
1025// const dir = &gop.kv.value;
1026//
1027// const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1028// if (file_table_gop.found_existing) {
1029// const prev_value = file_table_gop.kv.value;
1030// file_table_gop.kv.value = value_copy;
1031// return prev_value;
1032// } else {
1033// file_table_gop.kv.value = value_copy;
1034// basename_with_null_consumed = true;
1035// return null;
1036// }
1037// }
1038//
1039// async fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1040// const value_copy = value;
1041// // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1042//
1043// const dirname = try std.mem.dupe(self.channel.loop.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1044// var dirname_consumed = false;
1045// defer if (!dirname_consumed) self.channel.loop.allocator.free(dirname);
1046//
1047// const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, dirname);
1048// defer self.channel.loop.allocator.free(dirname_utf16le);
1049//
1050// // TODO https://github.com/ziglang/zig/issues/265
1051// const basename = std.fs.path.basename(file_path);
1052// const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
1053// var basename_utf16le_null_consumed = false;
1054// defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
1055// const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1056//
1057// const dir_handle = try windows.CreateFileW(
1058// dirname_utf16le.ptr,
1059// windows.FILE_LIST_DIRECTORY,
1060// windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1061// null,
1062// windows.OPEN_EXISTING,
1063// windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1064// null,
1065// );
1066// var dir_handle_consumed = false;
1067// defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1068//
1069// const held = await (async self.os_data.table_lock.acquire() catch unreachable);
1070// defer held.release();
1071//
1072// const gop = try self.os_data.dir_table.getOrPut(dirname);
1073// if (gop.found_existing) {
1074// const dir = gop.kv.value;
1075// const held_dir_lock = await (async dir.table_lock.acquire() catch unreachable);
1076// defer held_dir_lock.release();
1077//
1078// const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1079// if (file_gop.found_existing) {
1080// const prev_value = file_gop.kv.value;
1081// file_gop.kv.value = value_copy;
1082// return prev_value;
1083// } else {
1084// file_gop.kv.value = value_copy;
1085// basename_utf16le_null_consumed = true;
1086// return null;
1087// }
1088// } else {
1089// errdefer _ = self.os_data.dir_table.remove(dirname);
1090// const dir = try self.channel.loop.allocator.create(OsData.Dir);
1091// errdefer self.channel.loop.allocator.destroy(dir);
1092//
1093// dir.* = OsData.Dir{
1094// .file_table = OsData.FileTable.init(self.channel.loop.allocator),
1095// .table_lock = event.Lock.init(self.channel.loop),
1096// .putter = undefined,
1097// };
1098// gop.kv.value = dir;
1099// assert((try dir.file_table.put(basename_utf16le_no_null, value_copy)) == null);
1100// basename_utf16le_null_consumed = true;
1101//
1102// dir.putter = try async self.windowsDirReader(dir_handle, dir);
1103// dir_handle_consumed = true;
1104//
1105// dirname_consumed = true;
1106//
1107// return null;
1108// }
1109// }
1110//
1111// async fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1112// self.ref();
1113// defer self.deref();
1114//
1115// defer os.close(dir_handle);
1116//
1117// var putter_node = std.atomic.Queue(anyframe).Node{
1118// .data = @frame(),
1119// .prev = null,
1120// .next = null,
1121// };
1122// self.os_data.all_putters.put(&putter_node);
1123// defer _ = self.os_data.all_putters.remove(&putter_node);
1124//
1125// var resume_node = Loop.ResumeNode.Basic{
1126// .base = Loop.ResumeNode{
1127// .id = Loop.ResumeNode.Id.Basic,
1128// .handle = @frame(),
1129// .overlapped = windows.OVERLAPPED{
1130// .Internal = 0,
1131// .InternalHigh = 0,
1132// .Offset = 0,
1133// .OffsetHigh = 0,
1134// .hEvent = null,
1135// },
1136// },
1137// };
1138// var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1139//
1140// // TODO handle this error not in the channel but in the setup
1141// _ = windows.CreateIoCompletionPort(
1142// dir_handle,
1143// self.channel.loop.os_data.io_port,
1144// undefined,
1145// undefined,
1146// ) catch |err| {
1147// await (async self.channel.put(err) catch unreachable);
1148// return;
1149// };
1150//
1151// while (true) {
1152// {
1153// // TODO only 1 beginOneEvent for the whole function
1154// self.channel.loop.beginOneEvent();
1155// errdefer self.channel.loop.finishOneEvent();
1156// errdefer {
1157// _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1158// }
1159// suspend {
1160// _ = windows.kernel32.ReadDirectoryChangesW(
1161// dir_handle,
1162// &event_buf,
1163// @intCast(windows.DWORD, event_buf.len),
1164// windows.FALSE, // watch subtree
1165// windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1166// windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1167// windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1168// windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1169// null, // number of bytes transferred (unused for async)
1170// &resume_node.base.overlapped,
1171// null, // completion routine - unused because we use IOCP
1172// );
1173// }
1174// }
1175// var bytes_transferred: windows.DWORD = undefined;
1176// if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1177// const err = switch (windows.kernel32.GetLastError()) {
1178// else => |err| windows.unexpectedError(err),
1179// };
1180// await (async self.channel.put(err) catch unreachable);
1181// } else {
1182// // can't use @bytesToSlice because of the special variable length name field
1183// var ptr = event_buf[0..].ptr;
1184// const end_ptr = ptr + bytes_transferred;
1185// var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1186// while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1187// ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1188// const emit = switch (ev.Action) {
1189// windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1190// windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1191// else => null,
1192// };
1193// if (emit) |id| {
1194// const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1195// const user_value = blk: {
1196// const held = await (async dir.table_lock.acquire() catch unreachable);
1197// defer held.release();
1198//
1199// if (dir.file_table.get(basename_utf16le)) |entry| {
1200// break :blk entry.value;
1201// } else {
1202// break :blk null;
1203// }
1204// };
1205// if (user_value) |v| {
1206// await (async self.channel.put(Event{
1207// .id = id,
1208// .data = v,
1209// }) catch unreachable);
1210// }
1211// }
1212// if (ev.NextEntryOffset == 0) break;
1213// }
1214// }
1215// }
1216// }
1217//
1218// pub async fn removeFile(self: *Self, file_path: []const u8) ?V {
1219// @panic("TODO");
1220// }
1221//
1222// async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event.Error!Event), out_watch: **Self) void {
1223// const loop = channel.loop;
1224//
1225// var watch = Self{
1226// .channel = channel,
1227// .os_data = OsData{
1228// .putter = @frame(),
1229// .inotify_fd = inotify_fd,
1230// .wd_table = OsData.WdTable.init(loop.allocator),
1231// .table_lock = event.Lock.init(loop),
1232// },
1233// };
1234// out_watch.* = &watch;
1235//
1236// loop.beginOneEvent();
1237//
1238// defer {
1239// watch.os_data.table_lock.deinit();
1240// var wd_it = watch.os_data.wd_table.iterator();
1241// while (wd_it.next()) |wd_entry| {
1242// var file_it = wd_entry.value.file_table.iterator();
1243// while (file_it.next()) |file_entry| {
1244// loop.allocator.free(file_entry.key);
1245// }
1246// loop.allocator.free(wd_entry.value.dirname);
1247// }
1248// loop.finishOneEvent();
1249// os.close(inotify_fd);
1250// channel.destroy();
1251// }
1252//
1253// var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1254//
1255// while (true) {
1256// const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
1257// const errno = os.linux.getErrno(rc);
1258// switch (errno) {
1259// 0 => {
1260// // can't use @bytesToSlice because of the special variable length name field
1261// var ptr = event_buf[0..].ptr;
1262// const end_ptr = ptr + event_buf.len;
1263// var ev: *os.linux.inotify_event = undefined;
1264// while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
1265// ev = @ptrCast(*os.linux.inotify_event, ptr);
1266// if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1267// const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1268// const basename_with_null = basename_ptr[0 .. std.mem.len(u8, basename_ptr) + 1];
1269// const user_value = blk: {
1270// const held = await (async watch.os_data.table_lock.acquire() catch unreachable);
1271// defer held.release();
1272//
1273// const dir = &watch.os_data.wd_table.get(ev.wd).?.value;
1274// if (dir.file_table.get(basename_with_null)) |entry| {
1275// break :blk entry.value;
1276// } else {
1277// break :blk null;
1278// }
1279// };
1280// if (user_value) |v| {
1281// await (async channel.put(Event{
1282// .id = WatchEventId.CloseWrite,
1283// .data = v,
1284// }) catch unreachable);
1285// }
1286// }
1287// }
1288// },
1289// os.linux.EINTR => continue,
1290// os.linux.EINVAL => unreachable,
1291// os.linux.EFAULT => unreachable,
1292// os.linux.EAGAIN => {
1293// (await (async loop.linuxWaitFd(
1294// inotify_fd,
1295// os.linux.EPOLLET | os.linux.EPOLLIN,
1296// ) catch unreachable)) catch |err| {
1297// const transformed_err = switch (err) {
1298// error.FileDescriptorAlreadyPresentInSet => unreachable,
1299// error.OperationCausesCircularLoop => unreachable,
1300// error.FileDescriptorNotRegistered => unreachable,
1301// error.FileDescriptorIncompatibleWithEpoll => unreachable,
1302// error.Unexpected => unreachable,
1303// else => |e| e,
1304// };
1305// await (async channel.put(transformed_err) catch unreachable);
1306// };
1307// },
1308// else => unreachable,
1309// }
1310// }
1311// }
1312// };
1313//}
724pub const WatchEventError = error{
725 UserResourceLimitReached,
726 SystemResources,
727 AccessDenied,
728 Unexpected, // TODO remove this possibility
729};
730
731pub fn Watch(comptime V: type) type {
732 return struct {
733 channel: *event.Channel(Event.Error!Event),
734 os_data: OsData,
735 allocator: *Allocator,
736
737 const OsData = switch (builtin.os) {
738 .macosx, .freebsd, .netbsd, .dragonfly => struct {
739 file_table: FileTable,
740 table_lock: event.Lock,
741
742 const FileTable = std.StringHashMap(*Put);
743 const Put = struct {
744 putter_frame: @Frame(kqPutEvents),
745 cancelled: bool = false,
746 value: V,
747 };
748 },
749
750 .linux => LinuxOsData,
751 .windows => WindowsOsData,
752
753 else => @compileError("Unsupported OS"),
754 };
755
756 const WindowsOsData = struct {
757 table_lock: event.Lock,
758 dir_table: DirTable,
759 all_putters: std.atomic.Queue(Put),
760 ref_count: std.atomic.Int(usize),
761
762 const Put = struct {
763 putter: anyframe,
764 cancelled: bool = false,
765 };
766
767 const DirTable = std.StringHashMap(*Dir);
768 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
769
770 const Dir = struct {
771 putter_frame: @Frame(windowsDirReader),
772 file_table: FileTable,
773 table_lock: event.Lock,
774 };
775 };
776
777 const LinuxOsData = struct {
778 putter_frame: @Frame(linuxEventPutter),
779 inotify_fd: i32,
780 wd_table: WdTable,
781 table_lock: event.Lock,
782 cancelled: bool = false,
783
784 const WdTable = std.AutoHashMap(i32, Dir);
785 const FileTable = std.StringHashMap(V);
786
787 const Dir = struct {
788 dirname: []const u8,
789 file_table: FileTable,
790 };
791 };
792
793 const Self = @This();
794
795 pub const Event = struct {
796 id: Id,
797 data: V,
798
799 pub const Id = WatchEventId;
800 pub const Error = WatchEventError;
801 };
802
803 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
804 const channel = try allocator.create(event.Channel(Event.Error!Event));
805 errdefer allocator.destroy(channel);
806 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
807 errdefer allocator.free(buf);
808 channel.init(buf);
809 errdefer channel.deinit();
810
811 const self = try allocator.create(Self);
812 errdefer allocator.destroy(self);
813
814 switch (builtin.os) {
815 .linux => {
816 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
817 errdefer os.close(inotify_fd);
818
819 self.* = Self{
820 .allocator = allocator,
821 .channel = channel,
822 .os_data = OsData{
823 .putter_frame = undefined,
824 .inotify_fd = inotify_fd,
825 .wd_table = OsData.WdTable.init(allocator),
826 .table_lock = event.Lock.init(),
827 },
828 };
829
830 self.os_data.putter_frame = async self.linuxEventPutter();
831 return self;
832 },
833
834 .windows => {
835 self.* = Self{
836 .allocator = allocator,
837 .channel = channel,
838 .os_data = OsData{
839 .table_lock = event.Lock.init(),
840 .dir_table = OsData.DirTable.init(allocator),
841 .ref_count = std.atomic.Int(usize).init(1),
842 .all_putters = std.atomic.Queue(anyframe).init(),
843 },
844 };
845 return self;
846 },
847
848 .macosx, .freebsd, .netbsd, .dragonfly => {
849 self.* = Self{
850 .allocator = allocator,
851 .channel = channel,
852 .os_data = OsData{
853 .table_lock = event.Lock.init(),
854 .file_table = OsData.FileTable.init(allocator),
855 },
856 };
857 return self;
858 },
859 else => @compileError("Unsupported OS"),
860 }
861 }
862
863 /// All addFile calls and removeFile calls must have completed.
864 pub fn deinit(self: *Self) void {
865 switch (builtin.os) {
866 .macosx, .freebsd, .netbsd, .dragonfly => {
867 // TODO we need to cancel the frames before destroying the lock
868 self.os_data.table_lock.deinit();
869 var it = self.os_data.file_table.iterator();
870 while (it.next()) |entry| {
871 entry.cancelled = true;
872 await entry.value.putter;
873 self.allocator.free(entry.key);
874 self.allocator.free(entry.value);
875 }
876 self.channel.deinit();
877 self.allocator.destroy(self.channel.buffer_nodes);
878 self.allocator.destroy(self);
879 },
880 .linux => {
881 self.os_data.cancelled = true;
882 await self.os_data.putter_frame;
883 self.allocator.destroy(self);
884 },
885 .windows => {
886 while (self.os_data.all_putters.get()) |putter_node| {
887 putter_node.cancelled = true;
888 await putter_node.frame;
889 }
890 self.deref();
891 },
892 else => @compileError("Unsupported OS"),
893 }
894 }
895
896 fn ref(self: *Self) void {
897 _ = self.os_data.ref_count.incr();
898 }
899
900 fn deref(self: *Self) void {
901 if (self.os_data.ref_count.decr() == 1) {
902 self.os_data.table_lock.deinit();
903 var it = self.os_data.dir_table.iterator();
904 while (it.next()) |entry| {
905 self.allocator.free(entry.key);
906 self.allocator.destroy(entry.value);
907 }
908 self.os_data.dir_table.deinit();
909 self.channel.deinit();
910 self.allocator.destroy(self.channel.buffer_nodes);
911 self.allocator.destroy(self);
912 }
913 }
914
915 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
916 switch (builtin.os) {
917 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
918 .linux => return addFileLinux(self, file_path, value),
919 .windows => return addFileWindows(self, file_path, value),
920 else => @compileError("Unsupported OS"),
921 }
922 }
923
924 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
925 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
926 var resolved_path_consumed = false;
927 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
928
929 var close_op = try CloseOperation.start(self.allocator);
930 var close_op_consumed = false;
931 defer if (!close_op_consumed) close_op.finish();
932
933 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
934 const mode = 0;
935 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
936 close_op.setHandle(fd);
937
938 var put = try self.allocator.create(OsData.Put);
939 errdefer self.allocator.destroy(put);
940 put.* = OsData.Put{
941 .value = value,
942 .putter_frame = undefined,
943 };
944 put.putter_frame = async self.kqPutEvents(close_op, put);
945 close_op_consumed = true;
946 errdefer {
947 put.cancelled = true;
948 await put.putter_frame;
949 }
950
951 const result = blk: {
952 const held = self.os_data.table_lock.acquire();
953 defer held.release();
954
955 const gop = try self.os_data.file_table.getOrPut(resolved_path);
956 if (gop.found_existing) {
957 const prev_value = gop.kv.value.value;
958 await gop.kv.value.putter_frame;
959 gop.kv.value = put;
960 break :blk prev_value;
961 } else {
962 resolved_path_consumed = true;
963 gop.kv.value = put;
964 break :blk null;
965 }
966 };
967
968 return result;
969 }
970
971 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
972 global_event_loop.beginOneEvent();
973
974 defer {
975 close_op.finish();
976 global_event_loop.finishOneEvent();
977 }
978
979 while (!put.cancelled) {
980 if (global_event_loop.bsdWaitKev(
981 @intCast(usize, close_op.getHandle()),
982 os.EVFILT_VNODE,
983 os.NOTE_WRITE | os.NOTE_DELETE,
984 )) |kev| {
985 // TODO handle EV_ERROR
986 if (kev.fflags & os.NOTE_DELETE != 0) {
987 self.channel.put(Self.Event{
988 .id = Event.Id.Delete,
989 .data = put.value,
990 });
991 } else if (kev.fflags & os.NOTE_WRITE != 0) {
992 self.channel.put(Self.Event{
993 .id = Event.Id.CloseWrite,
994 .data = put.value,
995 });
996 }
997 } else |err| switch (err) {
998 error.EventNotFound => unreachable,
999 error.ProcessNotFound => unreachable,
1000 error.Overflow => unreachable,
1001 error.AccessDenied, error.SystemResources => |casted_err| {
1002 self.channel.put(casted_err);
1003 },
1004 }
1005 }
1006 }
1007
1008 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
1009 const dirname = std.fs.path.dirname(file_path) orelse ".";
1010 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
1011 var dirname_with_null_consumed = false;
1012 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
1013
1014 const basename = std.fs.path.basename(file_path);
1015 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
1016 var basename_with_null_consumed = false;
1017 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
1018
1019 const wd = try os.inotify_add_watchC(
1020 self.os_data.inotify_fd,
1021 dirname_with_null.ptr,
1022 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
1023 );
1024 // wd is either a newly created watch or an existing one.
1025
1026 const held = self.os_data.table_lock.acquire();
1027 defer held.release();
1028
1029 const gop = try self.os_data.wd_table.getOrPut(wd);
1030 if (!gop.found_existing) {
1031 gop.kv.value = OsData.Dir{
1032 .dirname = dirname_with_null,
1033 .file_table = OsData.FileTable.init(self.allocator),
1034 };
1035 dirname_with_null_consumed = true;
1036 }
1037 const dir = &gop.kv.value;
1038
1039 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1040 if (file_table_gop.found_existing) {
1041 const prev_value = file_table_gop.kv.value;
1042 file_table_gop.kv.value = value;
1043 return prev_value;
1044 } else {
1045 file_table_gop.kv.value = value;
1046 basename_with_null_consumed = true;
1047 return null;
1048 }
1049 }
1050
1051 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1052 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1053 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1054 var dirname_consumed = false;
1055 defer if (!dirname_consumed) self.allocator.free(dirname);
1056
1057 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
1058 defer self.allocator.free(dirname_utf16le);
1059
1060 // TODO https://github.com/ziglang/zig/issues/265
1061 const basename = std.fs.path.basename(file_path);
1062 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
1063 var basename_utf16le_null_consumed = false;
1064 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
1065 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1066
1067 const dir_handle = try windows.CreateFileW(
1068 dirname_utf16le.ptr,
1069 windows.FILE_LIST_DIRECTORY,
1070 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1071 null,
1072 windows.OPEN_EXISTING,
1073 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1074 null,
1075 );
1076 var dir_handle_consumed = false;
1077 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1078
1079 const held = self.os_data.table_lock.acquire();
1080 defer held.release();
1081
1082 const gop = try self.os_data.dir_table.getOrPut(dirname);
1083 if (gop.found_existing) {
1084 const dir = gop.kv.value;
1085 const held_dir_lock = dir.table_lock.acquire();
1086 defer held_dir_lock.release();
1087
1088 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1089 if (file_gop.found_existing) {
1090 const prev_value = file_gop.kv.value;
1091 file_gop.kv.value = value;
1092 return prev_value;
1093 } else {
1094 file_gop.kv.value = value;
1095 basename_utf16le_null_consumed = true;
1096 return null;
1097 }
1098 } else {
1099 errdefer _ = self.os_data.dir_table.remove(dirname);
1100 const dir = try self.allocator.create(OsData.Dir);
1101 errdefer self.allocator.destroy(dir);
1102
1103 dir.* = OsData.Dir{
1104 .file_table = OsData.FileTable.init(self.allocator),
1105 .table_lock = event.Lock.init(),
1106 .putter_frame = undefined,
1107 };
1108 gop.kv.value = dir;
1109 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
1110 basename_utf16le_null_consumed = true;
1111
1112 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
1113 dir_handle_consumed = true;
1114
1115 dirname_consumed = true;
1116
1117 return null;
1118 }
1119 }
1120
1121 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1122 self.ref();
1123 defer self.deref();
1124
1125 defer os.close(dir_handle);
1126
1127 var putter_node = std.atomic.Queue(anyframe).Node{
1128 .data = .{ .putter = @frame() },
1129 .prev = null,
1130 .next = null,
1131 };
1132 self.os_data.all_putters.put(&putter_node);
1133 defer _ = self.os_data.all_putters.remove(&putter_node);
1134
1135 var resume_node = Loop.ResumeNode.Basic{
1136 .base = Loop.ResumeNode{
1137 .id = Loop.ResumeNode.Id.Basic,
1138 .handle = @frame(),
1139 .overlapped = windows.OVERLAPPED{
1140 .Internal = 0,
1141 .InternalHigh = 0,
1142 .Offset = 0,
1143 .OffsetHigh = 0,
1144 .hEvent = null,
1145 },
1146 },
1147 };
1148 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1149
1150 // TODO handle this error not in the channel but in the setup
1151 _ = windows.CreateIoCompletionPort(
1152 dir_handle,
1153 global_event_loop.os_data.io_port,
1154 undefined,
1155 undefined,
1156 ) catch |err| {
1157 self.channel.put(err);
1158 return;
1159 };
1160
1161 while (!putter_node.data.cancelled) {
1162 {
1163 // TODO only 1 beginOneEvent for the whole function
1164 global_event_loop.beginOneEvent();
1165 errdefer global_event_loop.finishOneEvent();
1166 errdefer {
1167 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1168 }
1169 suspend {
1170 _ = windows.kernel32.ReadDirectoryChangesW(
1171 dir_handle,
1172 &event_buf,
1173 @intCast(windows.DWORD, event_buf.len),
1174 windows.FALSE, // watch subtree
1175 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1176 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1177 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1178 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1179 null, // number of bytes transferred (unused for async)
1180 &resume_node.base.overlapped,
1181 null, // completion routine - unused because we use IOCP
1182 );
1183 }
1184 }
1185 var bytes_transferred: windows.DWORD = undefined;
1186 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1187 const err = switch (windows.kernel32.GetLastError()) {
1188 else => |err| windows.unexpectedError(err),
1189 };
1190 self.channel.put(err);
1191 } else {
1192 // can't use @bytesToSlice because of the special variable length name field
1193 var ptr = event_buf[0..].ptr;
1194 const end_ptr = ptr + bytes_transferred;
1195 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1196 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1197 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1198 const emit = switch (ev.Action) {
1199 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1200 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1201 else => null,
1202 };
1203 if (emit) |id| {
1204 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1205 const user_value = blk: {
1206 const held = dir.table_lock.acquire();
1207 defer held.release();
1208
1209 if (dir.file_table.get(basename_utf16le)) |entry| {
1210 break :blk entry.value;
1211 } else {
1212 break :blk null;
1213 }
1214 };
1215 if (user_value) |v| {
1216 self.channel.put(Event{
1217 .id = id,
1218 .data = v,
1219 });
1220 }
1221 }
1222 if (ev.NextEntryOffset == 0) break;
1223 }
1224 }
1225 }
1226 }
1227
1228 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
1229 @panic("TODO");
1230 }
1231
1232 fn linuxEventPutter(self: *Self) void {
1233 global_event_loop.beginOneEvent();
1234
1235 defer {
1236 self.os_data.table_lock.deinit();
1237 var wd_it = self.os_data.wd_table.iterator();
1238 while (wd_it.next()) |wd_entry| {
1239 var file_it = wd_entry.value.file_table.iterator();
1240 while (file_it.next()) |file_entry| {
1241 self.allocator.free(file_entry.key);
1242 }
1243 self.allocator.free(wd_entry.value.dirname);
1244 wd_entry.value.file_table.deinit();
1245 }
1246 self.os_data.wd_table.deinit();
1247 global_event_loop.finishOneEvent();
1248 os.close(self.os_data.inotify_fd);
1249 self.channel.deinit();
1250 self.allocator.free(self.channel.buffer_nodes);
1251 }
1252
1253 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1254
1255 while (!self.os_data.cancelled) {
1256 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
1257 const errno = os.linux.getErrno(rc);
1258 switch (errno) {
1259 0 => {
1260 // can't use @bytesToSlice because of the special variable length name field
1261 var ptr = event_buf[0..].ptr;
1262 const end_ptr = ptr + event_buf.len;
1263 var ev: *os.linux.inotify_event = undefined;
1264 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
1265 ev = @ptrCast(*os.linux.inotify_event, ptr);
1266 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1267 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1268 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
1269 const basename_with_null = basename_ptr[0 .. ev.len];
1270 const user_value = blk: {
1271 const held = self.os_data.table_lock.acquire();
1272 defer held.release();
1273
1274 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
1275 if (dir.file_table.get(basename_with_null)) |entry| {
1276 break :blk entry.value;
1277 } else {
1278 break :blk null;
1279 }
1280 };
1281 if (user_value) |v| {
1282 self.channel.put(Event{
1283 .id = WatchEventId.CloseWrite,
1284 .data = v,
1285 });
1286 }
1287 }
1288 }
1289 },
1290 os.linux.EINTR => continue,
1291 os.linux.EINVAL => unreachable,
1292 os.linux.EFAULT => unreachable,
1293 os.linux.EAGAIN => {
1294 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN);
1295 },
1296 else => unreachable,
1297 }
1298 }
1299 }
1300 };
1301}
13141302
13151303const test_tmp_dir = "std_event_fs_test";
13161304
1317// TODO this test is disabled until the async function rewrite is finished.
1318//test "write a file, watch it, write it again" {
1319// return error.SkipZigTest;
1320// const allocator = std.heap.direct_allocator;
1321//
1322// // TODO move this into event loop too
1323// try os.makePath(allocator, test_tmp_dir);
1324// defer os.deleteTree(test_tmp_dir) catch {};
1325//
1326// var loop: Loop = undefined;
1327// try loop.initMultiThreaded(allocator);
1328// defer loop.deinit();
1329//
1330// var result: anyerror!void = error.ResultNeverWritten;
1331// const handle = try async<allocator> testFsWatchCantFail(&loop, &result);
1332// defer cancel handle;
1333//
1334// loop.run();
1335// return result;
1336//}
1337
1338fn testFsWatchCantFail(loop: *Loop, result: *(anyerror!void)) void {
1339 result.* = testFsWatch(loop);
1305test "write a file, watch it, write it again" {
1306 // TODO provide a way to run tests in evented I/O mode
1307 if (!std.io.is_async) return error.SkipZigTest;
1308
1309 const allocator = std.heap.direct_allocator;
1310
1311 // TODO move this into event loop too
1312 try os.makePath(allocator, test_tmp_dir);
1313 defer os.deleteTree(test_tmp_dir) catch {};
1314
1315 return testFsWatch(&allocator);
13401316}
13411317
1342fn testFsWatch(loop: *Loop) !void {
1343 const file_path = try std.fs.path.join(loop.allocator, [][]const u8{ test_tmp_dir, "file.txt" });
1344 defer loop.allocator.free(file_path);
1318fn testFsWatch(allocator: *Allocator) !void {
1319 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
1320 defer allocator.free(file_path);
13451321
13461322 const contents =
13471323 \\line 1
......@@ -1350,27 +1326,27 @@ fn testFsWatch(loop: *Loop) !void {
13501326 const line2_offset = 7;
13511327
13521328 // first just write then read the file
1353 try writeFile(loop, file_path, contents);
1329 try writeFile(allocator, file_path, contents);
13541330
1355 const read_contents = try readFile(loop, file_path, 1024 * 1024);
1331 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
13561332 testing.expectEqualSlices(u8, contents, read_contents);
13571333
13581334 // now watch the file
1359 var watch = try Watch(void).create(loop, 0);
1360 defer watch.destroy();
1335 var watch = try Watch(void).init(allocator, 0);
1336 defer watch.deinit();
13611337
13621338 testing.expect((try watch.addFile(file_path, {})) == null);
13631339
1364 const ev = async watch.channel.get();
1340 const ev = watch.channel.get();
13651341 var ev_consumed = false;
13661342 defer if (!ev_consumed) await ev;
13671343
13681344 // overwrite line 2
1369 const fd = try await openReadWrite(loop, file_path, File.default_mode);
1345 const fd = try await openReadWrite(file_path, File.default_mode);
13701346 {
13711347 defer os.close(fd);
13721348
1373 try pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset);
1349 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
13741350 }
13751351
13761352 ev_consumed = true;
......@@ -1378,7 +1354,7 @@ fn testFsWatch(loop: *Loop) !void {
13781354 WatchEventId.CloseWrite => {},
13791355 WatchEventId.Delete => @panic("wrong event"),
13801356 }
1381 const contents_updated = try readFile(loop, file_path, 1024 * 1024);
1357 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
13821358 testing.expectEqualSlices(u8,
13831359 \\line 1
13841360 \\lorem ipsum
......@@ -1390,16 +1366,15 @@ fn testFsWatch(loop: *Loop) !void {
13901366pub const OutStream = struct {
13911367 fd: fd_t,
13921368 stream: Stream,
1393 loop: *Loop,
1369 allocator: *Allocator,
13941370 offset: usize,
13951371
13961372 pub const Error = File.WriteError;
13971373 pub const Stream = event.io.OutStream(Error);
13981374
1399 pub fn init(loop: *Loop, fd: fd_t, offset: usize) OutStream {
1375 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) OutStream {
14001376 return OutStream{
14011377 .fd = fd,
1402 .loop = loop,
14031378 .offset = offset,
14041379 .stream = Stream{ .writeFn = writeFn },
14051380 };
......@@ -1409,23 +1384,22 @@ pub const OutStream = struct {
14091384 const self = @fieldParentPtr(OutStream, "stream", out_stream);
14101385 const offset = self.offset;
14111386 self.offset += bytes.len;
1412 return pwritev(self.loop, self.fd, [][]const u8{bytes}, offset);
1387 return pwritev(self.allocator, self.fd, [_][]const u8{bytes}, offset);
14131388 }
14141389};
14151390
14161391pub const InStream = struct {
14171392 fd: fd_t,
14181393 stream: Stream,
1419 loop: *Loop,
1394 allocator: *Allocator,
14201395 offset: usize,
14211396
14221397 pub const Error = PReadVError; // TODO make this not have OutOfMemory
14231398 pub const Stream = event.io.InStream(Error);
14241399
1425 pub fn init(loop: *Loop, fd: fd_t, offset: usize) InStream {
1400 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) InStream {
14261401 return InStream{
14271402 .fd = fd,
1428 .loop = loop,
14291403 .offset = offset,
14301404 .stream = Stream{ .readFn = readFn },
14311405 };
......@@ -1433,7 +1407,7 @@ pub const InStream = struct {
14331407
14341408 fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
14351409 const self = @fieldParentPtr(InStream, "stream", in_stream);
1436 const amt = try preadv(self.loop, self.fd, [][]u8{bytes}, self.offset);
1410 const amt = try preadv(self.allocator, self.fd, [_][]u8{bytes}, self.offset);
14371411 self.offset += amt;
14381412 return amt;
14391413 }