| ... | @@ -1,8 +1,10 @@ | ... | @@ -1,8 +1,10 @@ |
| | 1 | const builtin = @import("builtin"); |
| 1 | const std = @import("../index.zig"); | 2 | const std = @import("../index.zig"); |
| 2 | const event = std.event; | 3 | const event = std.event; |
| 3 | const assert = std.debug.assert; | 4 | const assert = std.debug.assert; |
| 4 | const os = std.os; | 5 | const os = std.os; |
| 5 | const mem = std.mem; | 6 | const mem = std.mem; |
| | 7 | const posix = os.posix; |
| 6 | | 8 | |
| 7 | pub const RequestNode = std.atomic.Queue(Request).Node; | 9 | pub const RequestNode = std.atomic.Queue(Request).Node; |
| 8 | | 10 | |
| ... | @@ -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 exit | 27 | 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 | }; |
| 45 | | 46 | |
| 46 | pub const OpenRead = struct { | 47 | pub const Open = struct { |
| 47 | /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265 | 48 | /// 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, |
| 59 | | 53 | |
| 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 | }; |
| 78 | | 72 | |
| 79 | /// data - just the inner references - must live until pwritev promise completes. | 73 | /// data - just the inner references - must live until pwritev promise completes. |
| 80 | pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []const u8) !void { | 74 | pub 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/1194 | 75 | // 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 | } |
| 95 | | 89 | |
| 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 | }; |
| 117 | | 111 | |
| | 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 | } |
| 124 | | 120 | |
| 125 | /// data - just the inner references - must live until pwritev promise completes. | 121 | /// data - just the inner references - must live until pwritev promise completes. |
| 126 | pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: []const []u8) !usize { | 122 | pub 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); |
| 129 | | 125 | |
| ... | @@ -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 | } |
| 144 | | 140 | |
| 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 | }; |
| 166 | | 162 | |
| | 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 | } |
| 173 | | 171 | |
| 174 | pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.FileHandle { | 172 | pub 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/1194 | 175 | // 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); |
| 182 | | 182 | |
| 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 | }; |
| 202 | | 204 | |
| | 205 | errdefer loop.posixFsCancel(&req_node); |
| | 206 | |
| 203 | suspend { | 207 | suspend { |
| 204 | loop.posixFsRequest(&req_node); | 208 | loop.posixFsRequest(&req_node); |
| 205 | } | 209 | } |
| 206 | | 210 | |
| 207 | return req_node.data.msg.OpenRead.result; | 211 | return req_node.data.msg.Open.result; |
| | 212 | } |
| | 213 | |
| | 214 | pub 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 | } |
| 209 | | 218 | |
| 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/1194 | 225 | 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 | } |
| 251 | | 228 | |
| 252 | /// This abstraction helps to close file handles in defer expressions | 229 | /// 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 defer | 231 | /// 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`. |
| 256 | pub const CloseOperation = struct { | 235 | pub 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, |
| 260 | | 239 | |
| 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 | } |
| 279 | | 258 | |
| 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 | }; |
| 294 | | 283 | |
| 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); |
| 309 | | 298 | |
| 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 | }; |
| 331 | | 320 | |
| | 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. |
| 342 | pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) ![]u8 { | 333 | pub 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(); |
| 345 | | 336 | |
| 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) |
| 370 | pub fn Watch(comptime V: type) type { | 361 | pub 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 | }; |
| 377 | | 387 | |
| 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); |
| 380 | | 390 | |
| 381 | const Self = this; | 391 | const Self = this; |
| 382 | | 392 | |
| 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 | }; |
| 387 | | 397 | |
| 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 | }; |
| 397 | | 408 | |
| 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(); |
| 404 | | 412 | |
| 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 | } |
| 409 | | 438 | |
| 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 | } |
| 413 | | 454 | |
| 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); |
| 424 | | 548 | |
| 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. |
| 431 | | 555 | |
| 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(); |
| 434 | | 558 | |
| 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 | } |
| 460 | | 584 | |
| 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/1194 | 586 | // 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; |
| 468 | | 592 | |
| 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; |
| 477 | | 603 | |
| 478 | loop.beginOneEvent(); | 604 | loop.beginOneEvent(); |
| 479 | | 605 | |
| 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(); |
| 516 | | 640 | |
| 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(); |
| 574 | | 698 | |
| 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; |
| 578 | | 702 | |
| ... | @@ -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); |
| 617 | | 741 | |
| 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 | } |
| 620 | | 744 | |
| 621 | ev_consumed = true; | 745 | ev_consumed = true; |