aboutsummaryrefslogtreecommitdiff
path: root/std
diff options
context:
space:
mode:
authorAndrew Kelley <superjoe30@gmail.com>2018-08-10 15:51:17 -0400
committerGitHub <noreply@github.com>2018-08-10 15:51:17 -0400
commitc4b9466da7592b95246909908619b68db5389ceb (patch)
tree6c55cc3ecb3e289fe2c13e346b70560fceaafbef /std
parentd927f347de1f5a19545fc235f8779c2326409543 (diff)
parent598e80957e6eccc13ade72ce2693dcd60934763d (diff)
downloadzig-c4b9466da7592b95246909908619b68db5389ceb.tar.gz
zig-c4b9466da7592b95246909908619b68db5389ceb.zip
Merge pull request #1294 from ziglang/async-fs
introduce std.event.fs for async file system functions
Diffstat (limited to 'std')
-rw-r--r--std/atomic/queue.zig84
-rw-r--r--std/build.zig113
-rw-r--r--std/c/darwin.zig34
-rw-r--r--std/c/index.zig2
-rw-r--r--std/debug/index.zig9
-rw-r--r--std/event.zig22
-rw-r--r--std/event/channel.zig185
-rw-r--r--std/event/fs.zig1362
-rw-r--r--std/event/group.zig28
-rw-r--r--std/event/lock.zig15
-rw-r--r--std/event/loop.zig436
-rw-r--r--std/event/rwlock.zig296
-rw-r--r--std/event/rwlocked.zig58
-rw-r--r--std/event/tcp.zig7
-rw-r--r--std/hash_map.zig314
-rw-r--r--std/index.zig4
-rw-r--r--std/io.zig16
-rw-r--r--std/json.zig2
-rw-r--r--std/linked_list.zig101
-rw-r--r--std/mem.zig2
-rw-r--r--std/mutex.zig27
-rw-r--r--std/os/darwin.zig209
-rw-r--r--std/os/file.zig37
-rw-r--r--std/os/index.zig181
-rw-r--r--std/os/linux/index.zig68
-rw-r--r--std/os/path.zig2
-rw-r--r--std/os/windows/index.zig17
-rw-r--r--std/os/windows/kernel32.zig67
-rw-r--r--std/os/windows/util.zig23
-rw-r--r--std/segmented_list.zig18
-rw-r--r--std/special/build_runner.zig4
-rw-r--r--std/unicode.zig20
-rw-r--r--std/zig/ast.zig218
33 files changed, 3331 insertions, 650 deletions
diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig
index df31c88d2a..6948af43ba 100644
--- a/std/atomic/queue.zig
+++ b/std/atomic/queue.zig
@@ -1,40 +1,38 @@
+const std = @import("../index.zig");
const builtin = @import("builtin");
const AtomicOrder = builtin.AtomicOrder;
const AtomicRmwOp = builtin.AtomicRmwOp;
+const assert = std.debug.assert;
/// Many producer, many consumer, non-allocating, thread-safe.
-/// Uses a spinlock to protect get() and put().
+/// Uses a mutex to protect access.
pub fn Queue(comptime T: type) type {
return struct {
head: ?*Node,
tail: ?*Node,
- lock: u8,
+ mutex: std.Mutex,
pub const Self = this;
-
- pub const Node = struct {
- next: ?*Node,
- data: T,
- };
+ pub const Node = std.LinkedList(T).Node;
pub fn init() Self {
return Self{
.head = null,
.tail = null,
- .lock = 0,
+ .mutex = std.Mutex.init(),
};
}
pub fn put(self: *Self, node: *Node) void {
node.next = null;
- while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
- defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+ const held = self.mutex.acquire();
+ defer held.release();
- const opt_tail = self.tail;
+ node.prev = self.tail;
self.tail = node;
- if (opt_tail) |tail| {
- tail.next = node;
+ if (node.prev) |prev_tail| {
+ prev_tail.next = node;
} else {
assert(self.head == null);
self.head = node;
@@ -42,18 +40,27 @@ pub fn Queue(comptime T: type) type {
}
pub fn get(self: *Self) ?*Node {
- while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
- defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+ const held = self.mutex.acquire();
+ defer held.release();
const head = self.head orelse return null;
self.head = head.next;
- if (head.next == null) self.tail = null;
+ if (head.next) |new_head| {
+ new_head.prev = null;
+ } else {
+ self.tail = null;
+ }
+ // This way, a get() and a remove() are thread-safe with each other.
+ head.prev = null;
+ head.next = null;
return head;
}
pub fn unget(self: *Self, node: *Node) void {
- while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
- defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+ node.prev = null;
+
+ const held = self.mutex.acquire();
+ defer held.release();
const opt_head = self.head;
self.head = node;
@@ -65,13 +72,39 @@ pub fn Queue(comptime T: type) type {
}
}
+ /// Thread-safe with get() and remove(). Returns whether node was actually removed.
+ pub fn remove(self: *Self, node: *Node) bool {
+ const held = self.mutex.acquire();
+ defer held.release();
+
+ if (node.prev == null and node.next == null and self.head != node) {
+ return false;
+ }
+
+ if (node.prev) |prev| {
+ prev.next = node.next;
+ } else {
+ self.head = node.next;
+ }
+ if (node.next) |next| {
+ next.prev = node.prev;
+ } else {
+ self.tail = node.prev;
+ }
+ node.prev = null;
+ node.next = null;
+ return true;
+ }
+
pub fn isEmpty(self: *Self) bool {
- return @atomicLoad(?*Node, &self.head, builtin.AtomicOrder.SeqCst) != null;
+ const held = self.mutex.acquire();
+ defer held.release();
+ return self.head != null;
}
pub fn dump(self: *Self) void {
- while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
- defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+ const held = self.mutex.acquire();
+ defer held.release();
std.debug.warn("head: ");
dumpRecursive(self.head, 0);
@@ -93,9 +126,6 @@ pub fn Queue(comptime T: type) type {
};
}
-const std = @import("../index.zig");
-const assert = std.debug.assert;
-
const Context = struct {
allocator: *std.mem.Allocator,
queue: *Queue(i32),
@@ -169,6 +199,7 @@ fn startPuts(ctx: *Context) u8 {
std.os.time.sleep(0, 1); // let the os scheduler be our fuzz
const x = @bitCast(i32, r.random.scalar(u32));
const node = ctx.allocator.create(Queue(i32).Node{
+ .prev = undefined,
.next = undefined,
.data = x,
}) catch unreachable;
@@ -198,12 +229,14 @@ test "std.atomic.Queue single-threaded" {
var node_0 = Queue(i32).Node{
.data = 0,
.next = undefined,
+ .prev = undefined,
};
queue.put(&node_0);
var node_1 = Queue(i32).Node{
.data = 1,
.next = undefined,
+ .prev = undefined,
};
queue.put(&node_1);
@@ -212,12 +245,14 @@ test "std.atomic.Queue single-threaded" {
var node_2 = Queue(i32).Node{
.data = 2,
.next = undefined,
+ .prev = undefined,
};
queue.put(&node_2);
var node_3 = Queue(i32).Node{
.data = 3,
.next = undefined,
+ .prev = undefined,
};
queue.put(&node_3);
@@ -228,6 +263,7 @@ test "std.atomic.Queue single-threaded" {
var node_4 = Queue(i32).Node{
.data = 4,
.next = undefined,
+ .prev = undefined,
};
queue.put(&node_4);
diff --git a/std/build.zig b/std/build.zig
index 68cf13c1eb..bc14c29dee 100644
--- a/std/build.zig
+++ b/std/build.zig
@@ -424,60 +424,69 @@ pub const Builder = struct {
return mode;
}
- pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) bool {
- if (self.user_input_options.put(name, UserInputOption{
- .name = name,
- .value = UserValue{ .Scalar = value },
- .used = false,
- }) catch unreachable) |*prev_value| {
- // option already exists
- switch (prev_value.value) {
- UserValue.Scalar => |s| {
- // turn it into a list
- var list = ArrayList([]const u8).init(self.allocator);
- list.append(s) catch unreachable;
- list.append(value) catch unreachable;
- _ = self.user_input_options.put(name, UserInputOption{
- .name = name,
- .value = UserValue{ .List = list },
- .used = false,
- }) catch unreachable;
- },
- UserValue.List => |*list| {
- // append to the list
- list.append(value) catch unreachable;
- _ = self.user_input_options.put(name, UserInputOption{
- .name = name,
- .value = UserValue{ .List = list.* },
- .used = false,
- }) catch unreachable;
- },
- UserValue.Flag => {
- warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name);
- return true;
- },
- }
+ pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) !bool {
+ const gop = try self.user_input_options.getOrPut(name);
+ if (!gop.found_existing) {
+ gop.kv.value = UserInputOption{
+ .name = name,
+ .value = UserValue{ .Scalar = value },
+ .used = false,
+ };
+ return false;
+ }
+
+ // option already exists
+ switch (gop.kv.value.value) {
+ UserValue.Scalar => |s| {
+ // turn it into a list
+ var list = ArrayList([]const u8).init(self.allocator);
+ list.append(s) catch unreachable;
+ list.append(value) catch unreachable;
+ _ = self.user_input_options.put(name, UserInputOption{
+ .name = name,
+ .value = UserValue{ .List = list },
+ .used = false,
+ }) catch unreachable;
+ },
+ UserValue.List => |*list| {
+ // append to the list
+ list.append(value) catch unreachable;
+ _ = self.user_input_options.put(name, UserInputOption{
+ .name = name,
+ .value = UserValue{ .List = list.* },
+ .used = false,
+ }) catch unreachable;
+ },
+ UserValue.Flag => {
+ warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name);
+ return true;
+ },
}
return false;
}
- pub fn addUserInputFlag(self: *Builder, name: []const u8) bool {
- if (self.user_input_options.put(name, UserInputOption{
- .name = name,
- .value = UserValue{ .Flag = {} },
- .used = false,
- }) catch unreachable) |*prev_value| {
- switch (prev_value.value) {
- UserValue.Scalar => |s| {
- warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s);
- return true;
- },
- UserValue.List => {
- warn("Flag '-D{}' conflicts with multiple options of the same name.\n", name);
- return true;
- },
- UserValue.Flag => {},
- }
+ pub fn addUserInputFlag(self: *Builder, name: []const u8) !bool {
+ const gop = try self.user_input_options.getOrPut(name);
+ if (!gop.found_existing) {
+ gop.kv.value = UserInputOption{
+ .name = name,
+ .value = UserValue{ .Flag = {} },
+ .used = false,
+ };
+ return false;
+ }
+
+ // option already exists
+ switch (gop.kv.value.value) {
+ UserValue.Scalar => |s| {
+ warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s);
+ return true;
+ },
+ UserValue.List => {
+ warn("Flag '-D{}' conflicts with multiple options of the same name.\n", name);
+ return true;
+ },
+ UserValue.Flag => {},
}
return false;
}
@@ -603,10 +612,10 @@ pub const Builder = struct {
}
fn copyFile(self: *Builder, source_path: []const u8, dest_path: []const u8) !void {
- return self.copyFileMode(source_path, dest_path, os.default_file_mode);
+ return self.copyFileMode(source_path, dest_path, os.File.default_mode);
}
- fn copyFileMode(self: *Builder, source_path: []const u8, dest_path: []const u8, mode: os.FileMode) !void {
+ fn copyFileMode(self: *Builder, source_path: []const u8, dest_path: []const u8, mode: os.File.Mode) !void {
if (self.verbose) {
warn("cp {} {}\n", source_path, dest_path);
}
diff --git a/std/c/darwin.zig b/std/c/darwin.zig
index 1bd1d6c4c9..437b081cac 100644
--- a/std/c/darwin.zig
+++ b/std/c/darwin.zig
@@ -30,10 +30,36 @@ pub extern "c" fn sysctl(name: [*]c_int, namelen: c_uint, oldp: ?*c_void, oldlen
pub extern "c" fn sysctlbyname(name: [*]const u8, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int;
pub extern "c" fn sysctlnametomib(name: [*]const u8, mibp: ?*c_int, sizep: ?*usize) c_int;
+pub extern "c" fn bind(socket: c_int, address: ?*const sockaddr, address_len: socklen_t) c_int;
+pub extern "c" fn socket(domain: c_int, type: c_int, protocol: c_int) c_int;
+
pub use @import("../os/darwin/errno.zig");
pub const _errno = __error;
+pub const in_port_t = u16;
+pub const sa_family_t = u8;
+pub const socklen_t = u32;
+pub const sockaddr = extern union {
+ in: sockaddr_in,
+ in6: sockaddr_in6,
+};
+pub const sockaddr_in = extern struct {
+ len: u8,
+ family: sa_family_t,
+ port: in_port_t,
+ addr: u32,
+ zero: [8]u8,
+};
+pub const sockaddr_in6 = extern struct {
+ len: u8,
+ family: sa_family_t,
+ port: in_port_t,
+ flowinfo: u32,
+ addr: [16]u8,
+ scope_id: u32,
+};
+
pub const timeval = extern struct {
tv_sec: isize,
tv_usec: isize,
@@ -98,14 +124,6 @@ pub const dirent = extern struct {
d_name: u8, // field address is address of first byte of name
};
-pub const sockaddr = extern struct {
- sa_len: u8,
- sa_family: sa_family_t,
- sa_data: [14]u8,
-};
-
-pub const sa_family_t = u8;
-
pub const pthread_attr_t = extern struct {
__sig: c_long,
__opaque: [56]u8,
diff --git a/std/c/index.zig b/std/c/index.zig
index 738b2f9c05..6b20d718ef 100644
--- a/std/c/index.zig
+++ b/std/c/index.zig
@@ -21,8 +21,10 @@ pub extern "c" fn lseek(fd: c_int, offset: isize, whence: c_int) isize;
pub extern "c" fn open(path: [*]const u8, oflag: c_int, ...) c_int;
pub extern "c" fn raise(sig: c_int) c_int;
pub extern "c" fn read(fd: c_int, buf: *c_void, nbyte: usize) isize;
+pub extern "c" fn pread(fd: c_int, buf: *c_void, nbyte: usize, offset: u64) isize;
pub extern "c" fn stat(noalias path: [*]const u8, noalias buf: *Stat) c_int;
pub extern "c" fn write(fd: c_int, buf: *const c_void, nbyte: usize) isize;
+pub extern "c" fn pwrite(fd: c_int, buf: *const c_void, nbyte: usize, offset: u64) isize;
pub extern "c" fn mmap(addr: ?*c_void, len: usize, prot: c_int, flags: c_int, fd: c_int, offset: isize) ?*c_void;
pub extern "c" fn munmap(addr: *c_void, len: usize) c_int;
pub extern "c" fn unlink(path: [*]const u8) c_int;
diff --git a/std/debug/index.zig b/std/debug/index.zig
index ab50d79db3..f06da85f54 100644
--- a/std/debug/index.zig
+++ b/std/debug/index.zig
@@ -23,7 +23,10 @@ pub const runtime_safety = switch (builtin.mode) {
var stderr_file: os.File = undefined;
var stderr_file_out_stream: io.FileOutStream = undefined;
var stderr_stream: ?*io.OutStream(io.FileOutStream.Error) = null;
+var stderr_mutex = std.Mutex.init();
pub fn warn(comptime fmt: []const u8, args: ...) void {
+ const held = stderr_mutex.acquire();
+ defer held.release();
const stderr = getStderrStream() catch return;
stderr.print(fmt, args) catch return;
}
@@ -672,14 +675,10 @@ fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, comptime T: type
const ParseFormValueError = error{
EndOfStream,
- Io,
- BadFd,
- Unexpected,
InvalidDebugInfo,
EndOfFile,
- IsDir,
OutOfMemory,
-};
+} || std.os.File.ReadError;
fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) ParseFormValueError!FormValue {
return switch (form_id) {
diff --git a/std/event.zig b/std/event.zig
index 1e52086286..bd3262a575 100644
--- a/std/event.zig
+++ b/std/event.zig
@@ -1,17 +1,23 @@
+pub const Channel = @import("event/channel.zig").Channel;
+pub const Future = @import("event/future.zig").Future;
+pub const Group = @import("event/group.zig").Group;
+pub const Lock = @import("event/lock.zig").Lock;
pub const Locked = @import("event/locked.zig").Locked;
+pub const RwLock = @import("event/rwlock.zig").RwLock;
+pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
pub const Loop = @import("event/loop.zig").Loop;
-pub const Lock = @import("event/lock.zig").Lock;
+pub const fs = @import("event/fs.zig");
pub const tcp = @import("event/tcp.zig");
-pub const Channel = @import("event/channel.zig").Channel;
-pub const Group = @import("event/group.zig").Group;
-pub const Future = @import("event/future.zig").Future;
test "import event tests" {
+ _ = @import("event/channel.zig");
+ _ = @import("event/fs.zig");
+ _ = @import("event/future.zig");
+ _ = @import("event/group.zig");
+ _ = @import("event/lock.zig");
_ = @import("event/locked.zig");
+ _ = @import("event/rwlock.zig");
+ _ = @import("event/rwlocked.zig");
_ = @import("event/loop.zig");
- _ = @import("event/lock.zig");
_ = @import("event/tcp.zig");
- _ = @import("event/channel.zig");
- _ = @import("event/group.zig");
- _ = @import("event/future.zig");
}
diff --git a/std/event/channel.zig b/std/event/channel.zig
index 71e97f6e78..9ea75a2dd8 100644
--- a/std/event/channel.zig
+++ b/std/event/channel.zig
@@ -5,7 +5,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
const AtomicOrder = builtin.AtomicOrder;
const Loop = std.event.Loop;
-/// many producer, many consumer, thread-safe, lock-free, runtime configurable buffer size
+/// many producer, many consumer, thread-safe, runtime configurable buffer size
/// when buffer is empty, consumers suspend and are resumed by producers
/// when buffer is full, producers suspend and are resumed by consumers
pub fn Channel(comptime T: type) type {
@@ -13,6 +13,7 @@ pub fn Channel(comptime T: type) type {
loop: *Loop,
getters: std.atomic.Queue(GetNode),
+ or_null_queue: std.atomic.Queue(*std.atomic.Queue(GetNode).Node),
putters: std.atomic.Queue(PutNode),
get_count: usize,
put_count: usize,
@@ -26,8 +27,22 @@ pub fn Channel(comptime T: type) type {
const SelfChannel = this;
const GetNode = struct {
- ptr: *T,
tick_node: *Loop.NextTickNode,
+ data: Data,
+
+ const Data = union(enum) {
+ Normal: Normal,
+ OrNull: OrNull,
+ };
+
+ const Normal = struct {
+ ptr: *T,
+ };
+
+ const OrNull = struct {
+ ptr: *?T,
+ or_null: *std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node,
+ };
};
const PutNode = struct {
data: T,
@@ -48,6 +63,7 @@ pub fn Channel(comptime T: type) type {
.need_dispatch = 0,
.getters = std.atomic.Queue(GetNode).init(),
.putters = std.atomic.Queue(PutNode).init(),
+ .or_null_queue = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).init(),
.get_count = 0,
.put_count = 0,
});
@@ -71,18 +87,29 @@ pub fn Channel(comptime T: type) type {
/// puts a data item in the channel. The promise completes when the value has been added to the
/// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter.
pub async fn put(self: *SelfChannel, data: T) void {
+ // TODO fix this workaround
+ suspend {
+ resume @handle();
+ }
+
+ var my_tick_node = Loop.NextTickNode.init(@handle());
+ var queue_node = std.atomic.Queue(PutNode).Node.init(PutNode{
+ .tick_node = &my_tick_node,
+ .data = data,
+ });
+
+ // TODO test canceling a put()
+ errdefer {
+ _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ const need_dispatch = !self.putters.remove(&queue_node);
+ self.loop.cancelOnNextTick(&my_tick_node);
+ if (need_dispatch) {
+ // oops we made the put_count incorrect for a period of time. fix by dispatching.
+ _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+ self.dispatch();
+ }
+ }
suspend {
- var my_tick_node = Loop.NextTickNode{
- .next = undefined,
- .data = @handle(),
- };
- var queue_node = std.atomic.Queue(PutNode).Node{
- .data = PutNode{
- .tick_node = &my_tick_node,
- .data = data,
- },
- .next = undefined,
- };
self.putters.put(&queue_node);
_ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
@@ -93,23 +120,95 @@ pub fn Channel(comptime T: type) type {
/// await this function to get an item from the channel. If the buffer is empty, the promise will
/// complete when the next item is put in the channel.
pub async fn get(self: *SelfChannel) T {
+ // TODO fix this workaround
+ suspend {
+ resume @handle();
+ }
+
// TODO integrate this function with named return values
// so we can get rid of this extra result copy
var result: T = undefined;
+ var my_tick_node = Loop.NextTickNode.init(@handle());
+ var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{
+ .tick_node = &my_tick_node,
+ .data = GetNode.Data{
+ .Normal = GetNode.Normal{ .ptr = &result },
+ },
+ });
+
+ // TODO test canceling a get()
+ errdefer {
+ _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ const need_dispatch = !self.getters.remove(&queue_node);
+ self.loop.cancelOnNextTick(&my_tick_node);
+ if (need_dispatch) {
+ // oops we made the get_count incorrect for a period of time. fix by dispatching.
+ _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+ self.dispatch();
+ }
+ }
+
+ suspend {
+ self.getters.put(&queue_node);
+ _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+
+ self.dispatch();
+ }
+ return result;
+ }
+
+ //pub async fn select(comptime EnumUnion: type, channels: ...) EnumUnion {
+ // assert(@memberCount(EnumUnion) == channels.len); // enum union and channels mismatch
+ // assert(channels.len != 0); // enum unions cannot have 0 fields
+ // if (channels.len == 1) {
+ // const result = await (async channels[0].get() catch unreachable);
+ // return @unionInit(EnumUnion, @memberName(EnumUnion, 0), result);
+ // }
+ //}
+
+ /// Await this function to get an item from the channel. If the buffer is empty and there are no
+ /// puts waiting, this returns null.
+ /// Await is necessary for locking purposes. The function will be resumed after checking the channel
+ /// for data and will not wait for data to be available.
+ pub async fn getOrNull(self: *SelfChannel) ?T {
+ // TODO fix this workaround
suspend {
- var my_tick_node = Loop.NextTickNode{
- .next = undefined,
- .data = @handle(),
- };
- var queue_node = std.atomic.Queue(GetNode).Node{
- .data = GetNode{
+ resume @handle();
+ }
+
+ // TODO integrate this function with named return values
+ // so we can get rid of this extra result copy
+ var result: ?T = null;
+ var my_tick_node = Loop.NextTickNode.init(@handle());
+ var or_null_node = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node.init(undefined);
+ var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{
+ .tick_node = &my_tick_node,
+ .data = GetNode.Data{
+ .OrNull = GetNode.OrNull{
.ptr = &result,
- .tick_node = &my_tick_node,
+ .or_null = &or_null_node,
},
- .next = undefined,
- };
+ },
+ });
+ or_null_node.data = &queue_node;
+
+ // TODO test canceling getOrNull
+ errdefer {
+ _ = self.or_null_queue.remove(&or_null_node);
+ _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ const need_dispatch = !self.getters.remove(&queue_node);
+ self.loop.cancelOnNextTick(&my_tick_node);
+ if (need_dispatch) {
+ // oops we made the get_count incorrect for a period of time. fix by dispatching.
+ _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+ self.dispatch();
+ }
+ }
+
+ suspend {
self.getters.put(&queue_node);
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+ self.or_null_queue.put(&or_null_node);
self.dispatch();
}
@@ -139,7 +238,15 @@ pub fn Channel(comptime T: type) type {
if (get_count == 0) break :one_dispatch;
const get_node = &self.getters.get().?.data;
- get_node.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len];
+ switch (get_node.data) {
+ GetNode.Data.Normal => |info| {
+ info.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len];
+ },
+ GetNode.Data.OrNull => |info| {
+ _ = self.or_null_queue.remove(info.or_null);
+ info.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len];
+ },
+ }
self.loop.onNextTick(get_node.tick_node);
self.buffer_len -= 1;
@@ -151,7 +258,15 @@ pub fn Channel(comptime T: type) type {
const get_node = &self.getters.get().?.data;
const put_node = &self.putters.get().?.data;
- get_node.ptr.* = put_node.data;
+ switch (get_node.data) {
+ GetNode.Data.Normal => |info| {
+ info.ptr.* = put_node.data;
+ },
+ GetNode.Data.OrNull => |info| {
+ _ = self.or_null_queue.remove(info.or_null);
+ info.ptr.* = put_node.data;
+ },
+ }
self.loop.onNextTick(get_node.tick_node);
self.loop.onNextTick(put_node.tick_node);
@@ -176,6 +291,16 @@ pub fn Channel(comptime T: type) type {
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
_ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+ // All the "get or null" functions should resume now.
+ var remove_count: usize = 0;
+ while (self.or_null_queue.get()) |or_null_node| {
+ remove_count += @boolToInt(self.getters.remove(or_null_node.data));
+ self.loop.onNextTick(or_null_node.data.data.tick_node);
+ }
+ if (remove_count != 0) {
+ _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, remove_count, AtomicOrder.SeqCst);
+ }
+
// clear need-dispatch flag
const need_dispatch = @atomicRmw(u8, &self.need_dispatch, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
if (need_dispatch != 0) continue;
@@ -226,6 +351,15 @@ async fn testChannelGetter(loop: *Loop, channel: *Channel(i32)) void {
const value2_promise = try async channel.get();
const value2 = await value2_promise;
assert(value2 == 4567);
+
+ const value3_promise = try async channel.getOrNull();
+ const value3 = await value3_promise;
+ assert(value3 == null);
+
+ const last_put = try async testPut(channel, 4444);
+ const value4 = await try async channel.getOrNull();
+ assert(value4.? == 4444);
+ await last_put;
}
async fn testChannelPutter(channel: *Channel(i32)) void {
@@ -233,3 +367,6 @@ async fn testChannelPutter(channel: *Channel(i32)) void {
await (async channel.put(4567) catch @panic("out of memory"));
}
+async fn testPut(channel: *Channel(i32), value: i32) void {
+ await (async channel.put(value) catch @panic("out of memory"));
+}
diff --git a/std/event/fs.zig b/std/event/fs.zig
new file mode 100644
index 0000000000..00f45f2af5
--- /dev/null
+++ b/std/event/fs.zig
@@ -0,0 +1,1362 @@
+const builtin = @import("builtin");
+const std = @import("../index.zig");
+const event = std.event;
+const assert = std.debug.assert;
+const os = std.os;
+const mem = std.mem;
+const posix = os.posix;
+const windows = os.windows;
+const Loop = event.Loop;
+
+pub const RequestNode = std.atomic.Queue(Request).Node;
+
+pub const Request = struct {
+ msg: Msg,
+ finish: Finish,
+
+ pub const Finish = union(enum) {
+ TickNode: Loop.NextTickNode,
+ DeallocCloseOperation: *CloseOperation,
+ NoAction,
+ };
+
+ pub const Msg = union(enum) {
+ PWriteV: PWriteV,
+ PReadV: PReadV,
+ Open: Open,
+ Close: Close,
+ WriteFile: WriteFile,
+ End, // special - means the fs thread should exit
+
+ pub const PWriteV = struct {
+ fd: os.FileHandle,
+ iov: []os.posix.iovec_const,
+ offset: usize,
+ result: Error!void,
+
+ pub const Error = os.File.WriteError;
+ };
+
+ pub const PReadV = struct {
+ fd: os.FileHandle,
+ iov: []os.posix.iovec,
+ offset: usize,
+ result: Error!usize,
+
+ pub const Error = os.File.ReadError;
+ };
+
+ pub const Open = struct {
+ /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265
+ path: []const u8,
+ flags: u32,
+ mode: os.File.Mode,
+ result: Error!os.FileHandle,
+
+ pub const Error = os.File.OpenError;
+ };
+
+ pub const WriteFile = struct {
+ /// must be null terminated. TODO https://github.com/ziglang/zig/issues/265
+ path: []const u8,
+ contents: []const u8,
+ mode: os.File.Mode,
+ result: Error!void,
+
+ pub const Error = os.File.OpenError || os.File.WriteError;
+ };
+
+ pub const Close = struct {
+ fd: os.FileHandle,
+ };
+ };
+};
+
+/// data - just the inner references - must live until pwritev promise completes.
+pub async fn pwritev(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {
+ switch (builtin.os) {
+ builtin.Os.macosx,
+ builtin.Os.linux,
+ => return await (async pwritevPosix(loop, fd, data, offset) catch unreachable),
+ builtin.Os.windows,
+ => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
+ else => @compileError("Unsupported OS"),
+ }
+}
+
+/// data - just the inner references - must live until pwritev promise completes.
+pub async fn pwritevWindows(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {
+ if (data.len == 0) return;
+ if (data.len == 1) return await (async pwriteWindows(loop, fd, data[0], offset) catch unreachable);
+
+ const data_copy = try std.mem.dupe(loop.allocator, []const u8, data);
+ defer loop.allocator.free(data_copy);
+
+ // TODO do these in parallel
+ var off = offset;
+ for (data_copy) |buf| {
+ try await (async pwriteWindows(loop, fd, buf, off) catch unreachable);
+ off += buf.len;
+ }
+}
+
+pub async fn pwriteWindows(loop: *Loop, fd: os.FileHandle, data: []const u8, offset: u64) os.WindowsWriteError!void {
+ // workaround for https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ var resume_node = Loop.ResumeNode.Basic{
+ .base = Loop.ResumeNode{
+ .id = Loop.ResumeNode.Id.Basic,
+ .handle = @handle(),
+ },
+ };
+ const completion_key = @ptrToInt(&resume_node.base);
+ // TODO support concurrent async ops on the file handle
+ // we can do this by ignoring completion key and using @fieldParentPtr with the *Overlapped
+ _ = try os.windowsCreateIoCompletionPort(fd, loop.os_data.io_port, completion_key, undefined);
+ var overlapped = windows.OVERLAPPED{
+ .Internal = 0,
+ .InternalHigh = 0,
+ .Offset = @truncate(u32, offset),
+ .OffsetHigh = @truncate(u32, offset >> 32),
+ .hEvent = null,
+ };
+ loop.beginOneEvent();
+ errdefer loop.finishOneEvent();
+
+ errdefer {
+ _ = windows.CancelIoEx(fd, &overlapped);
+ }
+ suspend {
+ _ = windows.WriteFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &overlapped);
+ }
+ var bytes_transferred: windows.DWORD = undefined;
+ if (windows.GetOverlappedResult(fd, &overlapped, &bytes_transferred, windows.FALSE) == 0) {
+ const err = windows.GetLastError();
+ return switch (err) {
+ windows.ERROR.IO_PENDING => unreachable,
+ windows.ERROR.INVALID_USER_BUFFER => error.SystemResources,
+ windows.ERROR.NOT_ENOUGH_MEMORY => error.SystemResources,
+ windows.ERROR.OPERATION_ABORTED => error.OperationAborted,
+ windows.ERROR.NOT_ENOUGH_QUOTA => error.SystemResources,
+ windows.ERROR.BROKEN_PIPE => error.BrokenPipe,
+ else => os.unexpectedErrorWindows(err),
+ };
+ }
+}
+
+
+/// data - just the inner references - must live until pwritev promise completes.
+pub async fn pwritevPosix(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {
+ // workaround for https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ const iovecs = try loop.allocator.alloc(os.posix.iovec_const, data.len);
+ defer loop.allocator.free(iovecs);
+
+ for (data) |buf, i| {
+ iovecs[i] = os.posix.iovec_const{
+ .iov_base = buf.ptr,
+ .iov_len = buf.len,
+ };
+ }
+
+ var req_node = RequestNode{
+ .prev = null,
+ .next = null,
+ .data = Request{
+ .msg = Request.Msg{
+ .PWriteV = Request.Msg.PWriteV{
+ .fd = fd,
+ .iov = iovecs,
+ .offset = offset,
+ .result = undefined,
+ },
+ },
+ .finish = Request.Finish{
+ .TickNode = Loop.NextTickNode{
+ .prev = null,
+ .next = null,
+ .data = @handle(),
+ },
+ },
+ },
+ };
+
+ errdefer loop.posixFsCancel(&req_node);
+
+ suspend {
+ loop.posixFsRequest(&req_node);
+ }
+
+ return req_node.data.msg.PWriteV.result;
+}
+
+/// data - just the inner references - must live until preadv promise completes.
+pub async fn preadv(loop: *Loop, fd: os.FileHandle, data: []const []u8, offset: usize) !usize {
+ assert(data.len != 0);
+ switch (builtin.os) {
+ builtin.Os.macosx,
+ builtin.Os.linux,
+ => return await (async preadvPosix(loop, fd, data, offset) catch unreachable),
+ builtin.Os.windows,
+ => return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
+ else => @compileError("Unsupported OS"),
+ }
+}
+
+pub async fn preadvWindows(loop: *Loop, fd: os.FileHandle, data: []const []u8, offset: u64) !usize {
+ assert(data.len != 0);
+ if (data.len == 1) return await (async preadWindows(loop, fd, data[0], offset) catch unreachable);
+
+ const data_copy = try std.mem.dupe(loop.allocator, []u8, data);
+ defer loop.allocator.free(data_copy);
+
+ // TODO do these in parallel?
+ var off: usize = 0;
+ var iov_i: usize = 0;
+ var inner_off: usize = 0;
+ while (true) {
+ const v = data_copy[iov_i];
+ const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len-inner_off], offset + off) catch unreachable);
+ off += amt_read;
+ inner_off += amt_read;
+ if (inner_off == v.len) {
+ iov_i += 1;
+ inner_off = 0;
+ if (iov_i == data_copy.len) {
+ return off;
+ }
+ }
+ if (amt_read == 0) return off; // EOF
+ }
+}
+
+pub async fn preadWindows(loop: *Loop, fd: os.FileHandle, data: []u8, offset: u64) !usize {
+ // workaround for https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ var resume_node = Loop.ResumeNode.Basic{
+ .base = Loop.ResumeNode{
+ .id = Loop.ResumeNode.Id.Basic,
+ .handle = @handle(),
+ },
+ };
+ const completion_key = @ptrToInt(&resume_node.base);
+ // TODO support concurrent async ops on the file handle
+ // we can do this by ignoring completion key and using @fieldParentPtr with the *Overlapped
+ _ = try os.windowsCreateIoCompletionPort(fd, loop.os_data.io_port, completion_key, undefined);
+ var overlapped = windows.OVERLAPPED{
+ .Internal = 0,
+ .InternalHigh = 0,
+ .Offset = @truncate(u32, offset),
+ .OffsetHigh = @truncate(u32, offset >> 32),
+ .hEvent = null,
+ };
+ loop.beginOneEvent();
+ errdefer loop.finishOneEvent();
+
+ errdefer {
+ _ = windows.CancelIoEx(fd, &overlapped);
+ }
+ suspend {
+ _ = windows.ReadFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &overlapped);
+ }
+ var bytes_transferred: windows.DWORD = undefined;
+ if (windows.GetOverlappedResult(fd, &overlapped, &bytes_transferred, windows.FALSE) == 0) {
+ const err = windows.GetLastError();
+ return switch (err) {
+ windows.ERROR.IO_PENDING => unreachable,
+ windows.ERROR.OPERATION_ABORTED => error.OperationAborted,
+ windows.ERROR.BROKEN_PIPE => error.BrokenPipe,
+ else => os.unexpectedErrorWindows(err),
+ };
+ }
+ return usize(bytes_transferred);
+}
+
+/// data - just the inner references - must live until preadv promise completes.
+pub async fn preadvPosix(loop: *Loop, fd: os.FileHandle, data: []const []u8, offset: usize) !usize {
+ // workaround for https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ const iovecs = try loop.allocator.alloc(os.posix.iovec, data.len);
+ defer loop.allocator.free(iovecs);
+
+ for (data) |buf, i| {
+ iovecs[i] = os.posix.iovec{
+ .iov_base = buf.ptr,
+ .iov_len = buf.len,
+ };
+ }
+
+ var req_node = RequestNode{
+ .prev = null,
+ .next = null,
+ .data = Request{
+ .msg = Request.Msg{
+ .PReadV = Request.Msg.PReadV{
+ .fd = fd,
+ .iov = iovecs,
+ .offset = offset,
+ .result = undefined,
+ },
+ },
+ .finish = Request.Finish{
+ .TickNode = Loop.NextTickNode{
+ .prev = null,
+ .next = null,
+ .data = @handle(),
+ },
+ },
+ },
+ };
+
+ errdefer loop.posixFsCancel(&req_node);
+
+ suspend {
+ loop.posixFsRequest(&req_node);
+ }
+
+ return req_node.data.msg.PReadV.result;
+}
+
+pub async fn openPosix(
+ loop: *Loop,
+ path: []const u8,
+ flags: u32,
+ mode: os.File.Mode,
+) os.File.OpenError!os.FileHandle {
+ // workaround for https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
+ defer loop.allocator.free(path_with_null);
+
+ var req_node = RequestNode{
+ .prev = null,
+ .next = null,
+ .data = Request{
+ .msg = Request.Msg{
+ .Open = Request.Msg.Open{
+ .path = path_with_null[0..path.len],
+ .flags = flags,
+ .mode = mode,
+ .result = undefined,
+ },
+ },
+ .finish = Request.Finish{
+ .TickNode = Loop.NextTickNode{
+ .prev = null,
+ .next = null,
+ .data = @handle(),
+ },
+ },
+ },
+ };
+
+ errdefer loop.posixFsCancel(&req_node);
+
+ suspend {
+ loop.posixFsRequest(&req_node);
+ }
+
+ return req_node.data.msg.Open.result;
+}
+
+pub async fn openRead(loop: *Loop, path: []const u8) os.File.OpenError!os.FileHandle {
+ switch (builtin.os) {
+ builtin.Os.macosx, builtin.Os.linux => {
+ const flags = posix.O_LARGEFILE | posix.O_RDONLY | posix.O_CLOEXEC;
+ return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable);
+ },
+
+ builtin.Os.windows => return os.windowsOpen(
+ loop.allocator,
+ path,
+ windows.GENERIC_READ,
+ windows.FILE_SHARE_READ,
+ windows.OPEN_EXISTING,
+ windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
+ ),
+
+ else => @compileError("Unsupported OS"),
+ }
+}
+
+/// Creates if does not exist. Truncates the file if it exists.
+/// Uses the default mode.
+pub async fn openWrite(loop: *Loop, path: []const u8) os.File.OpenError!os.FileHandle {
+ return await (async openWriteMode(loop, path, os.File.default_mode) catch unreachable);
+}
+
+/// Creates if does not exist. Truncates the file if it exists.
+pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: os.File.Mode) os.File.OpenError!os.FileHandle {
+ switch (builtin.os) {
+ builtin.Os.macosx,
+ builtin.Os.linux,
+ => {
+ const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
+ return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable);
+ },
+ builtin.Os.windows,
+ => return os.windowsOpen(
+ loop.allocator,
+ path,
+ windows.GENERIC_WRITE,
+ windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
+ windows.CREATE_ALWAYS,
+ windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
+ ),
+ else => @compileError("Unsupported OS"),
+ }
+}
+
+/// Creates if does not exist. Does not truncate.
+pub async fn openReadWrite(
+ loop: *Loop,
+ path: []const u8,
+ mode: os.File.Mode,
+) os.File.OpenError!os.FileHandle {
+ switch (builtin.os) {
+ builtin.Os.macosx, builtin.Os.linux => {
+ const flags = posix.O_LARGEFILE | posix.O_RDWR | posix.O_CREAT | posix.O_CLOEXEC;
+ return await (async openPosix(loop, path, flags, mode) catch unreachable);
+ },
+
+ builtin.Os.windows => return os.windowsOpen(
+ loop.allocator,
+ path,
+ windows.GENERIC_WRITE|windows.GENERIC_READ,
+ windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
+ windows.OPEN_ALWAYS,
+ windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
+ ),
+
+ else => @compileError("Unsupported OS"),
+ }
+}
+
+/// This abstraction helps to close file handles in defer expressions
+/// without the possibility of failure and without the use of suspend points.
+/// Start a `CloseOperation` before opening a file, so that you can defer
+/// `CloseOperation.finish`.
+/// If you call `setHandle` then finishing will close the fd; otherwise finishing
+/// will deallocate the `CloseOperation`.
+pub const CloseOperation = struct {
+ loop: *Loop,
+ os_data: OsData,
+
+ const OsData = switch (builtin.os) {
+ builtin.Os.linux, builtin.Os.macosx => OsDataPosix,
+
+ builtin.Os.windows => struct {
+ handle: ?os.FileHandle,
+ },
+
+ else => @compileError("Unsupported OS"),
+ };
+
+ const OsDataPosix = struct {
+ have_fd: bool,
+ close_req_node: RequestNode,
+ };
+
+ pub fn start(loop: *Loop) (error{OutOfMemory}!*CloseOperation) {
+ const self = try loop.allocator.createOne(CloseOperation);
+ self.* = CloseOperation{
+ .loop = loop,
+ .os_data = switch (builtin.os) {
+ builtin.Os.linux, builtin.Os.macosx => initOsDataPosix(self),
+ builtin.Os.windows => OsData{ .handle = null },
+ else => @compileError("Unsupported OS"),
+ },
+ };
+ return self;
+ }
+
+ fn initOsDataPosix(self: *CloseOperation) OsData {
+ return OsData{
+ .have_fd = false,
+ .close_req_node = RequestNode{
+ .prev = null,
+ .next = null,
+ .data = Request{
+ .msg = Request.Msg{
+ .Close = Request.Msg.Close{ .fd = undefined },
+ },
+ .finish = Request.Finish{ .DeallocCloseOperation = self },
+ },
+ },
+ };
+ }
+
+ /// Defer this after creating.
+ pub fn finish(self: *CloseOperation) void {
+ switch (builtin.os) {
+ builtin.Os.linux,
+ builtin.Os.macosx,
+ => {
+ if (self.os_data.have_fd) {
+ self.loop.posixFsRequest(&self.os_data.close_req_node);
+ } else {
+ self.loop.allocator.destroy(self);
+ }
+ },
+ builtin.Os.windows,
+ => {
+ if (self.os_data.handle) |handle| {
+ os.close(handle);
+ }
+ self.loop.allocator.destroy(self);
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+
+ pub fn setHandle(self: *CloseOperation, handle: os.FileHandle) void {
+ switch (builtin.os) {
+ builtin.Os.linux,
+ builtin.Os.macosx,
+ => {
+ self.os_data.close_req_node.data.msg.Close.fd = handle;
+ self.os_data.have_fd = true;
+ },
+ builtin.Os.windows,
+ => {
+ self.os_data.handle = handle;
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+
+ /// Undo a `setHandle`.
+ pub fn clearHandle(self: *CloseOperation) void {
+ switch (builtin.os) {
+ builtin.Os.linux,
+ builtin.Os.macosx,
+ => {
+ self.os_data.have_fd = false;
+ },
+ builtin.Os.windows,
+ => {
+ self.os_data.handle = null;
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+
+ pub fn getHandle(self: *CloseOperation) os.FileHandle {
+ switch (builtin.os) {
+ builtin.Os.linux,
+ builtin.Os.macosx,
+ => {
+ assert(self.os_data.have_fd);
+ return self.os_data.close_req_node.data.msg.Close.fd;
+ },
+ builtin.Os.windows,
+ => {
+ return self.os_data.handle.?;
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+};
+
+/// contents must remain alive until writeFile completes.
+/// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate
+pub async fn writeFile(loop: *Loop, path: []const u8, contents: []const u8) !void {
+ return await (async writeFileMode(loop, path, contents, os.File.default_mode) catch unreachable);
+}
+
+/// contents must remain alive until writeFile completes.
+pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8, mode: os.File.Mode) !void {
+ switch (builtin.os) {
+ builtin.Os.linux,
+ builtin.Os.macosx,
+ => return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable),
+ builtin.Os.windows,
+ => return await (async writeFileWindows(loop, path, contents) catch unreachable),
+ else => @compileError("Unsupported OS"),
+ }
+}
+
+async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {
+ const handle = try os.windowsOpen(
+ loop.allocator,
+ path,
+ windows.GENERIC_WRITE,
+ windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
+ windows.CREATE_ALWAYS,
+ windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
+ );
+ defer os.close(handle);
+
+ try await (async pwriteWindows(loop, handle, contents, 0) catch unreachable);
+}
+
+async fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode: os.File.Mode) !void {
+ // workaround for https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
+ defer loop.allocator.free(path_with_null);
+
+ var req_node = RequestNode{
+ .prev = null,
+ .next = null,
+ .data = Request{
+ .msg = Request.Msg{
+ .WriteFile = Request.Msg.WriteFile{
+ .path = path_with_null[0..path.len],
+ .contents = contents,
+ .mode = mode,
+ .result = undefined,
+ },
+ },
+ .finish = Request.Finish{
+ .TickNode = Loop.NextTickNode{
+ .prev = null,
+ .next = null,
+ .data = @handle(),
+ },
+ },
+ },
+ };
+
+ errdefer loop.posixFsCancel(&req_node);
+
+ suspend {
+ loop.posixFsRequest(&req_node);
+ }
+
+ return req_node.data.msg.WriteFile.result;
+}
+
+/// The promise resumes when the last data has been confirmed written, but before the file handle
+/// is closed.
+/// Caller owns returned memory.
+pub async fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 {
+ var close_op = try CloseOperation.start(loop);
+ defer close_op.finish();
+
+ const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path);
+ defer loop.allocator.free(path_with_null);
+
+ const fd = try await (async openRead(loop, path_with_null[0..file_path.len]) catch unreachable);
+ close_op.setHandle(fd);
+
+ var list = std.ArrayList(u8).init(loop.allocator);
+ defer list.deinit();
+
+ while (true) {
+ try list.ensureCapacity(list.len + os.page_size);
+ const buf = list.items[list.len..];
+ const buf_array = [][]u8{buf};
+ const amt = try await (async preadv(loop, fd, buf_array, list.len) catch unreachable);
+ list.len += amt;
+ if (list.len > max_size) {
+ return error.FileTooBig;
+ }
+ if (amt < buf.len) {
+ return list.toOwnedSlice();
+ }
+ }
+}
+
+pub const WatchEventId = enum {
+ CloseWrite,
+ Delete,
+};
+
+pub const WatchEventError = error{
+ UserResourceLimitReached,
+ SystemResources,
+ AccessDenied,
+ Unexpected, // TODO remove this possibility
+};
+
+pub fn Watch(comptime V: type) type {
+ return struct {
+ channel: *event.Channel(Event.Error!Event),
+ os_data: OsData,
+
+ const OsData = switch (builtin.os) {
+ builtin.Os.macosx => struct {
+ file_table: FileTable,
+ table_lock: event.Lock,
+
+ const FileTable = std.AutoHashMap([]const u8, *Put);
+ const Put = struct {
+ putter: promise,
+ value_ptr: *V,
+ };
+ },
+
+ builtin.Os.linux => LinuxOsData,
+ builtin.Os.windows => WindowsOsData,
+
+ else => @compileError("Unsupported OS"),
+ };
+
+ const WindowsOsData = struct {
+ table_lock: event.Lock,
+ dir_table: DirTable,
+ all_putters: std.atomic.Queue(promise),
+ ref_count: std.atomic.Int(usize),
+
+ const DirTable = std.AutoHashMap([]const u8, *Dir);
+ const FileTable = std.AutoHashMap([]const u16, V);
+
+ const Dir = struct {
+ putter: promise,
+ file_table: FileTable,
+ table_lock: event.Lock,
+ };
+ };
+
+ const LinuxOsData = struct {
+ putter: promise,
+ inotify_fd: i32,
+ wd_table: WdTable,
+ table_lock: event.Lock,
+
+ const WdTable = std.AutoHashMap(i32, Dir);
+ const FileTable = std.AutoHashMap([]const u8, V);
+
+ const Dir = struct {
+ dirname: []const u8,
+ file_table: FileTable,
+ };
+ };
+
+ const FileToHandle = std.AutoHashMap([]const u8, promise);
+
+ const Self = this;
+
+ pub const Event = struct {
+ id: Id,
+ data: V,
+
+ pub const Id = WatchEventId;
+ pub const Error = WatchEventError;
+ };
+
+ pub fn create(loop: *Loop, event_buf_count: usize) !*Self {
+ const channel = try event.Channel(Self.Event.Error!Self.Event).create(loop, event_buf_count);
+ errdefer channel.destroy();
+
+ switch (builtin.os) {
+ builtin.Os.linux => {
+ const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
+ errdefer os.close(inotify_fd);
+
+ var result: *Self = undefined;
+ _ = try async<loop.allocator> linuxEventPutter(inotify_fd, channel, &result);
+ return result;
+ },
+
+ builtin.Os.windows => {
+ const self = try loop.allocator.createOne(Self);
+ errdefer loop.allocator.destroy(self);
+ self.* = Self{
+ .channel = channel,
+ .os_data = OsData{
+ .table_lock = event.Lock.init(loop),
+ .dir_table = OsData.DirTable.init(loop.allocator),
+ .ref_count = std.atomic.Int(usize).init(1),
+ .all_putters = std.atomic.Queue(promise).init(),
+ },
+ };
+ return self;
+ },
+
+ builtin.Os.macosx => {
+ const self = try loop.allocator.createOne(Self);
+ errdefer loop.allocator.destroy(self);
+
+ self.* = Self{
+ .channel = channel,
+ .os_data = OsData{
+ .table_lock = event.Lock.init(loop),
+ .file_table = OsData.FileTable.init(loop.allocator),
+ },
+ };
+ return self;
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+
+ /// All addFile calls and removeFile calls must have completed.
+ pub fn destroy(self: *Self) void {
+ switch (builtin.os) {
+ builtin.Os.macosx => {
+ // TODO we need to cancel the coroutines before destroying the lock
+ self.os_data.table_lock.deinit();
+ var it = self.os_data.file_table.iterator();
+ while (it.next()) |entry| {
+ cancel entry.value.putter;
+ self.channel.loop.allocator.free(entry.key);
+ }
+ self.channel.destroy();
+ },
+ builtin.Os.linux => cancel self.os_data.putter,
+ builtin.Os.windows => {
+ while (self.os_data.all_putters.get()) |putter_node| {
+ cancel putter_node.data;
+ }
+ self.deref();
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+
+ fn ref(self: *Self) void {
+ _ = self.os_data.ref_count.incr();
+ }
+
+ fn deref(self: *Self) void {
+ if (self.os_data.ref_count.decr() == 1) {
+ const allocator = self.channel.loop.allocator;
+ self.os_data.table_lock.deinit();
+ var it = self.os_data.dir_table.iterator();
+ while (it.next()) |entry| {
+ allocator.free(entry.key);
+ allocator.destroy(entry.value);
+ }
+ self.os_data.dir_table.deinit();
+ self.channel.destroy();
+ allocator.destroy(self);
+ }
+ }
+
+ pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
+ switch (builtin.os) {
+ builtin.Os.macosx => return await (async addFileMacosx(self, file_path, value) catch unreachable),
+ builtin.Os.linux => return await (async addFileLinux(self, file_path, value) catch unreachable),
+ builtin.Os.windows => return await (async addFileWindows(self, file_path, value) catch unreachable),
+ else => @compileError("Unsupported OS"),
+ }
+ }
+
+ async fn addFileMacosx(self: *Self, file_path: []const u8, value: V) !?V {
+ const resolved_path = try os.path.resolve(self.channel.loop.allocator, file_path);
+ var resolved_path_consumed = false;
+ defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path);
+
+ var close_op = try CloseOperation.start(self.channel.loop);
+ var close_op_consumed = false;
+ defer if (!close_op_consumed) close_op.finish();
+
+ const flags = posix.O_SYMLINK | posix.O_EVTONLY;
+ const mode = 0;
+ const fd = try await (async openPosix(self.channel.loop, resolved_path, flags, mode) catch unreachable);
+ close_op.setHandle(fd);
+
+ var put_data: *OsData.Put = undefined;
+ const putter = try async self.kqPutEvents(close_op, value, &put_data);
+ close_op_consumed = true;
+ errdefer cancel putter;
+
+ const result = blk: {
+ const held = await (async self.os_data.table_lock.acquire() catch unreachable);
+ defer held.release();
+
+ const gop = try self.os_data.file_table.getOrPut(resolved_path);
+ if (gop.found_existing) {
+ const prev_value = gop.kv.value.value_ptr.*;
+ cancel gop.kv.value.putter;
+ gop.kv.value = put_data;
+ break :blk prev_value;
+ } else {
+ resolved_path_consumed = true;
+ gop.kv.value = put_data;
+ break :blk null;
+ }
+ };
+
+ return result;
+ }
+
+ async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void {
+ // TODO https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ var value_copy = value;
+ var put = OsData.Put{
+ .putter = @handle(),
+ .value_ptr = &value_copy,
+ };
+ out_put.* = &put;
+ self.channel.loop.beginOneEvent();
+
+ defer {
+ close_op.finish();
+ self.channel.loop.finishOneEvent();
+ }
+
+ while (true) {
+ if (await (async self.channel.loop.bsdWaitKev(
+ @intCast(usize, close_op.getHandle()),
+ posix.EVFILT_VNODE,
+ posix.NOTE_WRITE | posix.NOTE_DELETE,
+ ) catch unreachable)) |kev| {
+ // TODO handle EV_ERROR
+ if (kev.fflags & posix.NOTE_DELETE != 0) {
+ await (async self.channel.put(Self.Event{
+ .id = Event.Id.Delete,
+ .data = value_copy,
+ }) catch unreachable);
+ } else if (kev.fflags & posix.NOTE_WRITE != 0) {
+ await (async self.channel.put(Self.Event{
+ .id = Event.Id.CloseWrite,
+ .data = value_copy,
+ }) catch unreachable);
+ }
+ } else |err| switch (err) {
+ error.EventNotFound => unreachable,
+ error.ProcessNotFound => unreachable,
+ error.AccessDenied, error.SystemResources => {
+ // TODO https://github.com/ziglang/zig/issues/769
+ const casted_err = @errSetCast(error{
+ AccessDenied,
+ SystemResources,
+ }, err);
+ await (async self.channel.put(casted_err) catch unreachable);
+ },
+ }
+ }
+ }
+
+ async fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
+ const value_copy = value;
+
+ const dirname = os.path.dirname(file_path) orelse ".";
+ const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname);
+ var dirname_with_null_consumed = false;
+ defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null);
+
+ const basename = os.path.basename(file_path);
+ const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename);
+ var basename_with_null_consumed = false;
+ defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null);
+
+ const wd = try os.linuxINotifyAddWatchC(
+ self.os_data.inotify_fd,
+ dirname_with_null.ptr,
+ os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
+ );
+ // wd is either a newly created watch or an existing one.
+
+ const held = await (async self.os_data.table_lock.acquire() catch unreachable);
+ defer held.release();
+
+ const gop = try self.os_data.wd_table.getOrPut(wd);
+ if (!gop.found_existing) {
+ gop.kv.value = OsData.Dir{
+ .dirname = dirname_with_null,
+ .file_table = OsData.FileTable.init(self.channel.loop.allocator),
+ };
+ dirname_with_null_consumed = true;
+ }
+ const dir = &gop.kv.value;
+
+ const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
+ if (file_table_gop.found_existing) {
+ const prev_value = file_table_gop.kv.value;
+ file_table_gop.kv.value = value_copy;
+ return prev_value;
+ } else {
+ file_table_gop.kv.value = value_copy;
+ basename_with_null_consumed = true;
+ return null;
+ }
+ }
+
+ async fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
+ const value_copy = value;
+ // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
+
+ const dirname = try std.mem.dupe(self.channel.loop.allocator, u8, os.path.dirname(file_path) orelse ".");
+ var dirname_consumed = false;
+ defer if (!dirname_consumed) self.channel.loop.allocator.free(dirname);
+
+ const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, dirname);
+ defer self.channel.loop.allocator.free(dirname_utf16le);
+
+ // TODO https://github.com/ziglang/zig/issues/265
+ const basename = os.path.basename(file_path);
+ const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
+ var basename_utf16le_null_consumed = false;
+ defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
+ const basename_utf16le_no_null = basename_utf16le_null[0..basename_utf16le_null.len-1];
+
+ const dir_handle = windows.CreateFileW(
+ dirname_utf16le.ptr,
+ windows.FILE_LIST_DIRECTORY,
+ windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
+ null,
+ windows.OPEN_EXISTING,
+ windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
+ null,
+ );
+ if (dir_handle == windows.INVALID_HANDLE_VALUE) {
+ const err = windows.GetLastError();
+ switch (err) {
+ windows.ERROR.FILE_NOT_FOUND,
+ windows.ERROR.PATH_NOT_FOUND,
+ => return error.PathNotFound,
+ else => return os.unexpectedErrorWindows(err),
+ }
+ }
+ var dir_handle_consumed = false;
+ defer if (!dir_handle_consumed) os.close(dir_handle);
+
+ const held = await (async self.os_data.table_lock.acquire() catch unreachable);
+ defer held.release();
+
+ const gop = try self.os_data.dir_table.getOrPut(dirname);
+ if (gop.found_existing) {
+ const dir = gop.kv.value;
+ const held_dir_lock = await (async dir.table_lock.acquire() catch unreachable);
+ defer held_dir_lock.release();
+
+ const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
+ if (file_gop.found_existing) {
+ const prev_value = file_gop.kv.value;
+ file_gop.kv.value = value_copy;
+ return prev_value;
+ } else {
+ file_gop.kv.value = value_copy;
+ basename_utf16le_null_consumed = true;
+ return null;
+ }
+ } else {
+ errdefer _ = self.os_data.dir_table.remove(dirname);
+ const dir = try self.channel.loop.allocator.createOne(OsData.Dir);
+ errdefer self.channel.loop.allocator.destroy(dir);
+
+ dir.* = OsData.Dir{
+ .file_table = OsData.FileTable.init(self.channel.loop.allocator),
+ .table_lock = event.Lock.init(self.channel.loop),
+ .putter = undefined,
+ };
+ gop.kv.value = dir;
+ assert((try dir.file_table.put(basename_utf16le_no_null, value_copy)) == null);
+ basename_utf16le_null_consumed = true;
+
+ dir.putter = try async self.windowsDirReader(dir_handle, dir);
+ dir_handle_consumed = true;
+
+ dirname_consumed = true;
+
+ return null;
+ }
+ }
+
+ async fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
+ // TODO https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ self.ref();
+ defer self.deref();
+
+ defer os.close(dir_handle);
+
+ var putter_node = std.atomic.Queue(promise).Node{
+ .data = @handle(),
+ .prev = null,
+ .next = null,
+ };
+ self.os_data.all_putters.put(&putter_node);
+ defer _ = self.os_data.all_putters.remove(&putter_node);
+
+ var resume_node = Loop.ResumeNode.Basic{
+ .base = Loop.ResumeNode{
+ .id = Loop.ResumeNode.Id.Basic,
+ .handle = @handle(),
+ },
+ };
+ const completion_key = @ptrToInt(&resume_node.base);
+ var overlapped = windows.OVERLAPPED{
+ .Internal = 0,
+ .InternalHigh = 0,
+ .Offset = 0,
+ .OffsetHigh = 0,
+ .hEvent = null,
+ };
+ var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
+
+ // TODO handle this error not in the channel but in the setup
+ _ = os.windowsCreateIoCompletionPort(
+ dir_handle, self.channel.loop.os_data.io_port, completion_key, undefined,
+ ) catch |err| {
+ await (async self.channel.put(err) catch unreachable);
+ return;
+ };
+
+ while (true) {
+ {
+ // TODO only 1 beginOneEvent for the whole coroutine
+ self.channel.loop.beginOneEvent();
+ errdefer self.channel.loop.finishOneEvent();
+ errdefer {
+ _ = windows.CancelIoEx(dir_handle, &overlapped);
+ }
+ suspend {
+ _ = windows.ReadDirectoryChangesW(
+ dir_handle,
+ &event_buf,
+ @intCast(windows.DWORD, event_buf.len),
+ windows.FALSE, // watch subtree
+ windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
+ windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
+ windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
+ windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
+ null, // number of bytes transferred (unused for async)
+ &overlapped,
+ null, // completion routine - unused because we use IOCP
+ );
+ }
+ }
+ var bytes_transferred: windows.DWORD = undefined;
+ if (windows.GetOverlappedResult(dir_handle, &overlapped, &bytes_transferred, windows.FALSE) == 0) {
+ const errno = windows.GetLastError();
+ const err = switch (errno) {
+ else => os.unexpectedErrorWindows(errno),
+ };
+ await (async self.channel.put(err) catch unreachable);
+ } else {
+ // can't use @bytesToSlice because of the special variable length name field
+ var ptr = event_buf[0..].ptr;
+ const end_ptr = ptr + bytes_transferred;
+ var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
+ while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
+ ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
+ const emit = switch (ev.Action) {
+ windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
+ windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
+ else => null,
+ };
+ if (emit) |id| {
+ const basename_utf16le = ([*]u16)(&ev.FileName)[0..ev.FileNameLength/2];
+ const user_value = blk: {
+ const held = await (async dir.table_lock.acquire() catch unreachable);
+ defer held.release();
+
+ if (dir.file_table.get(basename_utf16le)) |entry| {
+ break :blk entry.value;
+ } else {
+ break :blk null;
+ }
+ };
+ if (user_value) |v| {
+ await (async self.channel.put(Event{
+ .id = id,
+ .data = v,
+ }) catch unreachable);
+ }
+ }
+ if (ev.NextEntryOffset == 0) break;
+ }
+ }
+ }
+ }
+
+ pub async fn removeFile(self: *Self, file_path: []const u8) ?V {
+ @panic("TODO");
+ }
+
+ async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event.Error!Event), out_watch: **Self) void {
+ // TODO https://github.com/ziglang/zig/issues/1194
+ suspend {
+ resume @handle();
+ }
+
+ const loop = channel.loop;
+
+ var watch = Self{
+ .channel = channel,
+ .os_data = OsData{
+ .putter = @handle(),
+ .inotify_fd = inotify_fd,
+ .wd_table = OsData.WdTable.init(loop.allocator),
+ .table_lock = event.Lock.init(loop),
+ },
+ };
+ out_watch.* = &watch;
+
+ loop.beginOneEvent();
+
+ defer {
+ watch.os_data.table_lock.deinit();
+ var wd_it = watch.os_data.wd_table.iterator();
+ while (wd_it.next()) |wd_entry| {
+ var file_it = wd_entry.value.file_table.iterator();
+ while (file_it.next()) |file_entry| {
+ loop.allocator.free(file_entry.key);
+ }
+ loop.allocator.free(wd_entry.value.dirname);
+ }
+ loop.finishOneEvent();
+ os.close(inotify_fd);
+ channel.destroy();
+ }
+
+ var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
+
+ while (true) {
+ const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len);
+ const errno = os.linux.getErrno(rc);
+ switch (errno) {
+ 0 => {
+ // can't use @bytesToSlice because of the special variable length name field
+ var ptr = event_buf[0..].ptr;
+ const end_ptr = ptr + event_buf.len;
+ var ev: *os.linux.inotify_event = undefined;
+ while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) {
+ ev = @ptrCast(*os.linux.inotify_event, ptr);
+ if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
+ const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
+ const basename_with_null = basename_ptr[0 .. std.cstr.len(basename_ptr) + 1];
+ const user_value = blk: {
+ const held = await (async watch.os_data.table_lock.acquire() catch unreachable);
+ defer held.release();
+
+ const dir = &watch.os_data.wd_table.get(ev.wd).?.value;
+ if (dir.file_table.get(basename_with_null)) |entry| {
+ break :blk entry.value;
+ } else {
+ break :blk null;
+ }
+ };
+ if (user_value) |v| {
+ await (async channel.put(Event{
+ .id = WatchEventId.CloseWrite,
+ .data = v,
+ }) catch unreachable);
+ }
+ }
+ }
+ },
+ os.linux.EINTR => continue,
+ os.linux.EINVAL => unreachable,
+ os.linux.EFAULT => unreachable,
+ os.linux.EAGAIN => {
+ (await (async loop.linuxWaitFd(
+ inotify_fd,
+ os.linux.EPOLLET | os.linux.EPOLLIN,
+ ) catch unreachable)) catch |err| {
+ const transformed_err = switch (err) {
+ error.InvalidFileDescriptor => unreachable,
+ error.FileDescriptorAlreadyPresentInSet => unreachable,
+ error.InvalidSyscall => unreachable,
+ error.OperationCausesCircularLoop => unreachable,
+ error.FileDescriptorNotRegistered => unreachable,
+ error.SystemResources => error.SystemResources,
+ error.UserResourceLimitReached => error.UserResourceLimitReached,
+ error.FileDescriptorIncompatibleWithEpoll => unreachable,
+ error.Unexpected => unreachable,
+ };
+ await (async channel.put(transformed_err) catch unreachable);
+ };
+ },
+ else => unreachable,
+ }
+ }
+ }
+ };
+}
+
+const test_tmp_dir = "std_event_fs_test";
+
+test "write a file, watch it, write it again" {
+ var da = std.heap.DirectAllocator.init();
+ defer da.deinit();
+
+ const allocator = &da.allocator;
+
+ // TODO move this into event loop too
+ try os.makePath(allocator, test_tmp_dir);
+ defer os.deleteTree(allocator, test_tmp_dir) catch {};
+
+ var loop: Loop = undefined;
+ try loop.initMultiThreaded(allocator);
+ defer loop.deinit();
+
+ var result: error!void = error.ResultNeverWritten;
+ const handle = try async<allocator> testFsWatchCantFail(&loop, &result);
+ defer cancel handle;
+
+ loop.run();
+ return result;
+}
+
+async fn testFsWatchCantFail(loop: *Loop, result: *(error!void)) void {
+ result.* = await async testFsWatch(loop) catch unreachable;
+}
+
+async fn testFsWatch(loop: *Loop) !void {
+ const file_path = try os.path.join(loop.allocator, test_tmp_dir, "file.txt");
+ defer loop.allocator.free(file_path);
+
+ const contents =
+ \\line 1
+ \\line 2
+ ;
+ const line2_offset = 7;
+
+ // first just write then read the file
+ try await try async writeFile(loop, file_path, contents);
+
+ const read_contents = try await try async readFile(loop, file_path, 1024 * 1024);
+ assert(mem.eql(u8, read_contents, contents));
+
+ // now watch the file
+ var watch = try Watch(void).create(loop, 0);
+ defer watch.destroy();
+
+ assert((try await try async watch.addFile(file_path, {})) == null);
+
+ const ev = try async watch.channel.get();
+ var ev_consumed = false;
+ defer if (!ev_consumed) cancel ev;
+
+ // overwrite line 2
+ const fd = try await try async openReadWrite(loop, file_path, os.File.default_mode);
+ {
+ defer os.close(fd);
+
+ try await try async pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset);
+ }
+
+ ev_consumed = true;
+ switch ((try await ev).id) {
+ WatchEventId.CloseWrite => {},
+ WatchEventId.Delete => @panic("wrong event"),
+ }
+ const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024);
+ assert(mem.eql(u8, contents_updated,
+ \\line 1
+ \\lorem ipsum
+ ));
+
+ // TODO test deleting the file and then re-adding it. we should get events for both
+}
diff --git a/std/event/group.zig b/std/event/group.zig
index 6c7fc63699..2b5a517b2f 100644
--- a/std/event/group.zig
+++ b/std/event/group.zig
@@ -29,6 +29,17 @@ pub fn Group(comptime ReturnType: type) type {
};
}
+ /// Cancel all the outstanding promises. Can be called even if wait was already called.
+ pub fn deinit(self: *Self) void {
+ while (self.coro_stack.pop()) |node| {
+ cancel node.data;
+ }
+ while (self.alloc_stack.pop()) |node| {
+ cancel node.data;
+ self.lock.loop.allocator.destroy(node);
+ }
+ }
+
/// Add a promise to the group. Thread-safe.
pub fn add(self: *Self, handle: promise->ReturnType) (error{OutOfMemory}!void) {
const node = try self.lock.loop.allocator.create(Stack.Node{
@@ -88,7 +99,7 @@ pub fn Group(comptime ReturnType: type) type {
await node.data;
} else {
(await node.data) catch |err| {
- self.cancelAll();
+ self.deinit();
return err;
};
}
@@ -100,25 +111,12 @@ pub fn Group(comptime ReturnType: type) type {
await handle;
} else {
(await handle) catch |err| {
- self.cancelAll();
+ self.deinit();
return err;
};
}
}
}
-
- /// Cancel all the outstanding promises. May only be called if wait was never called.
- /// TODO These should be `cancelasync` not `cancel`.
- /// See https://github.com/ziglang/zig/issues/1261
- pub fn cancelAll(self: *Self) void {
- while (self.coro_stack.pop()) |node| {
- cancel node.data;
- }
- while (self.alloc_stack.pop()) |node| {
- cancel node.data;
- self.lock.loop.allocator.destroy(node);
- }
- }
};
}
diff --git a/std/event/lock.zig b/std/event/lock.zig
index c4cb1a3f0e..2ee9dc981f 100644
--- a/std/event/lock.zig
+++ b/std/event/lock.zig
@@ -9,6 +9,7 @@ const Loop = std.event.Loop;
/// Thread-safe async/await lock.
/// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and
/// are resumed when the lock is released, in order.
+/// Allows only one actor to hold the lock.
pub const Lock = struct {
loop: *Loop,
shared_bit: u8, // TODO make this a bool
@@ -90,13 +91,14 @@ pub const Lock = struct {
}
pub async fn acquire(self: *Lock) Held {
+ // TODO explicitly put this memory in the coroutine frame #1194
suspend {
- // TODO explicitly put this memory in the coroutine frame #1194
- var my_tick_node = Loop.NextTickNode{
- .data = @handle(),
- .next = undefined,
- };
+ resume @handle();
+ }
+ var my_tick_node = Loop.NextTickNode.init(@handle());
+ errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire
+ suspend {
self.queue.put(&my_tick_node);
// At this point, we are in the queue, so we might have already been resumed and this coroutine
@@ -146,6 +148,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void {
}
const handle1 = async lockRunner(lock) catch @panic("out of memory");
var tick_node1 = Loop.NextTickNode{
+ .prev = undefined,
.next = undefined,
.data = handle1,
};
@@ -153,6 +156,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void {
const handle2 = async lockRunner(lock) catch @panic("out of memory");
var tick_node2 = Loop.NextTickNode{
+ .prev = undefined,
.next = undefined,
.data = handle2,
};
@@ -160,6 +164,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void {
const handle3 = async lockRunner(lock) catch @panic("out of memory");
var tick_node3 = Loop.NextTickNode{
+ .prev = undefined,
.next = undefined,
.data = handle3,
};
diff --git a/std/event/loop.zig b/std/event/loop.zig
index 8b1b2e53db..733112549d 100644
--- a/std/event/loop.zig
+++ b/std/event/loop.zig
@@ -2,10 +2,12 @@ const std = @import("../index.zig");
const builtin = @import("builtin");
const assert = std.debug.assert;
const mem = std.mem;
-const posix = std.os.posix;
-const windows = std.os.windows;
const AtomicRmwOp = builtin.AtomicRmwOp;
const AtomicOrder = builtin.AtomicOrder;
+const fs = std.event.fs;
+const os = std.os;
+const posix = os.posix;
+const windows = os.windows;
pub const Loop = struct {
allocator: *mem.Allocator,
@@ -13,7 +15,7 @@ pub const Loop = struct {
os_data: OsData,
final_resume_node: ResumeNode,
pending_event_count: usize,
- extra_threads: []*std.os.Thread,
+ extra_threads: []*os.Thread,
// pre-allocated eventfds. all permanently active.
// this is how we send promises to be resumed on other threads.
@@ -50,6 +52,22 @@ pub const Loop = struct {
base: ResumeNode,
kevent: posix.Kevent,
};
+
+ pub const Basic = switch (builtin.os) {
+ builtin.Os.macosx => MacOsBasic,
+ builtin.Os.linux => struct {
+ base: ResumeNode,
+ },
+ builtin.Os.windows => struct {
+ base: ResumeNode,
+ },
+ else => @compileError("unsupported OS"),
+ };
+
+ const MacOsBasic = struct {
+ base: ResumeNode,
+ kev: posix.Kevent,
+ };
};
/// After initialization, call run().
@@ -65,7 +83,7 @@ pub const Loop = struct {
/// TODO copy elision / named return values so that the threads referencing *Loop
/// have the correct pointer value.
pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void {
- const core_count = try std.os.cpuCount(allocator);
+ const core_count = try os.cpuCount(allocator);
return self.initInternal(allocator, core_count);
}
@@ -92,7 +110,7 @@ pub const Loop = struct {
);
errdefer self.allocator.free(self.eventfd_resume_nodes);
- self.extra_threads = try self.allocator.alloc(*std.os.Thread, extra_thread_count);
+ self.extra_threads = try self.allocator.alloc(*os.Thread, extra_thread_count);
errdefer self.allocator.free(self.extra_threads);
try self.initOsData(extra_thread_count);
@@ -104,17 +122,30 @@ pub const Loop = struct {
self.allocator.free(self.extra_threads);
}
- const InitOsDataError = std.os.LinuxEpollCreateError || mem.Allocator.Error || std.os.LinuxEventFdError ||
- std.os.SpawnThreadError || std.os.LinuxEpollCtlError || std.os.BsdKEventError ||
- std.os.WindowsCreateIoCompletionPortError;
+ const InitOsDataError = os.LinuxEpollCreateError || mem.Allocator.Error || os.LinuxEventFdError ||
+ os.SpawnThreadError || os.LinuxEpollCtlError || os.BsdKEventError ||
+ os.WindowsCreateIoCompletionPortError;
const wakeup_bytes = []u8{0x1} ** 8;
fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
switch (builtin.os) {
builtin.Os.linux => {
+ self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
+ self.os_data.fs_queue_item = 0;
+ // we need another thread for the file system because Linux does not have an async
+ // file system I/O API.
+ self.os_data.fs_end_request = fs.RequestNode{
+ .prev = undefined,
+ .next = undefined,
+ .data = fs.Request{
+ .msg = fs.Request.Msg.End,
+ .finish = fs.Request.Finish.NoAction,
+ },
+ };
+
errdefer {
- while (self.available_eventfd_resume_nodes.pop()) |node| std.os.close(node.data.eventfd);
+ while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
}
for (self.eventfd_resume_nodes) |*eventfd_node| {
eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
@@ -123,7 +154,7 @@ pub const Loop = struct {
.id = ResumeNode.Id.EventFd,
.handle = undefined,
},
- .eventfd = try std.os.linuxEventFd(1, posix.EFD_CLOEXEC | posix.EFD_NONBLOCK),
+ .eventfd = try os.linuxEventFd(1, posix.EFD_CLOEXEC | posix.EFD_NONBLOCK),
.epoll_op = posix.EPOLL_CTL_ADD,
},
.next = undefined,
@@ -131,44 +162,62 @@ pub const Loop = struct {
self.available_eventfd_resume_nodes.push(eventfd_node);
}
- self.os_data.epollfd = try std.os.linuxEpollCreate(posix.EPOLL_CLOEXEC);
- errdefer std.os.close(self.os_data.epollfd);
+ self.os_data.epollfd = try os.linuxEpollCreate(posix.EPOLL_CLOEXEC);
+ errdefer os.close(self.os_data.epollfd);
- self.os_data.final_eventfd = try std.os.linuxEventFd(0, posix.EFD_CLOEXEC | posix.EFD_NONBLOCK);
- errdefer std.os.close(self.os_data.final_eventfd);
+ self.os_data.final_eventfd = try os.linuxEventFd(0, posix.EFD_CLOEXEC | posix.EFD_NONBLOCK);
+ errdefer os.close(self.os_data.final_eventfd);
self.os_data.final_eventfd_event = posix.epoll_event{
.events = posix.EPOLLIN,
.data = posix.epoll_data{ .ptr = @ptrToInt(&self.final_resume_node) },
};
- try std.os.linuxEpollCtl(
+ try os.linuxEpollCtl(
self.os_data.epollfd,
posix.EPOLL_CTL_ADD,
self.os_data.final_eventfd,
&self.os_data.final_eventfd_event,
);
+ self.os_data.fs_thread = try os.spawnThread(self, posixFsRun);
+ errdefer {
+ self.posixFsRequest(&self.os_data.fs_end_request);
+ self.os_data.fs_thread.wait();
+ }
+
var extra_thread_index: usize = 0;
errdefer {
// writing 8 bytes to an eventfd cannot fail
- std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
+ os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
while (extra_thread_index != 0) {
extra_thread_index -= 1;
self.extra_threads[extra_thread_index].wait();
}
}
while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
- self.extra_threads[extra_thread_index] = try std.os.spawnThread(self, workerRun);
+ self.extra_threads[extra_thread_index] = try os.spawnThread(self, workerRun);
}
},
builtin.Os.macosx => {
- self.os_data.kqfd = try std.os.bsdKQueue();
- errdefer std.os.close(self.os_data.kqfd);
-
- self.os_data.kevents = try self.allocator.alloc(posix.Kevent, extra_thread_count);
- errdefer self.allocator.free(self.os_data.kevents);
+ self.os_data.kqfd = try os.bsdKQueue();
+ errdefer os.close(self.os_data.kqfd);
+
+ self.os_data.fs_kqfd = try os.bsdKQueue();
+ errdefer os.close(self.os_data.fs_kqfd);
+
+ self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
+ // we need another thread for the file system because Darwin does not have an async
+ // file system I/O API.
+ self.os_data.fs_end_request = fs.RequestNode{
+ .prev = undefined,
+ .next = undefined,
+ .data = fs.Request{
+ .msg = fs.Request.Msg.End,
+ .finish = fs.Request.Finish.NoAction,
+ },
+ };
- const eventlist = ([*]posix.Kevent)(undefined)[0..0];
+ const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
for (self.eventfd_resume_nodes) |*eventfd_node, i| {
eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
@@ -191,18 +240,9 @@ pub const Loop = struct {
};
self.available_eventfd_resume_nodes.push(eventfd_node);
const kevent_array = (*[1]posix.Kevent)(&eventfd_node.data.kevent);
- _ = try std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null);
+ _ = try os.bsdKEvent(self.os_data.kqfd, kevent_array, empty_kevs, null);
eventfd_node.data.kevent.flags = posix.EV_CLEAR | posix.EV_ENABLE;
eventfd_node.data.kevent.fflags = posix.NOTE_TRIGGER;
- // this one is for waiting for events
- self.os_data.kevents[i] = posix.Kevent{
- .ident = i,
- .filter = posix.EVFILT_USER,
- .flags = 0,
- .fflags = 0,
- .data = 0,
- .udata = @ptrToInt(&eventfd_node.data.base),
- };
}
// Pre-add so that we cannot get error.SystemResources
@@ -215,31 +255,55 @@ pub const Loop = struct {
.data = 0,
.udata = @ptrToInt(&self.final_resume_node),
};
- const kevent_array = (*[1]posix.Kevent)(&self.os_data.final_kevent);
- _ = try std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null);
+ const final_kev_arr = (*[1]posix.Kevent)(&self.os_data.final_kevent);
+ _ = try os.bsdKEvent(self.os_data.kqfd, final_kev_arr, empty_kevs, null);
self.os_data.final_kevent.flags = posix.EV_ENABLE;
self.os_data.final_kevent.fflags = posix.NOTE_TRIGGER;
+ self.os_data.fs_kevent_wake = posix.Kevent{
+ .ident = 0,
+ .filter = posix.EVFILT_USER,
+ .flags = posix.EV_ADD | posix.EV_ENABLE,
+ .fflags = posix.NOTE_TRIGGER,
+ .data = 0,
+ .udata = undefined,
+ };
+
+ self.os_data.fs_kevent_wait = posix.Kevent{
+ .ident = 0,
+ .filter = posix.EVFILT_USER,
+ .flags = posix.EV_ADD | posix.EV_CLEAR,
+ .fflags = 0,
+ .data = 0,
+ .udata = undefined,
+ };
+
+ self.os_data.fs_thread = try os.spawnThread(self, posixFsRun);
+ errdefer {
+ self.posixFsRequest(&self.os_data.fs_end_request);
+ self.os_data.fs_thread.wait();
+ }
+
var extra_thread_index: usize = 0;
errdefer {
- _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch unreachable;
+ _ = os.bsdKEvent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;
while (extra_thread_index != 0) {
extra_thread_index -= 1;
self.extra_threads[extra_thread_index].wait();
}
}
while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
- self.extra_threads[extra_thread_index] = try std.os.spawnThread(self, workerRun);
+ self.extra_threads[extra_thread_index] = try os.spawnThread(self, workerRun);
}
},
builtin.Os.windows => {
- self.os_data.io_port = try std.os.windowsCreateIoCompletionPort(
+ self.os_data.io_port = try os.windowsCreateIoCompletionPort(
windows.INVALID_HANDLE_VALUE,
null,
undefined,
- undefined,
+ @maxValue(windows.DWORD),
);
- errdefer std.os.close(self.os_data.io_port);
+ errdefer os.close(self.os_data.io_port);
for (self.eventfd_resume_nodes) |*eventfd_node, i| {
eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
@@ -262,7 +326,7 @@ pub const Loop = struct {
while (i < extra_thread_index) : (i += 1) {
while (true) {
const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
- std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
+ os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
break;
}
}
@@ -272,7 +336,7 @@ pub const Loop = struct {
}
}
while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
- self.extra_threads[extra_thread_index] = try std.os.spawnThread(self, workerRun);
+ self.extra_threads[extra_thread_index] = try os.spawnThread(self, workerRun);
}
},
else => {},
@@ -282,63 +346,113 @@ pub const Loop = struct {
fn deinitOsData(self: *Loop) void {
switch (builtin.os) {
builtin.Os.linux => {
- std.os.close(self.os_data.final_eventfd);
- while (self.available_eventfd_resume_nodes.pop()) |node| std.os.close(node.data.eventfd);
- std.os.close(self.os_data.epollfd);
+ os.close(self.os_data.final_eventfd);
+ while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
+ os.close(self.os_data.epollfd);
self.allocator.free(self.eventfd_resume_nodes);
},
builtin.Os.macosx => {
- self.allocator.free(self.os_data.kevents);
- std.os.close(self.os_data.kqfd);
+ os.close(self.os_data.kqfd);
+ os.close(self.os_data.fs_kqfd);
},
builtin.Os.windows => {
- std.os.close(self.os_data.io_port);
+ os.close(self.os_data.io_port);
},
else => {},
}
}
/// resume_node must live longer than the promise that it holds a reference to.
- pub fn addFd(self: *Loop, fd: i32, resume_node: *ResumeNode) !void {
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
- errdefer {
- self.finishOneEvent();
- }
- try self.modFd(
+ /// flags must contain EPOLLET
+ pub fn linuxAddFd(self: *Loop, fd: i32, resume_node: *ResumeNode, flags: u32) !void {
+ assert(flags & posix.EPOLLET == posix.EPOLLET);
+ self.beginOneEvent();
+ errdefer self.finishOneEvent();
+ try self.linuxModFd(
fd,
posix.EPOLL_CTL_ADD,
- std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT | std.os.linux.EPOLLET,
+ flags,
resume_node,
);
}
- pub fn modFd(self: *Loop, fd: i32, op: u32, events: u32, resume_node: *ResumeNode) !void {
- var ev = std.os.linux.epoll_event{
- .events = events,
- .data = std.os.linux.epoll_data{ .ptr = @ptrToInt(resume_node) },
+ pub fn linuxModFd(self: *Loop, fd: i32, op: u32, flags: u32, resume_node: *ResumeNode) !void {
+ assert(flags & posix.EPOLLET == posix.EPOLLET);
+ var ev = os.linux.epoll_event{
+ .events = flags,
+ .data = os.linux.epoll_data{ .ptr = @ptrToInt(resume_node) },
};
- try std.os.linuxEpollCtl(self.os_data.epollfd, op, fd, &ev);
+ try os.linuxEpollCtl(self.os_data.epollfd, op, fd, &ev);
}
- pub fn removeFd(self: *Loop, fd: i32) void {
- self.removeFdNoCounter(fd);
+ pub fn linuxRemoveFd(self: *Loop, fd: i32) void {
+ os.linuxEpollCtl(self.os_data.epollfd, os.linux.EPOLL_CTL_DEL, fd, undefined) catch {};
self.finishOneEvent();
}
- fn removeFdNoCounter(self: *Loop, fd: i32) void {
- std.os.linuxEpollCtl(self.os_data.epollfd, std.os.linux.EPOLL_CTL_DEL, fd, undefined) catch {};
+ pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
+ defer self.linuxRemoveFd(fd);
+ suspend {
+ // TODO explicitly put this memory in the coroutine frame #1194
+ var resume_node = ResumeNode.Basic{
+ .base = ResumeNode{
+ .id = ResumeNode.Id.Basic,
+ .handle = @handle(),
+ },
+ };
+ try self.linuxAddFd(fd, &resume_node.base, flags);
+ }
}
- pub async fn waitFd(self: *Loop, fd: i32) !void {
- defer self.removeFd(fd);
+ pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !posix.Kevent {
+ // TODO #1194
suspend {
- // TODO explicitly put this memory in the coroutine frame #1194
- var resume_node = ResumeNode{
+ resume @handle();
+ }
+ var resume_node = ResumeNode.Basic{
+ .base = ResumeNode{
.id = ResumeNode.Id.Basic,
.handle = @handle(),
- };
- try self.addFd(fd, &resume_node);
+ },
+ .kev = undefined,
+ };
+ defer self.bsdRemoveKev(ident, filter);
+ suspend {
+ try self.bsdAddKev(&resume_node, ident, filter, fflags);
}
+ return resume_node.kev;
+ }
+
+ /// resume_node must live longer than the promise that it holds a reference to.
+ pub fn bsdAddKev(self: *Loop, resume_node: *ResumeNode.Basic, ident: usize, filter: i16, fflags: u32) !void {
+ self.beginOneEvent();
+ errdefer self.finishOneEvent();
+ var kev = posix.Kevent{
+ .ident = ident,
+ .filter = filter,
+ .flags = posix.EV_ADD | posix.EV_ENABLE | posix.EV_CLEAR,
+ .fflags = fflags,
+ .data = 0,
+ .udata = @ptrToInt(&resume_node.base),
+ };
+ const kevent_array = (*[1]posix.Kevent)(&kev);
+ const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
+ _ = try os.bsdKEvent(self.os_data.kqfd, kevent_array, empty_kevs, null);
+ }
+
+ pub fn bsdRemoveKev(self: *Loop, ident: usize, filter: i16) void {
+ var kev = posix.Kevent{
+ .ident = ident,
+ .filter = filter,
+ .flags = posix.EV_DELETE,
+ .fflags = 0,
+ .data = 0,
+ .udata = 0,
+ };
+ const kevent_array = (*[1]posix.Kevent)(&kev);
+ const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
+ _ = os.bsdKEvent(self.os_data.kqfd, kevent_array, empty_kevs, null) catch undefined;
+ self.finishOneEvent();
}
fn dispatch(self: *Loop) void {
@@ -352,8 +466,8 @@ pub const Loop = struct {
switch (builtin.os) {
builtin.Os.macosx => {
const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent);
- const eventlist = ([*]posix.Kevent)(undefined)[0..0];
- _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch {
+ const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
+ _ = os.bsdKEvent(self.os_data.kqfd, kevent_array, empty_kevs, null) catch {
self.next_tick_queue.unget(next_tick_node);
self.available_eventfd_resume_nodes.push(resume_stack_node);
return;
@@ -361,9 +475,9 @@ pub const Loop = struct {
},
builtin.Os.linux => {
// the pending count is already accounted for
- const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT |
- std.os.linux.EPOLLET;
- self.modFd(
+ const epoll_events = posix.EPOLLONESHOT | os.linux.EPOLLIN | os.linux.EPOLLOUT |
+ os.linux.EPOLLET;
+ self.linuxModFd(
eventfd_node.eventfd,
eventfd_node.epoll_op,
epoll_events,
@@ -379,7 +493,7 @@ pub const Loop = struct {
// the consumer code can decide whether to read the completion key.
// it has to do this for normal I/O, so we match that behavior here.
const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
- std.os.windowsPostQueuedCompletionStatus(
+ os.windowsPostQueuedCompletionStatus(
self.os_data.io_port,
undefined,
eventfd_node.completion_key,
@@ -397,15 +511,29 @@ pub const Loop = struct {
/// Bring your own linked list node. This means it can't fail.
pub fn onNextTick(self: *Loop, node: *NextTickNode) void {
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+ self.beginOneEvent(); // finished in dispatch()
self.next_tick_queue.put(node);
self.dispatch();
}
+ pub fn cancelOnNextTick(self: *Loop, node: *NextTickNode) void {
+ if (self.next_tick_queue.remove(node)) {
+ self.finishOneEvent();
+ }
+ }
+
pub fn run(self: *Loop) void {
self.finishOneEvent(); // the reference we start with
self.workerRun();
+
+ switch (builtin.os) {
+ builtin.Os.linux,
+ builtin.Os.macosx,
+ => self.os_data.fs_thread.wait(),
+ else => {},
+ }
+
for (self.extra_threads) |extra_thread| {
extra_thread.wait();
}
@@ -420,6 +548,7 @@ pub const Loop = struct {
suspend {
handle.* = @handle();
var my_tick_node = Loop.NextTickNode{
+ .prev = undefined,
.next = undefined,
.data = @handle(),
};
@@ -441,6 +570,7 @@ pub const Loop = struct {
pub async fn yield(self: *Loop) void {
suspend {
var my_tick_node = Loop.NextTickNode{
+ .prev = undefined,
.next = undefined,
.data = @handle(),
};
@@ -448,20 +578,28 @@ pub const Loop = struct {
}
}
- fn finishOneEvent(self: *Loop) void {
- if (@atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) == 1) {
+ /// call finishOneEvent when done
+ pub fn beginOneEvent(self: *Loop) void {
+ _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+ }
+
+ pub fn finishOneEvent(self: *Loop) void {
+ const prev = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ if (prev == 1) {
// cause all the threads to stop
switch (builtin.os) {
builtin.Os.linux => {
+ self.posixFsRequest(&self.os_data.fs_end_request);
// writing 8 bytes to an eventfd cannot fail
- std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
+ os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
return;
},
builtin.Os.macosx => {
+ self.posixFsRequest(&self.os_data.fs_end_request);
const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent);
- const eventlist = ([*]posix.Kevent)(undefined)[0..0];
+ const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
// cannot fail because we already added it and this just enables it
- _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable;
+ _ = os.bsdKEvent(self.os_data.kqfd, final_kevent, empty_kevs, null) catch unreachable;
return;
},
builtin.Os.windows => {
@@ -469,7 +607,7 @@ pub const Loop = struct {
while (i < self.extra_threads.len + 1) : (i += 1) {
while (true) {
const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
- std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
+ os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
break;
}
}
@@ -492,8 +630,8 @@ pub const Loop = struct {
switch (builtin.os) {
builtin.Os.linux => {
// only process 1 event so we don't steal from other threads
- var events: [1]std.os.linux.epoll_event = undefined;
- const count = std.os.linuxEpollWait(self.os_data.epollfd, events[0..], -1);
+ var events: [1]os.linux.epoll_event = undefined;
+ const count = os.linuxEpollWait(self.os_data.epollfd, events[0..], -1);
for (events[0..count]) |ev| {
const resume_node = @intToPtr(*ResumeNode, ev.data.ptr);
const handle = resume_node.handle;
@@ -516,13 +654,17 @@ pub const Loop = struct {
},
builtin.Os.macosx => {
var eventlist: [1]posix.Kevent = undefined;
- const count = std.os.bsdKEvent(self.os_data.kqfd, self.os_data.kevents, eventlist[0..], null) catch unreachable;
+ const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
+ const count = os.bsdKEvent(self.os_data.kqfd, empty_kevs, eventlist[0..], null) catch unreachable;
for (eventlist[0..count]) |ev| {
const resume_node = @intToPtr(*ResumeNode, ev.udata);
const handle = resume_node.handle;
const resume_node_id = resume_node.id;
switch (resume_node_id) {
- ResumeNode.Id.Basic => {},
+ ResumeNode.Id.Basic => {
+ const basic_node = @fieldParentPtr(ResumeNode.Basic, "base", resume_node);
+ basic_node.kev = ev;
+ },
ResumeNode.Id.Stop => return,
ResumeNode.Id.EventFd => {
const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node);
@@ -541,9 +683,10 @@ pub const Loop = struct {
while (true) {
var nbytes: windows.DWORD = undefined;
var overlapped: ?*windows.OVERLAPPED = undefined;
- switch (std.os.windowsGetQueuedCompletionStatus(self.os_data.io_port, &nbytes, &completion_key, &overlapped, windows.INFINITE)) {
- std.os.WindowsWaitResult.Aborted => return,
- std.os.WindowsWaitResult.Normal => {},
+ switch (os.windowsGetQueuedCompletionStatus(self.os_data.io_port, &nbytes, &completion_key, &overlapped, windows.INFINITE)) {
+ os.WindowsWaitResult.Aborted => return,
+ os.WindowsWaitResult.Normal => {},
+ os.WindowsWaitResult.Cancelled => continue,
}
if (overlapped != null) break;
}
@@ -560,21 +703,101 @@ pub const Loop = struct {
},
}
resume handle;
- if (resume_node_id == ResumeNode.Id.EventFd) {
- self.finishOneEvent();
- }
+ self.finishOneEvent();
},
else => @compileError("unsupported OS"),
}
}
}
+ fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void {
+ self.beginOneEvent(); // finished in posixFsRun after processing the msg
+ self.os_data.fs_queue.put(request_node);
+ switch (builtin.os) {
+ builtin.Os.macosx => {
+ const fs_kevs = (*[1]posix.Kevent)(&self.os_data.fs_kevent_wake);
+ const empty_kevs = ([*]posix.Kevent)(undefined)[0..0];
+ _ = os.bsdKEvent(self.os_data.fs_kqfd, fs_kevs, empty_kevs, null) catch unreachable;
+ },
+ builtin.Os.linux => {
+ _ = @atomicRmw(u8, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
+ const rc = os.linux.futex_wake(@ptrToInt(&self.os_data.fs_queue_item), os.linux.FUTEX_WAKE, 1);
+ switch (os.linux.getErrno(rc)) {
+ 0 => {},
+ posix.EINVAL => unreachable,
+ else => unreachable,
+ }
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+
+ fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void {
+ if (self.os_data.fs_queue.remove(request_node)) {
+ self.finishOneEvent();
+ }
+ }
+
+ fn posixFsRun(self: *Loop) void {
+ while (true) {
+ if (builtin.os == builtin.Os.linux) {
+ _ = @atomicRmw(u8, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
+ }
+ while (self.os_data.fs_queue.get()) |node| {
+ switch (node.data.msg) {
+ @TagType(fs.Request.Msg).End => return,
+ @TagType(fs.Request.Msg).PWriteV => |*msg| {
+ msg.result = os.posix_pwritev(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset);
+ },
+ @TagType(fs.Request.Msg).PReadV => |*msg| {
+ msg.result = os.posix_preadv(msg.fd, msg.iov.ptr, msg.iov.len, msg.offset);
+ },
+ @TagType(fs.Request.Msg).Open => |*msg| {
+ msg.result = os.posixOpenC(msg.path.ptr, msg.flags, msg.mode);
+ },
+ @TagType(fs.Request.Msg).Close => |*msg| os.close(msg.fd),
+ @TagType(fs.Request.Msg).WriteFile => |*msg| blk: {
+ const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT |
+ posix.O_CLOEXEC | posix.O_TRUNC;
+ const fd = os.posixOpenC(msg.path.ptr, flags, msg.mode) catch |err| {
+ msg.result = err;
+ break :blk;
+ };
+ defer os.close(fd);
+ msg.result = os.posixWrite(fd, msg.contents);
+ },
+ }
+ switch (node.data.finish) {
+ @TagType(fs.Request.Finish).TickNode => |*tick_node| self.onNextTick(tick_node),
+ @TagType(fs.Request.Finish).DeallocCloseOperation => |close_op| {
+ self.allocator.destroy(close_op);
+ },
+ @TagType(fs.Request.Finish).NoAction => {},
+ }
+ self.finishOneEvent();
+ }
+ switch (builtin.os) {
+ builtin.Os.linux => {
+ const rc = os.linux.futex_wait(@ptrToInt(&self.os_data.fs_queue_item), os.linux.FUTEX_WAIT, 0, null);
+ switch (os.linux.getErrno(rc)) {
+ 0 => continue,
+ posix.EINTR => continue,
+ posix.EAGAIN => continue,
+ else => unreachable,
+ }
+ },
+ builtin.Os.macosx => {
+ const fs_kevs = (*[1]posix.Kevent)(&self.os_data.fs_kevent_wait);
+ var out_kevs: [1]posix.Kevent = undefined;
+ _ = os.bsdKEvent(self.os_data.fs_kqfd, fs_kevs, out_kevs[0..], null) catch unreachable;
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+ }
+
const OsData = switch (builtin.os) {
- builtin.Os.linux => struct {
- epollfd: i32,
- final_eventfd: i32,
- final_eventfd_event: std.os.linux.epoll_event,
- },
+ builtin.Os.linux => LinuxOsData,
builtin.Os.macosx => MacOsData,
builtin.Os.windows => struct {
io_port: windows.HANDLE,
@@ -586,7 +809,22 @@ pub const Loop = struct {
const MacOsData = struct {
kqfd: i32,
final_kevent: posix.Kevent,
- kevents: []posix.Kevent,
+ fs_kevent_wake: posix.Kevent,
+ fs_kevent_wait: posix.Kevent,
+ fs_thread: *os.Thread,
+ fs_kqfd: i32,
+ fs_queue: std.atomic.Queue(fs.Request),
+ fs_end_request: fs.RequestNode,
+ };
+
+ const LinuxOsData = struct {
+ epollfd: i32,
+ final_eventfd: i32,
+ final_eventfd_event: os.linux.epoll_event,
+ fs_thread: *os.Thread,
+ fs_queue_item: u8,
+ fs_queue: std.atomic.Queue(fs.Request),
+ fs_end_request: fs.RequestNode,
};
};
diff --git a/std/event/rwlock.zig b/std/event/rwlock.zig
new file mode 100644
index 0000000000..186c81eb76
--- /dev/null
+++ b/std/event/rwlock.zig
@@ -0,0 +1,296 @@
+const std = @import("../index.zig");
+const builtin = @import("builtin");
+const assert = std.debug.assert;
+const mem = std.mem;
+const AtomicRmwOp = builtin.AtomicRmwOp;
+const AtomicOrder = builtin.AtomicOrder;
+const Loop = std.event.Loop;
+
+/// Thread-safe async/await lock.
+/// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and
+/// are resumed when the lock is released, in order.
+/// Many readers can hold the lock at the same time; however locking for writing is exclusive.
+/// When a read lock is held, it will not be released until the reader queue is empty.
+/// When a write lock is held, it will not be released until the writer queue is empty.
+pub const RwLock = struct {
+ loop: *Loop,
+ shared_state: u8, // TODO make this an enum
+ writer_queue: Queue,
+ reader_queue: Queue,
+ writer_queue_empty_bit: u8, // TODO make this a bool
+ reader_queue_empty_bit: u8, // TODO make this a bool
+ reader_lock_count: usize,
+
+ const State = struct {
+ const Unlocked = 0;
+ const WriteLock = 1;
+ const ReadLock = 2;
+ };
+
+ const Queue = std.atomic.Queue(promise);
+
+ pub const HeldRead = struct {
+ lock: *RwLock,
+
+ pub fn release(self: HeldRead) void {
+ // If other readers still hold the lock, we're done.
+ if (@atomicRmw(usize, &self.lock.reader_lock_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) != 1) {
+ return;
+ }
+
+ _ = @atomicRmw(u8, &self.lock.reader_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
+ if (@cmpxchgStrong(u8, &self.lock.shared_state, State.ReadLock, State.Unlocked, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) {
+ // Didn't unlock. Someone else's problem.
+ return;
+ }
+
+ self.lock.commonPostUnlock();
+ }
+ };
+
+ pub const HeldWrite = struct {
+ lock: *RwLock,
+
+ pub fn release(self: HeldWrite) void {
+ // See if we can leave it locked for writing, and pass the lock to the next writer
+ // in the queue to grab the lock.
+ if (self.lock.writer_queue.get()) |node| {
+ self.lock.loop.onNextTick(node);
+ return;
+ }
+
+ // We need to release the write lock. Check if any readers are waiting to grab the lock.
+ if (@atomicLoad(u8, &self.lock.reader_queue_empty_bit, AtomicOrder.SeqCst) == 0) {
+ // Switch to a read lock.
+ _ = @atomicRmw(u8, &self.lock.shared_state, AtomicRmwOp.Xchg, State.ReadLock, AtomicOrder.SeqCst);
+ while (self.lock.reader_queue.get()) |node| {
+ self.lock.loop.onNextTick(node);
+ }
+ return;
+ }
+
+ _ = @atomicRmw(u8, &self.lock.writer_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
+ _ = @atomicRmw(u8, &self.lock.shared_state, AtomicRmwOp.Xchg, State.Unlocked, AtomicOrder.SeqCst);
+
+ self.lock.commonPostUnlock();
+ }
+ };
+
+ pub fn init(loop: *Loop) RwLock {
+ return RwLock{
+ .loop = loop,
+ .shared_state = State.Unlocked,
+ .writer_queue = Queue.init(),
+ .writer_queue_empty_bit = 1,
+ .reader_queue = Queue.init(),
+ .reader_queue_empty_bit = 1,
+ .reader_lock_count = 0,
+ };
+ }
+
+ /// Must be called when not locked. Not thread safe.
+ /// All calls to acquire() and release() must complete before calling deinit().
+ pub fn deinit(self: *RwLock) void {
+ assert(self.shared_state == State.Unlocked);
+ while (self.writer_queue.get()) |node| cancel node.data;
+ while (self.reader_queue.get()) |node| cancel node.data;
+ }
+
+ pub async fn acquireRead(self: *RwLock) HeldRead {
+ _ = @atomicRmw(usize, &self.reader_lock_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+
+ suspend {
+ // TODO explicitly put this memory in the coroutine frame #1194
+ var my_tick_node = Loop.NextTickNode{
+ .data = @handle(),
+ .prev = undefined,
+ .next = undefined,
+ };
+
+ self.reader_queue.put(&my_tick_node);
+
+ // At this point, we are in the reader_queue, so we might have already been resumed and this coroutine
+ // frame might be destroyed. For the rest of the suspend block we cannot access the coroutine frame.
+
+ // We set this bit so that later we can rely on the fact, that if reader_queue_empty_bit is 1,
+ // some actor will attempt to grab the lock.
+ _ = @atomicRmw(u8, &self.reader_queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
+
+ // Here we don't care if we are the one to do the locking or if it was already locked for reading.
+ const have_read_lock = if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst)) |old_state| old_state == State.ReadLock else true;
+ if (have_read_lock) {
+ // Give out all the read locks.
+ if (self.reader_queue.get()) |first_node| {
+ while (self.reader_queue.get()) |node| {
+ self.loop.onNextTick(node);
+ }
+ resume first_node.data;
+ }
+ }
+ }
+ return HeldRead{ .lock = self };
+ }
+
+ pub async fn acquireWrite(self: *RwLock) HeldWrite {
+ suspend {
+ // TODO explicitly put this memory in the coroutine frame #1194
+ var my_tick_node = Loop.NextTickNode{
+ .data = @handle(),
+ .prev = undefined,
+ .next = undefined,
+ };
+
+ self.writer_queue.put(&my_tick_node);
+
+ // At this point, we are in the writer_queue, so we might have already been resumed and this coroutine
+ // frame might be destroyed. For the rest of the suspend block we cannot access the coroutine frame.
+
+ // We set this bit so that later we can rely on the fact, that if writer_queue_empty_bit is 1,
+ // some actor will attempt to grab the lock.
+ _ = @atomicRmw(u8, &self.writer_queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
+
+ // Here we must be the one to acquire the write lock. It cannot already be locked.
+ if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) == null) {
+ // We now have a write lock.
+ if (self.writer_queue.get()) |node| {
+ // Whether this node is us or someone else, we tail resume it.
+ resume node.data;
+ }
+ }
+ }
+ return HeldWrite{ .lock = self };
+ }
+
+ fn commonPostUnlock(self: *RwLock) void {
+ while (true) {
+ // There might be a writer_queue item or a reader_queue item
+ // If we check and both are empty, we can be done, because the other actors will try to
+ // obtain the lock.
+ // But if there's a writer_queue item or a reader_queue item,
+ // we are the actor which must loop and attempt to grab the lock again.
+ if (@atomicLoad(u8, &self.writer_queue_empty_bit, AtomicOrder.SeqCst) == 0) {
+ if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) {
+ // We did not obtain the lock. Great, the queues are someone else's problem.
+ return;
+ }
+ // If there's an item in the writer queue, give them the lock, and we're done.
+ if (self.writer_queue.get()) |node| {
+ self.loop.onNextTick(node);
+ return;
+ }
+ // Release the lock again.
+ _ = @atomicRmw(u8, &self.writer_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
+ _ = @atomicRmw(u8, &self.shared_state, AtomicRmwOp.Xchg, State.Unlocked, AtomicOrder.SeqCst);
+ continue;
+ }
+
+ if (@atomicLoad(u8, &self.reader_queue_empty_bit, AtomicOrder.SeqCst) == 0) {
+ if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) {
+ // We did not obtain the lock. Great, the queues are someone else's problem.
+ return;
+ }
+ // If there are any items in the reader queue, give out all the reader locks, and we're done.
+ if (self.reader_queue.get()) |first_node| {
+ self.loop.onNextTick(first_node);
+ while (self.reader_queue.get()) |node| {
+ self.loop.onNextTick(node);
+ }
+ return;
+ }
+ // Release the lock again.
+ _ = @atomicRmw(u8, &self.reader_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
+ if (@cmpxchgStrong(u8, &self.shared_state, State.ReadLock, State.Unlocked, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) {
+ // Didn't unlock. Someone else's problem.
+ return;
+ }
+ continue;
+ }
+ return;
+ }
+ }
+};
+
+test "std.event.RwLock" {
+ var da = std.heap.DirectAllocator.init();
+ defer da.deinit();
+
+ const allocator = &da.allocator;
+
+ var loop: Loop = undefined;
+ try loop.initMultiThreaded(allocator);
+ defer loop.deinit();
+
+ var lock = RwLock.init(&loop);
+ defer lock.deinit();
+
+ const handle = try async<allocator> testLock(&loop, &lock);
+ defer cancel handle;
+ loop.run();
+
+ const expected_result = [1]i32{shared_it_count * @intCast(i32, shared_test_data.len)} ** shared_test_data.len;
+ assert(mem.eql(i32, shared_test_data, expected_result));
+}
+
+async fn testLock(loop: *Loop, lock: *RwLock) void {
+ // TODO explicitly put next tick node memory in the coroutine frame #1194
+ suspend {
+ resume @handle();
+ }
+
+ var read_nodes: [100]Loop.NextTickNode = undefined;
+ for (read_nodes) |*read_node| {
+ read_node.data = async readRunner(lock) catch @panic("out of memory");
+ loop.onNextTick(read_node);
+ }
+
+ var write_nodes: [shared_it_count]Loop.NextTickNode = undefined;
+ for (write_nodes) |*write_node| {
+ write_node.data = async writeRunner(lock) catch @panic("out of memory");
+ loop.onNextTick(write_node);
+ }
+
+ for (write_nodes) |*write_node| {
+ await @ptrCast(promise->void, write_node.data);
+ }
+ for (read_nodes) |*read_node| {
+ await @ptrCast(promise->void, read_node.data);
+ }
+}
+
+const shared_it_count = 10;
+var shared_test_data = [1]i32{0} ** 10;
+var shared_test_index: usize = 0;
+var shared_count: usize = 0;
+
+async fn writeRunner(lock: *RwLock) void {
+ suspend; // resumed by onNextTick
+
+ var i: usize = 0;
+ while (i < shared_test_data.len) : (i += 1) {
+ std.os.time.sleep(0, 100000);
+ const lock_promise = async lock.acquireWrite() catch @panic("out of memory");
+ const handle = await lock_promise;
+ defer handle.release();
+
+ shared_count += 1;
+ while (shared_test_index < shared_test_data.len) : (shared_test_index += 1) {
+ shared_test_data[shared_test_index] = shared_test_data[shared_test_index] + 1;
+ }
+ shared_test_index = 0;
+ }
+}
+
+async fn readRunner(lock: *RwLock) void {
+ suspend; // resumed by onNextTick
+ std.os.time.sleep(0, 1);
+
+ var i: usize = 0;
+ while (i < shared_test_data.len) : (i += 1) {
+ const lock_promise = async lock.acquireRead() catch @panic("out of memory");
+ const handle = await lock_promise;
+ defer handle.release();
+
+ assert(shared_test_index == 0);
+ assert(shared_test_data[i] == @intCast(i32, shared_count));
+ }
+}
diff --git a/std/event/rwlocked.zig b/std/event/rwlocked.zig
new file mode 100644
index 0000000000..ef7e83d20c
--- /dev/null
+++ b/std/event/rwlocked.zig
@@ -0,0 +1,58 @@
+const std = @import("../index.zig");
+const RwLock = std.event.RwLock;
+const Loop = std.event.Loop;
+
+/// Thread-safe async/await RW lock that protects one piece of data.
+/// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and
+/// are resumed when the lock is released, in order.
+pub fn RwLocked(comptime T: type) type {
+ return struct {
+ lock: RwLock,
+ locked_data: T,
+
+ const Self = this;
+
+ pub const HeldReadLock = struct {
+ value: *const T,
+ held: RwLock.HeldRead,
+
+ pub fn release(self: HeldReadLock) void {
+ self.held.release();
+ }
+ };
+
+ pub const HeldWriteLock = struct {
+ value: *T,
+ held: RwLock.HeldWrite,
+
+ pub fn release(self: HeldWriteLock) void {
+ self.held.release();
+ }
+ };
+
+ pub fn init(loop: *Loop, data: T) Self {
+ return Self{
+ .lock = RwLock.init(loop),
+ .locked_data = data,
+ };
+ }
+
+ pub fn deinit(self: *Self) void {
+ self.lock.deinit();
+ }
+
+ pub async fn acquireRead(self: *Self) HeldReadLock {
+ return HeldReadLock{
+ .held = await (async self.lock.acquireRead() catch unreachable),
+ .value = &self.locked_data,
+ };
+ }
+
+ pub async fn acquireWrite(self: *Self) HeldWriteLock {
+ return HeldWriteLock{
+ .held = await (async self.lock.acquireWrite() catch unreachable),
+ .value = &self.locked_data,
+ };
+ }
+ };
+}
diff --git a/std/event/tcp.zig b/std/event/tcp.zig
index ea803a9322..19cce4a5e5 100644
--- a/std/event/tcp.zig
+++ b/std/event/tcp.zig
@@ -55,13 +55,13 @@ pub const Server = struct {
errdefer cancel self.accept_coro.?;
self.listen_resume_node.handle = self.accept_coro.?;
- try self.loop.addFd(sockfd, &self.listen_resume_node);
+ try self.loop.linuxAddFd(sockfd, &self.listen_resume_node, posix.EPOLLIN | posix.EPOLLOUT | posix.EPOLLET);
errdefer self.loop.removeFd(sockfd);
}
/// Stop listening
pub fn close(self: *Server) void {
- self.loop.removeFd(self.sockfd.?);
+ self.loop.linuxRemoveFd(self.sockfd.?);
std.os.close(self.sockfd.?);
}
@@ -116,7 +116,7 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !std.os.File
errdefer std.os.close(sockfd);
try std.os.posixConnectAsync(sockfd, &address.os_addr);
- try await try async loop.waitFd(sockfd);
+ try await try async loop.linuxWaitFd(sockfd, posix.EPOLLIN | posix.EPOLLOUT | posix.EPOLLET);
try std.os.posixGetSockOptConnectError(sockfd);
return std.os.File.openHandle(sockfd);
@@ -181,4 +181,3 @@ async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Serv
assert(mem.eql(u8, msg, "hello from server\n"));
server.close();
}
-
diff --git a/std/hash_map.zig b/std/hash_map.zig
index cebd5272c0..0c100e15d9 100644
--- a/std/hash_map.zig
+++ b/std/hash_map.zig
@@ -9,6 +9,10 @@ const builtin = @import("builtin");
const want_modification_safety = builtin.mode != builtin.Mode.ReleaseFast;
const debug_u32 = if (want_modification_safety) u32 else void;
+pub fn AutoHashMap(comptime K: type, comptime V: type) type {
+ return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K));
+}
+
pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u32, comptime eql: fn (a: K, b: K) bool) type {
return struct {
entries: []Entry,
@@ -20,13 +24,22 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
const Self = this;
- pub const Entry = struct {
- used: bool,
- distance_from_start_index: usize,
+ pub const KV = struct {
key: K,
value: V,
};
+ const Entry = struct {
+ used: bool,
+ distance_from_start_index: usize,
+ kv: KV,
+ };
+
+ pub const GetOrPutResult = struct {
+ kv: *KV,
+ found_existing: bool,
+ };
+
pub const Iterator = struct {
hm: *const Self,
// how many items have we returned
@@ -36,7 +49,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
// used to detect concurrent modification
initial_modification_count: debug_u32,
- pub fn next(it: *Iterator) ?*Entry {
+ pub fn next(it: *Iterator) ?*KV {
if (want_modification_safety) {
assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification
}
@@ -46,7 +59,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
if (entry.used) {
it.index += 1;
it.count += 1;
- return entry;
+ return &entry.kv;
}
}
unreachable; // no next item
@@ -71,7 +84,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
};
}
- pub fn deinit(hm: *const Self) void {
+ pub fn deinit(hm: Self) void {
hm.allocator.free(hm.entries);
}
@@ -84,34 +97,65 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
hm.incrementModificationCount();
}
- pub fn count(hm: *const Self) usize {
- return hm.size;
+ pub fn count(self: Self) usize {
+ return self.size;
}
- /// Returns the value that was already there.
- pub fn put(hm: *Self, key: K, value: *const V) !?V {
- if (hm.entries.len == 0) {
- try hm.initCapacity(16);
+ /// If key exists this function cannot fail.
+ /// If there is an existing item with `key`, then the result
+ /// kv pointer points to it, and found_existing is true.
+ /// Otherwise, puts a new item with undefined value, and
+ /// the kv pointer points to it. Caller should then initialize
+ /// the data.
+ pub fn getOrPut(self: *Self, key: K) !GetOrPutResult {
+ // TODO this implementation can be improved - we should only
+ // have to hash once and find the entry once.
+ if (self.get(key)) |kv| {
+ return GetOrPutResult{
+ .kv = kv,
+ .found_existing = true,
+ };
+ }
+ self.incrementModificationCount();
+ try self.ensureCapacity();
+ const put_result = self.internalPut(key);
+ assert(put_result.old_kv == null);
+ return GetOrPutResult{
+ .kv = &put_result.new_entry.kv,
+ .found_existing = false,
+ };
+ }
+
+ fn ensureCapacity(self: *Self) !void {
+ if (self.entries.len == 0) {
+ return self.initCapacity(16);
}
- hm.incrementModificationCount();
// if we get too full (60%), double the capacity
- if (hm.size * 5 >= hm.entries.len * 3) {
- const old_entries = hm.entries;
- try hm.initCapacity(hm.entries.len * 2);
+ if (self.size * 5 >= self.entries.len * 3) {
+ const old_entries = self.entries;
+ try self.initCapacity(self.entries.len * 2);
// dump all of the old elements into the new table
for (old_entries) |*old_entry| {
if (old_entry.used) {
- _ = hm.internalPut(old_entry.key, old_entry.value);
+ self.internalPut(old_entry.kv.key).new_entry.kv.value = old_entry.kv.value;
}
}
- hm.allocator.free(old_entries);
+ self.allocator.free(old_entries);
}
+ }
+
+ /// Returns the kv pair that was already there.
+ pub fn put(self: *Self, key: K, value: V) !?KV {
+ self.incrementModificationCount();
+ try self.ensureCapacity();
- return hm.internalPut(key, value);
+ const put_result = self.internalPut(key);
+ put_result.new_entry.kv.value = value;
+ return put_result.old_kv;
}
- pub fn get(hm: *const Self, key: K) ?*Entry {
+ pub fn get(hm: *const Self, key: K) ?*KV {
if (hm.entries.len == 0) {
return null;
}
@@ -122,7 +166,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
return hm.get(key) != null;
}
- pub fn remove(hm: *Self, key: K) ?*Entry {
+ pub fn remove(hm: *Self, key: K) ?*KV {
if (hm.entries.len == 0) return null;
hm.incrementModificationCount();
const start_index = hm.keyToIndex(key);
@@ -134,7 +178,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
if (!entry.used) return null;
- if (!eql(entry.key, key)) continue;
+ if (!eql(entry.kv.key, key)) continue;
while (roll_over < hm.entries.len) : (roll_over += 1) {
const next_index = (start_index + roll_over + 1) % hm.entries.len;
@@ -142,7 +186,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
if (!next_entry.used or next_entry.distance_from_start_index == 0) {
entry.used = false;
hm.size -= 1;
- return entry;
+ return &entry.kv;
}
entry.* = next_entry.*;
entry.distance_from_start_index -= 1;
@@ -163,6 +207,16 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
};
}
+ pub fn clone(self: Self) !Self {
+ var other = Self.init(self.allocator);
+ try other.initCapacity(self.entries.len);
+ var it = self.iterator();
+ while (it.next()) |entry| {
+ assert((try other.put(entry.key, entry.value)) == null);
+ }
+ return other;
+ }
+
fn initCapacity(hm: *Self, capacity: usize) !void {
hm.entries = try hm.allocator.alloc(Entry, capacity);
hm.size = 0;
@@ -178,60 +232,81 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
}
}
- /// Returns the value that was already there.
- fn internalPut(hm: *Self, orig_key: K, orig_value: *const V) ?V {
+ const InternalPutResult = struct {
+ new_entry: *Entry,
+ old_kv: ?KV,
+ };
+
+ /// Returns a pointer to the new entry.
+ /// Asserts that there is enough space for the new item.
+ fn internalPut(self: *Self, orig_key: K) InternalPutResult {
var key = orig_key;
- var value = orig_value.*;
- const start_index = hm.keyToIndex(key);
+ var value: V = undefined;
+ const start_index = self.keyToIndex(key);
var roll_over: usize = 0;
var distance_from_start_index: usize = 0;
- while (roll_over < hm.entries.len) : ({
+ var got_result_entry = false;
+ var result = InternalPutResult{
+ .new_entry = undefined,
+ .old_kv = null,
+ };
+ while (roll_over < self.entries.len) : ({
roll_over += 1;
distance_from_start_index += 1;
}) {
- const index = (start_index + roll_over) % hm.entries.len;
- const entry = &hm.entries[index];
+ const index = (start_index + roll_over) % self.entries.len;
+ const entry = &self.entries[index];
- if (entry.used and !eql(entry.key, key)) {
+ if (entry.used and !eql(entry.kv.key, key)) {
if (entry.distance_from_start_index < distance_from_start_index) {
// robin hood to the rescue
const tmp = entry.*;
- hm.max_distance_from_start_index = math.max(hm.max_distance_from_start_index, distance_from_start_index);
+ self.max_distance_from_start_index = math.max(self.max_distance_from_start_index, distance_from_start_index);
+ if (!got_result_entry) {
+ got_result_entry = true;
+ result.new_entry = entry;
+ }
entry.* = Entry{
.used = true,
.distance_from_start_index = distance_from_start_index,
- .key = key,
- .value = value,
+ .kv = KV{
+ .key = key,
+ .value = value,
+ },
};
- key = tmp.key;
- value = tmp.value;
+ key = tmp.kv.key;
+ value = tmp.kv.value;
distance_from_start_index = tmp.distance_from_start_index;
}
continue;
}
- var result: ?V = null;
if (entry.used) {
- result = entry.value;
+ result.old_kv = entry.kv;
} else {
// adding an entry. otherwise overwriting old value with
// same key
- hm.size += 1;
+ self.size += 1;
}
- hm.max_distance_from_start_index = math.max(distance_from_start_index, hm.max_distance_from_start_index);
+ self.max_distance_from_start_index = math.max(distance_from_start_index, self.max_distance_from_start_index);
+ if (!got_result_entry) {
+ result.new_entry = entry;
+ }
entry.* = Entry{
.used = true,
.distance_from_start_index = distance_from_start_index,
- .key = key,
- .value = value,
+ .kv = KV{
+ .key = key,
+ .value = value,
+ },
};
return result;
}
unreachable; // put into a full map
}
- fn internalGet(hm: *const Self, key: K) ?*Entry {
+ fn internalGet(hm: Self, key: K) ?*KV {
const start_index = hm.keyToIndex(key);
{
var roll_over: usize = 0;
@@ -240,13 +315,13 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
const entry = &hm.entries[index];
if (!entry.used) return null;
- if (eql(entry.key, key)) return entry;
+ if (eql(entry.kv.key, key)) return &entry.kv;
}
}
return null;
}
- fn keyToIndex(hm: *const Self, key: K) usize {
+ fn keyToIndex(hm: Self, key: K) usize {
return usize(hash(key)) % hm.entries.len;
}
};
@@ -256,7 +331,7 @@ test "basic hash map usage" {
var direct_allocator = std.heap.DirectAllocator.init();
defer direct_allocator.deinit();
- var map = HashMap(i32, i32, hash_i32, eql_i32).init(&direct_allocator.allocator);
+ var map = AutoHashMap(i32, i32).init(&direct_allocator.allocator);
defer map.deinit();
assert((try map.put(1, 11)) == null);
@@ -265,8 +340,19 @@ test "basic hash map usage" {
assert((try map.put(4, 44)) == null);
assert((try map.put(5, 55)) == null);
- assert((try map.put(5, 66)).? == 55);
- assert((try map.put(5, 55)).? == 66);
+ assert((try map.put(5, 66)).?.value == 55);
+ assert((try map.put(5, 55)).?.value == 66);
+
+ const gop1 = try map.getOrPut(5);
+ assert(gop1.found_existing == true);
+ assert(gop1.kv.value == 55);
+ gop1.kv.value = 77;
+ assert(map.get(5).?.value == 77);
+
+ const gop2 = try map.getOrPut(99);
+ assert(gop2.found_existing == false);
+ gop2.kv.value = 42;
+ assert(map.get(99).?.value == 42);
assert(map.contains(2));
assert(map.get(2).?.value == 22);
@@ -279,7 +365,7 @@ test "iterator hash map" {
var direct_allocator = std.heap.DirectAllocator.init();
defer direct_allocator.deinit();
- var reset_map = HashMap(i32, i32, hash_i32, eql_i32).init(&direct_allocator.allocator);
+ var reset_map = AutoHashMap(i32, i32).init(&direct_allocator.allocator);
defer reset_map.deinit();
assert((try reset_map.put(1, 11)) == null);
@@ -287,14 +373,14 @@ test "iterator hash map" {
assert((try reset_map.put(3, 33)) == null);
var keys = []i32{
- 1,
- 2,
3,
+ 2,
+ 1,
};
var values = []i32{
- 11,
- 22,
33,
+ 22,
+ 11,
};
var it = reset_map.iterator();
@@ -322,10 +408,124 @@ test "iterator hash map" {
assert(entry.value == values[0]);
}
-fn hash_i32(x: i32) u32 {
- return @bitCast(u32, x);
+pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
+ return struct {
+ fn hash(key: K) u32 {
+ comptime var rng = comptime std.rand.DefaultPrng.init(0);
+ return autoHash(key, &rng.random, u32);
+ }
+ }.hash;
+}
+
+pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
+ return struct {
+ fn eql(a: K, b: K) bool {
+ return autoEql(a, b);
+ }
+ }.eql;
+}
+
+// TODO improve these hash functions
+pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type) HashInt {
+ switch (@typeInfo(@typeOf(key))) {
+ builtin.TypeId.NoReturn,
+ builtin.TypeId.Opaque,
+ builtin.TypeId.Undefined,
+ builtin.TypeId.ArgTuple,
+ => @compileError("cannot hash this type"),
+
+ builtin.TypeId.Void,
+ builtin.TypeId.Null,
+ => return 0,
+
+ builtin.TypeId.Int => |info| {
+ const unsigned_x = @bitCast(@IntType(false, info.bits), key);
+ if (info.bits <= HashInt.bit_count) {
+ return HashInt(unsigned_x) ^ comptime rng.scalar(HashInt);
+ } else {
+ return @truncate(HashInt, unsigned_x ^ comptime rng.scalar(@typeOf(unsigned_x)));
+ }
+ },
+
+ builtin.TypeId.Float => |info| {
+ return autoHash(@bitCast(@IntType(false, info.bits), key), rng);
+ },
+ builtin.TypeId.Bool => return autoHash(@boolToInt(key), rng),
+ builtin.TypeId.Enum => return autoHash(@enumToInt(key), rng),
+ builtin.TypeId.ErrorSet => return autoHash(@errorToInt(key), rng),
+ builtin.TypeId.Promise, builtin.TypeId.Fn => return autoHash(@ptrToInt(key), rng),
+
+ builtin.TypeId.Namespace,
+ builtin.TypeId.Block,
+ builtin.TypeId.BoundFn,
+ builtin.TypeId.ComptimeFloat,
+ builtin.TypeId.ComptimeInt,
+ builtin.TypeId.Type,
+ => return 0,
+
+ builtin.TypeId.Pointer => |info| switch (info.size) {
+ builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto hash for single item pointers"),
+ builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto hash for many item pointers"),
+ builtin.TypeInfo.Pointer.Size.Slice => {
+ const interval = std.math.max(1, key.len / 256);
+ var i: usize = 0;
+ var h = comptime rng.scalar(HashInt);
+ while (i < key.len) : (i += interval) {
+ h ^= autoHash(key[i], rng, HashInt);
+ }
+ return h;
+ },
+ },
+
+ builtin.TypeId.Optional => @compileError("TODO auto hash for optionals"),
+ builtin.TypeId.Array => @compileError("TODO auto hash for arrays"),
+ builtin.TypeId.Struct => @compileError("TODO auto hash for structs"),
+ builtin.TypeId.Union => @compileError("TODO auto hash for unions"),
+ builtin.TypeId.ErrorUnion => @compileError("TODO auto hash for unions"),
+ }
}
-fn eql_i32(a: i32, b: i32) bool {
- return a == b;
+pub fn autoEql(a: var, b: @typeOf(a)) bool {
+ switch (@typeInfo(@typeOf(a))) {
+ builtin.TypeId.NoReturn,
+ builtin.TypeId.Opaque,
+ builtin.TypeId.Undefined,
+ builtin.TypeId.ArgTuple,
+ => @compileError("cannot test equality of this type"),
+ builtin.TypeId.Void,
+ builtin.TypeId.Null,
+ => return true,
+ builtin.TypeId.Bool,
+ builtin.TypeId.Int,
+ builtin.TypeId.Float,
+ builtin.TypeId.ComptimeFloat,
+ builtin.TypeId.ComptimeInt,
+ builtin.TypeId.Namespace,
+ builtin.TypeId.Block,
+ builtin.TypeId.Promise,
+ builtin.TypeId.Enum,
+ builtin.TypeId.BoundFn,
+ builtin.TypeId.Fn,
+ builtin.TypeId.ErrorSet,
+ builtin.TypeId.Type,
+ => return a == b,
+
+ builtin.TypeId.Pointer => |info| switch (info.size) {
+ builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto eql for single item pointers"),
+ builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto eql for many item pointers"),
+ builtin.TypeInfo.Pointer.Size.Slice => {
+ if (a.len != b.len) return false;
+ for (a) |a_item, i| {
+ if (!autoEql(a_item, b[i])) return false;
+ }
+ return true;
+ },
+ },
+
+ builtin.TypeId.Optional => @compileError("TODO auto eql for optionals"),
+ builtin.TypeId.Array => @compileError("TODO auto eql for arrays"),
+ builtin.TypeId.Struct => @compileError("TODO auto eql for structs"),
+ builtin.TypeId.Union => @compileError("TODO auto eql for unions"),
+ builtin.TypeId.ErrorUnion => @compileError("TODO auto eql for unions"),
+ }
}
diff --git a/std/index.zig b/std/index.zig
index f4728beba6..59f54fa9d7 100644
--- a/std/index.zig
+++ b/std/index.zig
@@ -5,10 +5,11 @@ pub const BufSet = @import("buf_set.zig").BufSet;
pub const Buffer = @import("buffer.zig").Buffer;
pub const BufferOutStream = @import("buffer.zig").BufferOutStream;
pub const HashMap = @import("hash_map.zig").HashMap;
+pub const AutoHashMap = @import("hash_map.zig").AutoHashMap;
pub const LinkedList = @import("linked_list.zig").LinkedList;
-pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList;
pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
pub const DynLib = @import("dynamic_library.zig").DynLib;
+pub const Mutex = @import("mutex.zig").Mutex;
pub const atomic = @import("atomic/index.zig");
pub const base64 = @import("base64.zig");
@@ -49,6 +50,7 @@ test "std" {
_ = @import("hash_map.zig");
_ = @import("linked_list.zig");
_ = @import("segmented_list.zig");
+ _ = @import("mutex.zig");
_ = @import("base64.zig");
_ = @import("build.zig");
diff --git a/std/io.zig b/std/io.zig
index ff73c04f78..49e03a64b2 100644
--- a/std/io.zig
+++ b/std/io.zig
@@ -415,13 +415,12 @@ pub fn PeekStream(comptime buffer_size: usize, comptime InStreamError: type) typ
self.at_end = (read < left);
return pos + read;
}
-
};
}
pub const SliceInStream = struct {
const Self = this;
- pub const Error = error { };
+ pub const Error = error{};
pub const Stream = InStream(Error);
pub stream: Stream,
@@ -481,13 +480,12 @@ pub const SliceOutStream = struct {
assert(self.pos <= self.slice.len);
- const n =
- if (self.pos + bytes.len <= self.slice.len)
- bytes.len
- else
- self.slice.len - self.pos;
+ const n = if (self.pos + bytes.len <= self.slice.len)
+ bytes.len
+ else
+ self.slice.len - self.pos;
- std.mem.copy(u8, self.slice[self.pos..self.pos + n], bytes[0..n]);
+ std.mem.copy(u8, self.slice[self.pos .. self.pos + n], bytes[0..n]);
self.pos += n;
if (n < bytes.len) {
@@ -586,7 +584,7 @@ pub const BufferedAtomicFile = struct {
});
errdefer allocator.destroy(self);
- self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.default_file_mode);
+ self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.File.default_mode);
errdefer self.atomic_file.deinit();
self.file_stream = FileOutStream.init(&self.atomic_file.file);
diff --git a/std/json.zig b/std/json.zig
index e62d5a3466..5fc2274985 100644
--- a/std/json.zig
+++ b/std/json.zig
@@ -1318,7 +1318,7 @@ pub const Parser = struct {
_ = p.stack.pop();
var object = &p.stack.items[p.stack.len - 1].Object;
- _ = try object.put(key, value);
+ _ = try object.put(key, value.*);
p.state = State.ObjectKey;
},
// Array Parent -> [ ..., <array>, value ]
diff --git a/std/linked_list.zig b/std/linked_list.zig
index 62cd5ca2bb..130ddbce5d 100644
--- a/std/linked_list.zig
+++ b/std/linked_list.zig
@@ -4,18 +4,8 @@ const assert = debug.assert;
const mem = std.mem;
const Allocator = mem.Allocator;
-/// Generic non-intrusive doubly linked list.
-pub fn LinkedList(comptime T: type) type {
- return BaseLinkedList(T, void, "");
-}
-
-/// Generic intrusive doubly linked list.
-pub fn IntrusiveLinkedList(comptime ParentType: type, comptime field_name: []const u8) type {
- return BaseLinkedList(void, ParentType, field_name);
-}
-
/// Generic doubly linked list.
-fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_name: []const u8) type {
+pub fn LinkedList(comptime T: type) type {
return struct {
const Self = this;
@@ -25,23 +15,13 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
next: ?*Node,
data: T,
- pub fn init(value: *const T) Node {
+ pub fn init(data: T) Node {
return Node{
.prev = null,
.next = null,
- .data = value.*,
+ .data = data,
};
}
-
- pub fn initIntrusive() Node {
- // TODO: when #678 is solved this can become `init`.
- return Node.init({});
- }
-
- pub fn toData(node: *Node) *ParentType {
- comptime assert(isIntrusive());
- return @fieldParentPtr(ParentType, field_name, node);
- }
};
first: ?*Node,
@@ -60,10 +40,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
};
}
- fn isIntrusive() bool {
- return ParentType != void or field_name.len != 0;
- }
-
/// Insert a new node after an existing one.
///
/// Arguments:
@@ -192,7 +168,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
/// Returns:
/// A pointer to the new node.
pub fn allocateNode(list: *Self, allocator: *Allocator) !*Node {
- comptime assert(!isIntrusive());
return allocator.create(Node(undefined));
}
@@ -202,7 +177,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
/// node: Pointer to the node to deallocate.
/// allocator: Dynamic memory allocator.
pub fn destroyNode(list: *Self, node: *Node, allocator: *Allocator) void {
- comptime assert(!isIntrusive());
allocator.destroy(node);
}
@@ -214,8 +188,7 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
///
/// Returns:
/// A pointer to the new node.
- pub fn createNode(list: *Self, data: *const T, allocator: *Allocator) !*Node {
- comptime assert(!isIntrusive());
+ pub fn createNode(list: *Self, data: T, allocator: *Allocator) !*Node {
var node = try list.allocateNode(allocator);
node.* = Node.init(data);
return node;
@@ -274,69 +247,3 @@ test "basic linked list test" {
assert(list.last.?.data == 4);
assert(list.len == 2);
}
-
-const ElementList = IntrusiveLinkedList(Element, "link");
-const Element = struct {
- value: u32,
- link: IntrusiveLinkedList(Element, "link").Node,
-};
-
-test "basic intrusive linked list test" {
- const allocator = debug.global_allocator;
- var list = ElementList.init();
-
- var one = Element{
- .value = 1,
- .link = ElementList.Node.initIntrusive(),
- };
- var two = Element{
- .value = 2,
- .link = ElementList.Node.initIntrusive(),
- };
- var three = Element{
- .value = 3,
- .link = ElementList.Node.initIntrusive(),
- };
- var four = Element{
- .value = 4,
- .link = ElementList.Node.initIntrusive(),
- };
- var five = Element{
- .value = 5,
- .link = ElementList.Node.initIntrusive(),
- };
-
- list.append(&two.link); // {2}
- list.append(&five.link); // {2, 5}
- list.prepend(&one.link); // {1, 2, 5}
- list.insertBefore(&five.link, &four.link); // {1, 2, 4, 5}
- list.insertAfter(&two.link, &three.link); // {1, 2, 3, 4, 5}
-
- // Traverse forwards.
- {
- var it = list.first;
- var index: u32 = 1;
- while (it) |node| : (it = node.next) {
- assert(node.toData().value == index);
- index += 1;
- }
- }
-
- // Traverse backwards.
- {
- var it = list.last;
- var index: u32 = 1;
- while (it) |node| : (it = node.prev) {
- assert(node.toData().value == (6 - index));
- index += 1;
- }
- }
-
- var first = list.popFirst(); // {2, 3, 4, 5}
- var last = list.pop(); // {2, 3, 4}
- list.remove(&three.link); // {2, 4}
-
- assert(list.first.?.toData().value == 2);
- assert(list.last.?.toData().value == 4);
- assert(list.len == 2);
-}
diff --git a/std/mem.zig b/std/mem.zig
index b717ffa609..dc5fc8a539 100644
--- a/std/mem.zig
+++ b/std/mem.zig
@@ -577,7 +577,7 @@ pub fn join(allocator: *Allocator, sep: u8, strings: ...) ![]u8 {
}
}
- return buf[0..buf_index];
+ return allocator.shrink(u8, buf, buf_index);
}
test "mem.join" {
diff --git a/std/mutex.zig b/std/mutex.zig
new file mode 100644
index 0000000000..6aee87d1d7
--- /dev/null
+++ b/std/mutex.zig
@@ -0,0 +1,27 @@
+const std = @import("index.zig");
+const builtin = @import("builtin");
+const AtomicOrder = builtin.AtomicOrder;
+const AtomicRmwOp = builtin.AtomicRmwOp;
+const assert = std.debug.assert;
+
+/// TODO use syscalls instead of a spinlock
+pub const Mutex = struct {
+ lock: u8, // TODO use a bool
+
+ pub const Held = struct {
+ mutex: *Mutex,
+
+ pub fn release(self: Held) void {
+ assert(@atomicRmw(u8, &self.mutex.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+ }
+ };
+
+ pub fn init() Mutex {
+ return Mutex{ .lock = 0 };
+ }
+
+ pub fn acquire(self: *Mutex) Held {
+ while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
+ return Held{ .mutex = self };
+ }
+};
diff --git a/std/os/darwin.zig b/std/os/darwin.zig
index cf67b01d5a..935d28d6f1 100644
--- a/std/os/darwin.zig
+++ b/std/os/darwin.zig
@@ -482,91 +482,98 @@ pub const NOTE_MACH_CONTINUOUS_TIME = 0x00000080;
/// data is mach absolute time units
pub const NOTE_MACHTIME = 0x00000100;
-pub const AF_UNSPEC: c_int = 0;
-pub const AF_LOCAL: c_int = 1;
-pub const AF_UNIX: c_int = AF_LOCAL;
-pub const AF_INET: c_int = 2;
-pub const AF_SYS_CONTROL: c_int = 2;
-pub const AF_IMPLINK: c_int = 3;
-pub const AF_PUP: c_int = 4;
-pub const AF_CHAOS: c_int = 5;
-pub const AF_NS: c_int = 6;
-pub const AF_ISO: c_int = 7;
-pub const AF_OSI: c_int = AF_ISO;
-pub const AF_ECMA: c_int = 8;
-pub const AF_DATAKIT: c_int = 9;
-pub const AF_CCITT: c_int = 10;
-pub const AF_SNA: c_int = 11;
-pub const AF_DECnet: c_int = 12;
-pub const AF_DLI: c_int = 13;
-pub const AF_LAT: c_int = 14;
-pub const AF_HYLINK: c_int = 15;
-pub const AF_APPLETALK: c_int = 16;
-pub const AF_ROUTE: c_int = 17;
-pub const AF_LINK: c_int = 18;
-pub const AF_XTP: c_int = 19;
-pub const AF_COIP: c_int = 20;
-pub const AF_CNT: c_int = 21;
-pub const AF_RTIP: c_int = 22;
-pub const AF_IPX: c_int = 23;
-pub const AF_SIP: c_int = 24;
-pub const AF_PIP: c_int = 25;
-pub const AF_ISDN: c_int = 28;
-pub const AF_E164: c_int = AF_ISDN;
-pub const AF_KEY: c_int = 29;
-pub const AF_INET6: c_int = 30;
-pub const AF_NATM: c_int = 31;
-pub const AF_SYSTEM: c_int = 32;
-pub const AF_NETBIOS: c_int = 33;
-pub const AF_PPP: c_int = 34;
-pub const AF_MAX: c_int = 40;
-
-pub const PF_UNSPEC: c_int = AF_UNSPEC;
-pub const PF_LOCAL: c_int = AF_LOCAL;
-pub const PF_UNIX: c_int = PF_LOCAL;
-pub const PF_INET: c_int = AF_INET;
-pub const PF_IMPLINK: c_int = AF_IMPLINK;
-pub const PF_PUP: c_int = AF_PUP;
-pub const PF_CHAOS: c_int = AF_CHAOS;
-pub const PF_NS: c_int = AF_NS;
-pub const PF_ISO: c_int = AF_ISO;
-pub const PF_OSI: c_int = AF_ISO;
-pub const PF_ECMA: c_int = AF_ECMA;
-pub const PF_DATAKIT: c_int = AF_DATAKIT;
-pub const PF_CCITT: c_int = AF_CCITT;
-pub const PF_SNA: c_int = AF_SNA;
-pub const PF_DECnet: c_int = AF_DECnet;
-pub const PF_DLI: c_int = AF_DLI;
-pub const PF_LAT: c_int = AF_LAT;
-pub const PF_HYLINK: c_int = AF_HYLINK;
-pub const PF_APPLETALK: c_int = AF_APPLETALK;
-pub const PF_ROUTE: c_int = AF_ROUTE;
-pub const PF_LINK: c_int = AF_LINK;
-pub const PF_XTP: c_int = AF_XTP;
-pub const PF_COIP: c_int = AF_COIP;
-pub const PF_CNT: c_int = AF_CNT;
-pub const PF_SIP: c_int = AF_SIP;
-pub const PF_IPX: c_int = AF_IPX;
-pub const PF_RTIP: c_int = AF_RTIP;
-pub const PF_PIP: c_int = AF_PIP;
-pub const PF_ISDN: c_int = AF_ISDN;
-pub const PF_KEY: c_int = AF_KEY;
-pub const PF_INET6: c_int = AF_INET6;
-pub const PF_NATM: c_int = AF_NATM;
-pub const PF_SYSTEM: c_int = AF_SYSTEM;
-pub const PF_NETBIOS: c_int = AF_NETBIOS;
-pub const PF_PPP: c_int = AF_PPP;
-pub const PF_MAX: c_int = AF_MAX;
-
-pub const SYSPROTO_EVENT: c_int = 1;
-pub const SYSPROTO_CONTROL: c_int = 2;
-
-pub const SOCK_STREAM: c_int = 1;
-pub const SOCK_DGRAM: c_int = 2;
-pub const SOCK_RAW: c_int = 3;
-pub const SOCK_RDM: c_int = 4;
-pub const SOCK_SEQPACKET: c_int = 5;
-pub const SOCK_MAXADDRLEN: c_int = 255;
+pub const AF_UNSPEC = 0;
+pub const AF_LOCAL = 1;
+pub const AF_UNIX = AF_LOCAL;
+pub const AF_INET = 2;
+pub const AF_SYS_CONTROL = 2;
+pub const AF_IMPLINK = 3;
+pub const AF_PUP = 4;
+pub const AF_CHAOS = 5;
+pub const AF_NS = 6;
+pub const AF_ISO = 7;
+pub const AF_OSI = AF_ISO;
+pub const AF_ECMA = 8;
+pub const AF_DATAKIT = 9;
+pub const AF_CCITT = 10;
+pub const AF_SNA = 11;
+pub const AF_DECnet = 12;
+pub const AF_DLI = 13;
+pub const AF_LAT = 14;
+pub const AF_HYLINK = 15;
+pub const AF_APPLETALK = 16;
+pub const AF_ROUTE = 17;
+pub const AF_LINK = 18;
+pub const AF_XTP = 19;
+pub const AF_COIP = 20;
+pub const AF_CNT = 21;
+pub const AF_RTIP = 22;
+pub const AF_IPX = 23;
+pub const AF_SIP = 24;
+pub const AF_PIP = 25;
+pub const AF_ISDN = 28;
+pub const AF_E164 = AF_ISDN;
+pub const AF_KEY = 29;
+pub const AF_INET6 = 30;
+pub const AF_NATM = 31;
+pub const AF_SYSTEM = 32;
+pub const AF_NETBIOS = 33;
+pub const AF_PPP = 34;
+pub const AF_MAX = 40;
+
+pub const PF_UNSPEC = AF_UNSPEC;
+pub const PF_LOCAL = AF_LOCAL;
+pub const PF_UNIX = PF_LOCAL;
+pub const PF_INET = AF_INET;
+pub const PF_IMPLINK = AF_IMPLINK;
+pub const PF_PUP = AF_PUP;
+pub const PF_CHAOS = AF_CHAOS;
+pub const PF_NS = AF_NS;
+pub const PF_ISO = AF_ISO;
+pub const PF_OSI = AF_ISO;
+pub const PF_ECMA = AF_ECMA;
+pub const PF_DATAKIT = AF_DATAKIT;
+pub const PF_CCITT = AF_CCITT;
+pub const PF_SNA = AF_SNA;
+pub const PF_DECnet = AF_DECnet;
+pub const PF_DLI = AF_DLI;
+pub const PF_LAT = AF_LAT;
+pub const PF_HYLINK = AF_HYLINK;
+pub const PF_APPLETALK = AF_APPLETALK;
+pub const PF_ROUTE = AF_ROUTE;
+pub const PF_LINK = AF_LINK;
+pub const PF_XTP = AF_XTP;
+pub const PF_COIP = AF_COIP;
+pub const PF_CNT = AF_CNT;
+pub const PF_SIP = AF_SIP;
+pub const PF_IPX = AF_IPX;
+pub const PF_RTIP = AF_RTIP;
+pub const PF_PIP = AF_PIP;
+pub const PF_ISDN = AF_ISDN;
+pub const PF_KEY = AF_KEY;
+pub const PF_INET6 = AF_INET6;
+pub const PF_NATM = AF_NATM;
+pub const PF_SYSTEM = AF_SYSTEM;
+pub const PF_NETBIOS = AF_NETBIOS;
+pub const PF_PPP = AF_PPP;
+pub const PF_MAX = AF_MAX;
+
+pub const SYSPROTO_EVENT = 1;
+pub const SYSPROTO_CONTROL = 2;
+
+pub const SOCK_STREAM = 1;
+pub const SOCK_DGRAM = 2;
+pub const SOCK_RAW = 3;
+pub const SOCK_RDM = 4;
+pub const SOCK_SEQPACKET = 5;
+pub const SOCK_MAXADDRLEN = 255;
+
+pub const IPPROTO_ICMP = 1;
+pub const IPPROTO_ICMPV6 = 58;
+pub const IPPROTO_TCP = 6;
+pub const IPPROTO_UDP = 17;
+pub const IPPROTO_IP = 0;
+pub const IPPROTO_IPV6 = 41;
fn wstatus(x: i32) i32 {
return x & 0o177;
@@ -605,6 +612,11 @@ pub fn abort() noreturn {
c.abort();
}
+// bind(int socket, const struct sockaddr *address, socklen_t address_len)
+pub fn bind(fd: i32, addr: *const sockaddr, len: socklen_t) usize {
+ return errnoWrap(c.bind(@bitCast(c_int, fd), addr, len));
+}
+
pub fn exit(code: i32) noreturn {
c.exit(code);
}
@@ -634,6 +646,10 @@ pub fn read(fd: i32, buf: [*]u8, nbyte: usize) usize {
return errnoWrap(c.read(fd, @ptrCast(*c_void, buf), nbyte));
}
+pub fn pread(fd: i32, buf: [*]u8, nbyte: usize, offset: u64) usize {
+ return errnoWrap(c.pread(fd, @ptrCast(*c_void, buf), nbyte, offset));
+}
+
pub fn stat(noalias path: [*]const u8, noalias buf: *stat) usize {
return errnoWrap(c.stat(path, buf));
}
@@ -642,6 +658,10 @@ pub fn write(fd: i32, buf: [*]const u8, nbyte: usize) usize {
return errnoWrap(c.write(fd, @ptrCast(*const c_void, buf), nbyte));
}
+pub fn pwrite(fd: i32, buf: [*]const u8, nbyte: usize, offset: u64) usize {
+ return errnoWrap(c.pwrite(fd, @ptrCast(*const c_void, buf), nbyte, offset));
+}
+
pub fn mmap(address: ?[*]u8, length: usize, prot: usize, flags: u32, fd: i32, offset: isize) usize {
const ptr_result = c.mmap(
@ptrCast(*c_void, address),
@@ -805,6 +825,20 @@ pub fn sigaction(sig: u5, noalias act: *const Sigaction, noalias oact: ?*Sigacti
return result;
}
+pub fn socket(domain: u32, socket_type: u32, protocol: u32) usize {
+ return errnoWrap(c.socket(@bitCast(c_int, domain), @bitCast(c_int, socket_type), @bitCast(c_int, protocol)));
+}
+
+pub const iovec = extern struct {
+ iov_base: [*]u8,
+ iov_len: usize,
+};
+
+pub const iovec_const = extern struct {
+ iov_base: [*]const u8,
+ iov_len: usize,
+};
+
pub const sigset_t = c.sigset_t;
pub const empty_sigset = sigset_t(0);
@@ -812,8 +846,13 @@ pub const timespec = c.timespec;
pub const Stat = c.Stat;
pub const dirent = c.dirent;
+pub const in_port_t = c.in_port_t;
pub const sa_family_t = c.sa_family_t;
+pub const socklen_t = c.socklen_t;
+
pub const sockaddr = c.sockaddr;
+pub const sockaddr_in = c.sockaddr_in;
+pub const sockaddr_in6 = c.sockaddr_in6;
/// Renamed from `kevent` to `Kevent` to avoid conflict with the syscall.
pub const Kevent = c.Kevent;
diff --git a/std/os/file.zig b/std/os/file.zig
index 6998ba00d1..074547193c 100644
--- a/std/os/file.zig
+++ b/std/os/file.zig
@@ -15,6 +15,16 @@ pub const File = struct {
/// The OS-specific file descriptor or file handle.
handle: os.FileHandle,
+ pub const Mode = switch (builtin.os) {
+ Os.windows => void,
+ else => u32,
+ };
+
+ pub const default_mode = switch (builtin.os) {
+ Os.windows => {},
+ else => 0o666,
+ };
+
pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
@@ -39,16 +49,16 @@ pub const File = struct {
}
}
- /// Calls `openWriteMode` with os.default_file_mode for the mode.
+ /// Calls `openWriteMode` with os.File.default_mode for the mode.
pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {
- return openWriteMode(allocator, path, os.default_file_mode);
+ return openWriteMode(allocator, path, os.File.default_mode);
}
/// If the path does not exist it will be created.
/// If a file already exists in the destination it will be truncated.
/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
/// Call close to clean up.
- pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
+ pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {
if (is_posix) {
const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
const fd = try os.posixOpen(allocator, path, flags, file_mode);
@@ -72,7 +82,7 @@ pub const File = struct {
/// If a file already exists in the destination this returns OpenError.PathAlreadyExists
/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
/// Call close to clean up.
- pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
+ pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {
if (is_posix) {
const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
const fd = try os.posixOpen(allocator, path, flags, file_mode);
@@ -282,7 +292,7 @@ pub const File = struct {
Unexpected,
};
- pub fn mode(self: *File) ModeError!os.FileMode {
+ pub fn mode(self: *File) ModeError!Mode {
if (is_posix) {
var stat: posix.Stat = undefined;
const err = posix.getErrno(posix.fstat(self.handle, &stat));
@@ -296,7 +306,7 @@ pub const File = struct {
// TODO: we should be able to cast u16 to ModeError!u32, making this
// explicit cast not necessary
- return os.FileMode(stat.mode);
+ return Mode(stat.mode);
} else if (is_windows) {
return {};
} else {
@@ -305,9 +315,11 @@ pub const File = struct {
}
pub const ReadError = error{
- BadFd,
- Io,
+ FileClosed,
+ InputOutput,
IsDir,
+ WouldBlock,
+ SystemResources,
Unexpected,
};
@@ -323,9 +335,12 @@ pub const File = struct {
posix.EINTR => continue,
posix.EINVAL => unreachable,
posix.EFAULT => unreachable,
- posix.EBADF => return error.BadFd,
- posix.EIO => return error.Io,
+ posix.EAGAIN => return error.WouldBlock,
+ posix.EBADF => return error.FileClosed,
+ posix.EIO => return error.InputOutput,
posix.EISDIR => return error.IsDir,
+ posix.ENOBUFS => return error.SystemResources,
+ posix.ENOMEM => return error.SystemResources,
else => return os.unexpectedErrorPosix(read_err),
}
}
@@ -338,7 +353,7 @@ pub const File = struct {
while (index < buffer.len) {
const want_read_count = @intCast(windows.DWORD, math.min(windows.DWORD(@maxValue(windows.DWORD)), buffer.len - index));
var amt_read: windows.DWORD = undefined;
- if (windows.ReadFile(self.handle, @ptrCast(*c_void, buffer.ptr + index), want_read_count, &amt_read, null) == 0) {
+ if (windows.ReadFile(self.handle, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.OPERATION_ABORTED => continue,
diff --git a/std/os/index.zig b/std/os/index.zig
index d47444d67d..34be1acabe 100644
--- a/std/os/index.zig
+++ b/std/os/index.zig
@@ -38,16 +38,6 @@ pub const path = @import("path.zig");
pub const File = @import("file.zig").File;
pub const time = @import("time.zig");
-pub const FileMode = switch (builtin.os) {
- Os.windows => void,
- else => u32,
-};
-
-pub const default_file_mode = switch (builtin.os) {
- Os.windows => {},
- else => 0o666,
-};
-
pub const page_size = 4 * 1024;
pub const UserInfo = @import("get_user_id.zig").UserInfo;
@@ -256,6 +246,67 @@ pub fn posixRead(fd: i32, buf: []u8) !void {
}
}
+/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
+pub fn posix_preadv(fd: i32, iov: [*]const posix.iovec, count: usize, offset: u64) !usize {
+ switch (builtin.os) {
+ builtin.Os.macosx => {
+ // Darwin does not have preadv but it does have pread.
+ var off: usize = 0;
+ var iov_i: usize = 0;
+ var inner_off: usize = 0;
+ while (true) {
+ const v = iov[iov_i];
+ const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
+ const err = darwin.getErrno(rc);
+ switch (err) {
+ 0 => {
+ off += rc;
+ inner_off += rc;
+ if (inner_off == v.iov_len) {
+ iov_i += 1;
+ inner_off = 0;
+ if (iov_i == count) {
+ return off;
+ }
+ }
+ if (rc == 0) return off; // EOF
+ continue;
+ },
+ posix.EINTR => continue,
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.ESPIPE => unreachable, // fd is not seekable
+ posix.EAGAIN => return error.WouldBlock,
+ posix.EBADF => return error.FileClosed,
+ posix.EIO => return error.InputOutput,
+ posix.EISDIR => return error.IsDir,
+ posix.ENOBUFS => return error.SystemResources,
+ posix.ENOMEM => return error.SystemResources,
+ else => return unexpectedErrorPosix(err),
+ }
+ }
+ },
+ builtin.Os.linux, builtin.Os.freebsd => while (true) {
+ const rc = posix.preadv(fd, iov, count, offset);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return rc,
+ posix.EINTR => continue,
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EAGAIN => return error.WouldBlock,
+ posix.EBADF => return error.FileClosed,
+ posix.EIO => return error.InputOutput,
+ posix.EISDIR => return error.IsDir,
+ posix.ENOBUFS => return error.SystemResources,
+ posix.ENOMEM => return error.SystemResources,
+ else => return unexpectedErrorPosix(err),
+ }
+ },
+ else => @compileError("Unsupported OS"),
+ }
+}
+
pub const PosixWriteError = error{
WouldBlock,
FileClosed,
@@ -300,6 +351,71 @@ pub fn posixWrite(fd: i32, bytes: []const u8) !void {
}
}
+pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, offset: u64) PosixWriteError!void {
+ switch (builtin.os) {
+ builtin.Os.macosx => {
+ // Darwin does not have pwritev but it does have pwrite.
+ var off: usize = 0;
+ var iov_i: usize = 0;
+ var inner_off: usize = 0;
+ while (true) {
+ const v = iov[iov_i];
+ const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
+ const err = darwin.getErrno(rc);
+ switch (err) {
+ 0 => {
+ off += rc;
+ inner_off += rc;
+ if (inner_off == v.iov_len) {
+ iov_i += 1;
+ inner_off = 0;
+ if (iov_i == count) {
+ return;
+ }
+ }
+ continue;
+ },
+ posix.EINTR => continue,
+ posix.ESPIPE => unreachable, // fd is not seekable
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EAGAIN => return PosixWriteError.WouldBlock,
+ posix.EBADF => return PosixWriteError.FileClosed,
+ posix.EDESTADDRREQ => return PosixWriteError.DestinationAddressRequired,
+ posix.EDQUOT => return PosixWriteError.DiskQuota,
+ posix.EFBIG => return PosixWriteError.FileTooBig,
+ posix.EIO => return PosixWriteError.InputOutput,
+ posix.ENOSPC => return PosixWriteError.NoSpaceLeft,
+ posix.EPERM => return PosixWriteError.AccessDenied,
+ posix.EPIPE => return PosixWriteError.BrokenPipe,
+ else => return unexpectedErrorPosix(err),
+ }
+ }
+ },
+ builtin.Os.linux => while (true) {
+ const rc = posix.pwritev(fd, iov, count, offset);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return,
+ posix.EINTR => continue,
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EAGAIN => return PosixWriteError.WouldBlock,
+ posix.EBADF => return PosixWriteError.FileClosed,
+ posix.EDESTADDRREQ => return PosixWriteError.DestinationAddressRequired,
+ posix.EDQUOT => return PosixWriteError.DiskQuota,
+ posix.EFBIG => return PosixWriteError.FileTooBig,
+ posix.EIO => return PosixWriteError.InputOutput,
+ posix.ENOSPC => return PosixWriteError.NoSpaceLeft,
+ posix.EPERM => return PosixWriteError.AccessDenied,
+ posix.EPIPE => return PosixWriteError.BrokenPipe,
+ else => return unexpectedErrorPosix(err),
+ }
+ },
+ else => @compileError("Unsupported OS"),
+ }
+}
+
pub const PosixOpenError = error{
OutOfMemory,
AccessDenied,
@@ -853,7 +969,7 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
/// merged and readily available,
/// there is a possibility of power loss or application termination leaving temporary files present
-pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: FileMode) !void {
+pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
var in_file = try os.File.openRead(allocator, source_path);
defer in_file.close();
@@ -879,7 +995,7 @@ pub const AtomicFile = struct {
/// dest_path must remain valid for the lifetime of AtomicFile
/// call finish to atomically replace dest_path with contents
- pub fn init(allocator: *Allocator, dest_path: []const u8, mode: FileMode) !AtomicFile {
+ pub fn init(allocator: *Allocator, dest_path: []const u8, mode: File.Mode) !AtomicFile {
const dirname = os.path.dirname(dest_path);
var rand_buf: [12]u8 = undefined;
@@ -2943,3 +3059,44 @@ pub fn bsdKEvent(
}
}
}
+
+pub fn linuxINotifyInit1(flags: u32) !i32 {
+ const rc = linux.inotify_init1(flags);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return @intCast(i32, rc),
+ posix.EINVAL => unreachable,
+ posix.EMFILE => return error.ProcessFdQuotaExceeded,
+ posix.ENFILE => return error.SystemFdQuotaExceeded,
+ posix.ENOMEM => return error.SystemResources,
+ else => return unexpectedErrorPosix(err),
+ }
+}
+
+pub fn linuxINotifyAddWatchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) !i32 {
+ const rc = linux.inotify_add_watch(inotify_fd, pathname, mask);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return @intCast(i32, rc),
+ posix.EACCES => return error.AccessDenied,
+ posix.EBADF => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EINVAL => unreachable,
+ posix.ENAMETOOLONG => return error.NameTooLong,
+ posix.ENOENT => return error.FileNotFound,
+ posix.ENOMEM => return error.SystemResources,
+ posix.ENOSPC => return error.UserResourceLimitReached,
+ else => return unexpectedErrorPosix(err),
+ }
+}
+
+pub fn linuxINotifyRmWatch(inotify_fd: i32, wd: i32) !void {
+ const rc = linux.inotify_rm_watch(inotify_fd, wd);
+ const err = posix.getErrno(rc);
+ switch (err) {
+ 0 => return rc,
+ posix.EBADF => unreachable,
+ posix.EINVAL => unreachable,
+ else => unreachable,
+ }
+}
diff --git a/std/os/linux/index.zig b/std/os/linux/index.zig
index 15ca649f06..c369921e14 100644
--- a/std/os/linux/index.zig
+++ b/std/os/linux/index.zig
@@ -567,6 +567,37 @@ pub const MNT_DETACH = 2;
pub const MNT_EXPIRE = 4;
pub const UMOUNT_NOFOLLOW = 8;
+pub const IN_CLOEXEC = O_CLOEXEC;
+pub const IN_NONBLOCK = O_NONBLOCK;
+
+pub const IN_ACCESS = 0x00000001;
+pub const IN_MODIFY = 0x00000002;
+pub const IN_ATTRIB = 0x00000004;
+pub const IN_CLOSE_WRITE = 0x00000008;
+pub const IN_CLOSE_NOWRITE = 0x00000010;
+pub const IN_CLOSE = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE;
+pub const IN_OPEN = 0x00000020;
+pub const IN_MOVED_FROM = 0x00000040;
+pub const IN_MOVED_TO = 0x00000080;
+pub const IN_MOVE = IN_MOVED_FROM | IN_MOVED_TO;
+pub const IN_CREATE = 0x00000100;
+pub const IN_DELETE = 0x00000200;
+pub const IN_DELETE_SELF = 0x00000400;
+pub const IN_MOVE_SELF = 0x00000800;
+pub const IN_ALL_EVENTS = 0x00000fff;
+
+pub const IN_UNMOUNT = 0x00002000;
+pub const IN_Q_OVERFLOW = 0x00004000;
+pub const IN_IGNORED = 0x00008000;
+
+pub const IN_ONLYDIR = 0x01000000;
+pub const IN_DONT_FOLLOW = 0x02000000;
+pub const IN_EXCL_UNLINK = 0x04000000;
+pub const IN_MASK_ADD = 0x20000000;
+
+pub const IN_ISDIR = 0x40000000;
+pub const IN_ONESHOT = 0x80000000;
+
pub const S_IFMT = 0o170000;
pub const S_IFDIR = 0o040000;
@@ -692,6 +723,10 @@ pub fn futex_wait(uaddr: usize, futex_op: u32, val: i32, timeout: ?*timespec) us
return syscall4(SYS_futex, uaddr, futex_op, @bitCast(u32, val), @ptrToInt(timeout));
}
+pub fn futex_wake(uaddr: usize, futex_op: u32, val: i32) usize {
+ return syscall3(SYS_futex, uaddr, futex_op, @bitCast(u32, val));
+}
+
pub fn getcwd(buf: [*]u8, size: usize) usize {
return syscall2(SYS_getcwd, @ptrToInt(buf), size);
}
@@ -700,6 +735,18 @@ pub fn getdents(fd: i32, dirp: [*]u8, count: usize) usize {
return syscall3(SYS_getdents, @intCast(usize, fd), @ptrToInt(dirp), count);
}
+pub fn inotify_init1(flags: u32) usize {
+ return syscall1(SYS_inotify_init1, flags);
+}
+
+pub fn inotify_add_watch(fd: i32, pathname: [*]const u8, mask: u32) usize {
+ return syscall3(SYS_inotify_add_watch, @intCast(usize, fd), @ptrToInt(pathname), mask);
+}
+
+pub fn inotify_rm_watch(fd: i32, wd: i32) usize {
+ return syscall2(SYS_inotify_rm_watch, @intCast(usize, fd), @intCast(usize, wd));
+}
+
pub fn isatty(fd: i32) bool {
var wsz: winsize = undefined;
return syscall3(SYS_ioctl, @intCast(usize, fd), TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
@@ -742,6 +789,14 @@ pub fn read(fd: i32, buf: [*]u8, count: usize) usize {
return syscall3(SYS_read, @intCast(usize, fd), @ptrToInt(buf), count);
}
+pub fn preadv(fd: i32, iov: [*]const iovec, count: usize, offset: u64) usize {
+ return syscall4(SYS_preadv, @intCast(usize, fd), @ptrToInt(iov), count, offset);
+}
+
+pub fn pwritev(fd: i32, iov: [*]const iovec_const, count: usize, offset: u64) usize {
+ return syscall4(SYS_pwritev, @intCast(usize, fd), @ptrToInt(iov), count, offset);
+}
+
// TODO https://github.com/ziglang/zig/issues/265
pub fn rmdir(path: [*]const u8) usize {
return syscall1(SYS_rmdir, @ptrToInt(path));
@@ -1064,6 +1119,11 @@ pub const iovec = extern struct {
iov_len: usize,
};
+pub const iovec_const = extern struct {
+ iov_base: [*]const u8,
+ iov_len: usize,
+};
+
pub fn getsockname(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize {
return syscall3(SYS_getsockname, @intCast(usize, fd), @ptrToInt(addr), @ptrToInt(len));
}
@@ -1372,6 +1432,14 @@ pub fn capset(hdrp: *cap_user_header_t, datap: *const cap_user_data_t) usize {
return syscall2(SYS_capset, @ptrToInt(hdrp), @ptrToInt(datap));
}
+pub const inotify_event = extern struct {
+ wd: i32,
+ mask: u32,
+ cookie: u32,
+ len: u32,
+ //name: [?]u8,
+};
+
test "import" {
if (builtin.os == builtin.Os.linux) {
_ = @import("test.zig");
diff --git a/std/os/path.zig b/std/os/path.zig
index d3ab0c519f..23c217b295 100644
--- a/std/os/path.zig
+++ b/std/os/path.zig
@@ -506,7 +506,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
result_index += 1;
}
- return result[0..result_index];
+ return allocator.shrink(u8, result, result_index);
}
/// This function is like a series of `cd` statements executed one after another.
diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig
index 90ccfaf6c5..bb055468a5 100644
--- a/std/os/windows/index.zig
+++ b/std/os/windows/index.zig
@@ -67,8 +67,9 @@ pub const INVALID_FILE_ATTRIBUTES = DWORD(@maxValue(DWORD));
pub const OVERLAPPED = extern struct {
Internal: ULONG_PTR,
InternalHigh: ULONG_PTR,
- Pointer: PVOID,
- hEvent: HANDLE,
+ Offset: DWORD,
+ OffsetHigh: DWORD,
+ hEvent: ?HANDLE,
};
pub const LPOVERLAPPED = *OVERLAPPED;
@@ -350,3 +351,15 @@ pub const E_ACCESSDENIED = @bitCast(c_long, c_ulong(0x80070005));
pub const E_HANDLE = @bitCast(c_long, c_ulong(0x80070006));
pub const E_OUTOFMEMORY = @bitCast(c_long, c_ulong(0x8007000E));
pub const E_INVALIDARG = @bitCast(c_long, c_ulong(0x80070057));
+
+pub const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
+pub const FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
+pub const FILE_FLAG_NO_BUFFERING = 0x20000000;
+pub const FILE_FLAG_OPEN_NO_RECALL = 0x00100000;
+pub const FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
+pub const FILE_FLAG_OVERLAPPED = 0x40000000;
+pub const FILE_FLAG_POSIX_SEMANTICS = 0x0100000;
+pub const FILE_FLAG_RANDOM_ACCESS = 0x10000000;
+pub const FILE_FLAG_SESSION_AWARE = 0x00800000;
+pub const FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;
+pub const FILE_FLAG_WRITE_THROUGH = 0x80000000;
diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig
index daeebf1021..0cdf27754a 100644
--- a/std/os/windows/kernel32.zig
+++ b/std/os/windows/kernel32.zig
@@ -1,5 +1,8 @@
use @import("index.zig");
+
+pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;
+
pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
pub extern "kernel32" stdcallcc fn CreateDirectoryA(
@@ -8,7 +11,17 @@ pub extern "kernel32" stdcallcc fn CreateDirectoryA(
) BOOL;
pub extern "kernel32" stdcallcc fn CreateFileA(
- lpFileName: LPCSTR,
+ lpFileName: [*]const u8, // TODO null terminated pointer type
+ dwDesiredAccess: DWORD,
+ dwShareMode: DWORD,
+ lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
+ dwCreationDisposition: DWORD,
+ dwFlagsAndAttributes: DWORD,
+ hTemplateFile: ?HANDLE,
+) HANDLE;
+
+pub extern "kernel32" stdcallcc fn CreateFileW(
+ lpFileName: [*]const u16, // TODO null terminated pointer type
dwDesiredAccess: DWORD,
dwShareMode: DWORD,
lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
@@ -94,6 +107,9 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
dwFlags: DWORD,
) DWORD;
+
+pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL;
+
pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;
pub extern "kernel32" stdcallcc fn GetQueuedCompletionStatus(CompletionPort: HANDLE, lpNumberOfBytesTransferred: LPDWORD, lpCompletionKey: *ULONG_PTR, lpOverlapped: *?*OVERLAPPED, dwMilliseconds: DWORD) BOOL;
@@ -104,7 +120,6 @@ pub extern "kernel32" stdcallcc fn HeapCreate(flOptions: DWORD, dwInitialSize: S
pub extern "kernel32" stdcallcc fn HeapDestroy(hHeap: HANDLE) BOOL;
pub extern "kernel32" stdcallcc fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void, dwBytes: SIZE_T) ?*c_void;
pub extern "kernel32" stdcallcc fn HeapSize(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) SIZE_T;
-pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) BOOL;
pub extern "kernel32" stdcallcc fn HeapCompact(hHeap: HANDLE, dwFlags: DWORD) SIZE_T;
pub extern "kernel32" stdcallcc fn HeapSummary(hHeap: HANDLE, dwFlags: DWORD, lpSummary: LPHEAP_SUMMARY) BOOL;
@@ -114,6 +129,8 @@ pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBy
pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void) BOOL;
+pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL;
+
pub extern "kernel32" stdcallcc fn MoveFileExA(
lpExistingFileName: LPCSTR,
lpNewFileName: LPCSTR,
@@ -126,11 +143,22 @@ pub extern "kernel32" stdcallcc fn QueryPerformanceCounter(lpPerformanceCount: *
pub extern "kernel32" stdcallcc fn QueryPerformanceFrequency(lpFrequency: *LARGE_INTEGER) BOOL;
+pub extern "kernel32" stdcallcc fn ReadDirectoryChangesW(
+ hDirectory: HANDLE,
+ lpBuffer: [*]align(@alignOf(FILE_NOTIFY_INFORMATION)) u8,
+ nBufferLength: DWORD,
+ bWatchSubtree: BOOL,
+ dwNotifyFilter: DWORD,
+ lpBytesReturned: ?*DWORD,
+ lpOverlapped: ?*OVERLAPPED,
+ lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE,
+) BOOL;
+
pub extern "kernel32" stdcallcc fn ReadFile(
in_hFile: HANDLE,
- out_lpBuffer: *c_void,
+ out_lpBuffer: [*]u8,
in_nNumberOfBytesToRead: DWORD,
- out_lpNumberOfBytesRead: *DWORD,
+ out_lpNumberOfBytesRead: ?*DWORD,
in_out_lpOverlapped: ?*OVERLAPPED,
) BOOL;
@@ -153,13 +181,42 @@ pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMillis
pub extern "kernel32" stdcallcc fn WriteFile(
in_hFile: HANDLE,
- in_lpBuffer: *const c_void,
+ in_lpBuffer: [*]const u8,
in_nNumberOfBytesToWrite: DWORD,
out_lpNumberOfBytesWritten: ?*DWORD,
in_out_lpOverlapped: ?*OVERLAPPED,
) BOOL;
+pub extern "kernel32" stdcallcc fn WriteFileEx(hFile: HANDLE, lpBuffer: [*]const u8, nNumberOfBytesToWrite: DWORD, lpOverlapped: LPOVERLAPPED, lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE) BOOL;
+
//TODO: call unicode versions instead of relying on ANSI code page
pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
+
+
+pub const FILE_NOTIFY_INFORMATION = extern struct {
+ NextEntryOffset: DWORD,
+ Action: DWORD,
+ FileNameLength: DWORD,
+ FileName: [1]WCHAR,
+};
+
+pub const FILE_ACTION_ADDED = 0x00000001;
+pub const FILE_ACTION_REMOVED = 0x00000002;
+pub const FILE_ACTION_MODIFIED = 0x00000003;
+pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
+pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
+
+pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn(DWORD, DWORD, *OVERLAPPED) void;
+
+pub const FILE_LIST_DIRECTORY = 1;
+
+pub const FILE_NOTIFY_CHANGE_CREATION = 64;
+pub const FILE_NOTIFY_CHANGE_SIZE = 8;
+pub const FILE_NOTIFY_CHANGE_SECURITY = 256;
+pub const FILE_NOTIFY_CHANGE_LAST_ACCESS = 32;
+pub const FILE_NOTIFY_CHANGE_LAST_WRITE = 16;
+pub const FILE_NOTIFY_CHANGE_DIR_NAME = 2;
+pub const FILE_NOTIFY_CHANGE_FILE_NAME = 1;
+pub const FILE_NOTIFY_CHANGE_ATTRIBUTES = 4;
diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig
index c9d2c3c3e6..2f9f4f2c72 100644
--- a/std/os/windows/util.zig
+++ b/std/os/windows/util.zig
@@ -36,20 +36,19 @@ pub fn windowsClose(handle: windows.HANDLE) void {
pub const WriteError = error{
SystemResources,
OperationAborted,
- IoPending,
BrokenPipe,
Unexpected,
};
pub fn windowsWrite(handle: windows.HANDLE, bytes: []const u8) WriteError!void {
- if (windows.WriteFile(handle, @ptrCast(*const c_void, bytes.ptr), @intCast(u32, bytes.len), null, null) == 0) {
+ if (windows.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), null, null) == 0) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.INVALID_USER_BUFFER => WriteError.SystemResources,
windows.ERROR.NOT_ENOUGH_MEMORY => WriteError.SystemResources,
windows.ERROR.OPERATION_ABORTED => WriteError.OperationAborted,
windows.ERROR.NOT_ENOUGH_QUOTA => WriteError.SystemResources,
- windows.ERROR.IO_PENDING => WriteError.IoPending,
+ windows.ERROR.IO_PENDING => unreachable,
windows.ERROR.BROKEN_PIPE => WriteError.BrokenPipe,
else => os.unexpectedErrorWindows(err),
};
@@ -221,6 +220,7 @@ pub fn windowsCreateIoCompletionPort(file_handle: windows.HANDLE, existing_compl
const handle = windows.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse {
const err = windows.GetLastError();
switch (err) {
+ windows.ERROR.INVALID_PARAMETER => unreachable,
else => return os.unexpectedErrorWindows(err),
}
};
@@ -238,21 +238,24 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_
}
}
-pub const WindowsWaitResult = error{
+pub const WindowsWaitResult = enum{
Normal,
Aborted,
+ Cancelled,
};
pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_transferred_count: *windows.DWORD, lpCompletionKey: *usize, lpOverlapped: *?*windows.OVERLAPPED, dwMilliseconds: windows.DWORD) WindowsWaitResult {
if (windows.GetQueuedCompletionStatus(completion_port, bytes_transferred_count, lpCompletionKey, lpOverlapped, dwMilliseconds) == windows.FALSE) {
- if (std.debug.runtime_safety) {
- const err = windows.GetLastError();
- if (err != windows.ERROR.ABANDONED_WAIT_0) {
- std.debug.warn("err: {}\n", err);
+ const err = windows.GetLastError();
+ switch (err) {
+ windows.ERROR.ABANDONED_WAIT_0 => return WindowsWaitResult.Aborted,
+ windows.ERROR.OPERATION_ABORTED => return WindowsWaitResult.Cancelled,
+ else => {
+ if (std.debug.runtime_safety) {
+ std.debug.panic("unexpected error: {}\n", err);
+ }
}
- assert(err == windows.ERROR.ABANDONED_WAIT_0);
}
- return WindowsWaitResult.Aborted;
}
return WindowsWaitResult.Normal;
}
diff --git a/std/segmented_list.zig b/std/segmented_list.zig
index 6e3f32e9d6..c6d8effdd2 100644
--- a/std/segmented_list.zig
+++ b/std/segmented_list.zig
@@ -2,7 +2,7 @@ const std = @import("index.zig");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
-// Imagine that `fn at(self: &Self, index: usize) &T` is a customer asking for a box
+// Imagine that `fn at(self: *Self, index: usize) &T` is a customer asking for a box
// from a warehouse, based on a flat array, boxes ordered from 0 to N - 1.
// But the warehouse actually stores boxes in shelves of increasing powers of 2 sizes.
// So when the customer requests a box index, we have to translate it to shelf index
@@ -93,6 +93,14 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type
pub const prealloc_count = prealloc_item_count;
+ fn AtType(comptime SelfType: type) type {
+ if (@typeInfo(SelfType).Pointer.is_const) {
+ return *const T;
+ } else {
+ return *T;
+ }
+ }
+
/// Deinitialize with `deinit`
pub fn init(allocator: *Allocator) Self {
return Self{
@@ -109,7 +117,7 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type
self.* = undefined;
}
- pub fn at(self: *Self, i: usize) *T {
+ pub fn at(self: var, i: usize) AtType(@typeOf(self)) {
assert(i < self.len);
return self.uncheckedAt(i);
}
@@ -133,7 +141,7 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type
if (self.len == 0) return null;
const index = self.len - 1;
- const result = self.uncheckedAt(index).*;
+ const result = uncheckedAt(self, index).*;
self.len = index;
return result;
}
@@ -141,7 +149,7 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type
pub fn addOne(self: *Self) !*T {
const new_length = self.len + 1;
try self.growCapacity(new_length);
- const result = self.uncheckedAt(self.len);
+ const result = uncheckedAt(self, self.len);
self.len = new_length;
return result;
}
@@ -193,7 +201,7 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type
self.dynamic_segments = self.allocator.shrink([*]T, self.dynamic_segments, new_cap_shelf_count);
}
- pub fn uncheckedAt(self: *Self, index: usize) *T {
+ pub fn uncheckedAt(self: var, index: usize) AtType(@typeOf(self)) {
if (index < prealloc_item_count) {
return &self.prealloc_segment[index];
}
diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig
index 2f073b3e98..982c60aed8 100644
--- a/std/special/build_runner.zig
+++ b/std/special/build_runner.zig
@@ -72,10 +72,10 @@ pub fn main() !void {
if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
const option_name = option_contents[0..name_end];
const option_value = option_contents[name_end + 1 ..];
- if (builder.addUserInputOption(option_name, option_value))
+ if (try builder.addUserInputOption(option_name, option_value))
return usageAndErr(&builder, false, try stderr_stream);
} else {
- if (builder.addUserInputFlag(option_contents))
+ if (try builder.addUserInputFlag(option_contents))
return usageAndErr(&builder, false, try stderr_stream);
}
} else if (mem.startsWith(u8, arg, "-")) {
diff --git a/std/unicode.zig b/std/unicode.zig
index 8a9d4a9214..0e7b4cdc3e 100644
--- a/std/unicode.zig
+++ b/std/unicode.zig
@@ -188,6 +188,7 @@ pub const Utf8View = struct {
return Utf8View{ .bytes = s };
}
+ /// TODO: https://github.com/ziglang/zig/issues/425
pub fn initComptime(comptime s: []const u8) Utf8View {
if (comptime init(s)) |r| {
return r;
@@ -199,7 +200,7 @@ pub const Utf8View = struct {
}
}
- pub fn iterator(s: *const Utf8View) Utf8Iterator {
+ pub fn iterator(s: Utf8View) Utf8Iterator {
return Utf8Iterator{
.bytes = s.bytes,
.i = 0,
@@ -530,3 +531,20 @@ test "utf16leToUtf8" {
assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));
}
}
+
+/// TODO support codepoints bigger than 16 bits
+/// TODO type for null terminated pointer
+pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![]u16 {
+ var result = std.ArrayList(u16).init(allocator);
+ // optimistically guess that it will not require surrogate pairs
+ try result.ensureCapacity(utf8.len + 1);
+
+ const view = try Utf8View.init(utf8);
+ var it = view.iterator();
+ while (it.nextCodepoint()) |codepoint| {
+ try result.append(@intCast(u16, codepoint)); // TODO surrogate pairs
+ }
+
+ try result.append(0);
+ return result.toOwnedSlice();
+}
diff --git a/std/zig/ast.zig b/std/zig/ast.zig
index 95e899fb92..0046dff1a2 100644
--- a/std/zig/ast.zig
+++ b/std/zig/ast.zig
@@ -32,6 +32,12 @@ pub const Tree = struct {
return self.source[token.start..token.end];
}
+ pub fn getNodeSource(self: *const Tree, node: *const Node) []const u8 {
+ const first_token = self.tokens.at(node.firstToken());
+ const last_token = self.tokens.at(node.lastToken());
+ return self.source[first_token.start..last_token.end];
+ }
+
pub const Location = struct {
line: usize,
column: usize,
@@ -338,7 +344,7 @@ pub const Node = struct {
unreachable;
}
- pub fn firstToken(base: *Node) TokenIndex {
+ pub fn firstToken(base: *const Node) TokenIndex {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
@@ -349,7 +355,7 @@ pub const Node = struct {
unreachable;
}
- pub fn lastToken(base: *Node) TokenIndex {
+ pub fn lastToken(base: *const Node) TokenIndex {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
@@ -473,11 +479,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Root) TokenIndex {
+ pub fn firstToken(self: *const Root) TokenIndex {
return if (self.decls.len == 0) self.eof_token else (self.decls.at(0).*).firstToken();
}
- pub fn lastToken(self: *Root) TokenIndex {
+ pub fn lastToken(self: *const Root) TokenIndex {
return if (self.decls.len == 0) self.eof_token else (self.decls.at(self.decls.len - 1).*).lastToken();
}
};
@@ -518,7 +524,7 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *VarDecl) TokenIndex {
+ pub fn firstToken(self: *const VarDecl) TokenIndex {
if (self.visib_token) |visib_token| return visib_token;
if (self.comptime_token) |comptime_token| return comptime_token;
if (self.extern_export_token) |extern_export_token| return extern_export_token;
@@ -526,7 +532,7 @@ pub const Node = struct {
return self.mut_token;
}
- pub fn lastToken(self: *VarDecl) TokenIndex {
+ pub fn lastToken(self: *const VarDecl) TokenIndex {
return self.semicolon_token;
}
};
@@ -548,12 +554,12 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Use) TokenIndex {
+ pub fn firstToken(self: *const Use) TokenIndex {
if (self.visib_token) |visib_token| return visib_token;
return self.use_token;
}
- pub fn lastToken(self: *Use) TokenIndex {
+ pub fn lastToken(self: *const Use) TokenIndex {
return self.semicolon_token;
}
};
@@ -575,11 +581,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *ErrorSetDecl) TokenIndex {
+ pub fn firstToken(self: *const ErrorSetDecl) TokenIndex {
return self.error_token;
}
- pub fn lastToken(self: *ErrorSetDecl) TokenIndex {
+ pub fn lastToken(self: *const ErrorSetDecl) TokenIndex {
return self.rbrace_token;
}
};
@@ -618,14 +624,14 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *ContainerDecl) TokenIndex {
+ pub fn firstToken(self: *const ContainerDecl) TokenIndex {
if (self.layout_token) |layout_token| {
return layout_token;
}
return self.kind_token;
}
- pub fn lastToken(self: *ContainerDecl) TokenIndex {
+ pub fn lastToken(self: *const ContainerDecl) TokenIndex {
return self.rbrace_token;
}
};
@@ -646,12 +652,12 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *StructField) TokenIndex {
+ pub fn firstToken(self: *const StructField) TokenIndex {
if (self.visib_token) |visib_token| return visib_token;
return self.name_token;
}
- pub fn lastToken(self: *StructField) TokenIndex {
+ pub fn lastToken(self: *const StructField) TokenIndex {
return self.type_expr.lastToken();
}
};
@@ -679,11 +685,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *UnionTag) TokenIndex {
+ pub fn firstToken(self: *const UnionTag) TokenIndex {
return self.name_token;
}
- pub fn lastToken(self: *UnionTag) TokenIndex {
+ pub fn lastToken(self: *const UnionTag) TokenIndex {
if (self.value_expr) |value_expr| {
return value_expr.lastToken();
}
@@ -712,11 +718,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *EnumTag) TokenIndex {
+ pub fn firstToken(self: *const EnumTag) TokenIndex {
return self.name_token;
}
- pub fn lastToken(self: *EnumTag) TokenIndex {
+ pub fn lastToken(self: *const EnumTag) TokenIndex {
if (self.value) |value| {
return value.lastToken();
}
@@ -741,11 +747,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *ErrorTag) TokenIndex {
+ pub fn firstToken(self: *const ErrorTag) TokenIndex {
return self.name_token;
}
- pub fn lastToken(self: *ErrorTag) TokenIndex {
+ pub fn lastToken(self: *const ErrorTag) TokenIndex {
return self.name_token;
}
};
@@ -758,11 +764,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Identifier) TokenIndex {
+ pub fn firstToken(self: *const Identifier) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *Identifier) TokenIndex {
+ pub fn lastToken(self: *const Identifier) TokenIndex {
return self.token;
}
};
@@ -784,11 +790,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *AsyncAttribute) TokenIndex {
+ pub fn firstToken(self: *const AsyncAttribute) TokenIndex {
return self.async_token;
}
- pub fn lastToken(self: *AsyncAttribute) TokenIndex {
+ pub fn lastToken(self: *const AsyncAttribute) TokenIndex {
if (self.rangle_bracket) |rangle_bracket| {
return rangle_bracket;
}
@@ -856,7 +862,7 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *FnProto) TokenIndex {
+ pub fn firstToken(self: *const FnProto) TokenIndex {
if (self.visib_token) |visib_token| return visib_token;
if (self.async_attr) |async_attr| return async_attr.firstToken();
if (self.extern_export_inline_token) |extern_export_inline_token| return extern_export_inline_token;
@@ -865,7 +871,7 @@ pub const Node = struct {
return self.fn_token;
}
- pub fn lastToken(self: *FnProto) TokenIndex {
+ pub fn lastToken(self: *const FnProto) TokenIndex {
if (self.body_node) |body_node| return body_node.lastToken();
switch (self.return_type) {
// TODO allow this and next prong to share bodies since the types are the same
@@ -896,11 +902,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *PromiseType) TokenIndex {
+ pub fn firstToken(self: *const PromiseType) TokenIndex {
return self.promise_token;
}
- pub fn lastToken(self: *PromiseType) TokenIndex {
+ pub fn lastToken(self: *const PromiseType) TokenIndex {
if (self.result) |result| return result.return_type.lastToken();
return self.promise_token;
}
@@ -923,14 +929,14 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *ParamDecl) TokenIndex {
+ pub fn firstToken(self: *const ParamDecl) TokenIndex {
if (self.comptime_token) |comptime_token| return comptime_token;
if (self.noalias_token) |noalias_token| return noalias_token;
if (self.name_token) |name_token| return name_token;
return self.type_node.firstToken();
}
- pub fn lastToken(self: *ParamDecl) TokenIndex {
+ pub fn lastToken(self: *const ParamDecl) TokenIndex {
if (self.var_args_token) |var_args_token| return var_args_token;
return self.type_node.lastToken();
}
@@ -954,7 +960,7 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Block) TokenIndex {
+ pub fn firstToken(self: *const Block) TokenIndex {
if (self.label) |label| {
return label;
}
@@ -962,7 +968,7 @@ pub const Node = struct {
return self.lbrace;
}
- pub fn lastToken(self: *Block) TokenIndex {
+ pub fn lastToken(self: *const Block) TokenIndex {
return self.rbrace;
}
};
@@ -981,11 +987,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Defer) TokenIndex {
+ pub fn firstToken(self: *const Defer) TokenIndex {
return self.defer_token;
}
- pub fn lastToken(self: *Defer) TokenIndex {
+ pub fn lastToken(self: *const Defer) TokenIndex {
return self.expr.lastToken();
}
};
@@ -1005,11 +1011,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Comptime) TokenIndex {
+ pub fn firstToken(self: *const Comptime) TokenIndex {
return self.comptime_token;
}
- pub fn lastToken(self: *Comptime) TokenIndex {
+ pub fn lastToken(self: *const Comptime) TokenIndex {
return self.expr.lastToken();
}
};
@@ -1029,11 +1035,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Payload) TokenIndex {
+ pub fn firstToken(self: *const Payload) TokenIndex {
return self.lpipe;
}
- pub fn lastToken(self: *Payload) TokenIndex {
+ pub fn lastToken(self: *const Payload) TokenIndex {
return self.rpipe;
}
};
@@ -1054,11 +1060,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *PointerPayload) TokenIndex {
+ pub fn firstToken(self: *const PointerPayload) TokenIndex {
return self.lpipe;
}
- pub fn lastToken(self: *PointerPayload) TokenIndex {
+ pub fn lastToken(self: *const PointerPayload) TokenIndex {
return self.rpipe;
}
};
@@ -1085,11 +1091,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *PointerIndexPayload) TokenIndex {
+ pub fn firstToken(self: *const PointerIndexPayload) TokenIndex {
return self.lpipe;
}
- pub fn lastToken(self: *PointerIndexPayload) TokenIndex {
+ pub fn lastToken(self: *const PointerIndexPayload) TokenIndex {
return self.rpipe;
}
};
@@ -1114,11 +1120,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Else) TokenIndex {
+ pub fn firstToken(self: *const Else) TokenIndex {
return self.else_token;
}
- pub fn lastToken(self: *Else) TokenIndex {
+ pub fn lastToken(self: *const Else) TokenIndex {
return self.body.lastToken();
}
};
@@ -1146,11 +1152,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Switch) TokenIndex {
+ pub fn firstToken(self: *const Switch) TokenIndex {
return self.switch_token;
}
- pub fn lastToken(self: *Switch) TokenIndex {
+ pub fn lastToken(self: *const Switch) TokenIndex {
return self.rbrace;
}
};
@@ -1181,11 +1187,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *SwitchCase) TokenIndex {
+ pub fn firstToken(self: *const SwitchCase) TokenIndex {
return (self.items.at(0).*).firstToken();
}
- pub fn lastToken(self: *SwitchCase) TokenIndex {
+ pub fn lastToken(self: *const SwitchCase) TokenIndex {
return self.expr.lastToken();
}
};
@@ -1198,11 +1204,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *SwitchElse) TokenIndex {
+ pub fn firstToken(self: *const SwitchElse) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *SwitchElse) TokenIndex {
+ pub fn lastToken(self: *const SwitchElse) TokenIndex {
return self.token;
}
};
@@ -1245,7 +1251,7 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *While) TokenIndex {
+ pub fn firstToken(self: *const While) TokenIndex {
if (self.label) |label| {
return label;
}
@@ -1257,7 +1263,7 @@ pub const Node = struct {
return self.while_token;
}
- pub fn lastToken(self: *While) TokenIndex {
+ pub fn lastToken(self: *const While) TokenIndex {
if (self.@"else") |@"else"| {
return @"else".body.lastToken();
}
@@ -1298,7 +1304,7 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *For) TokenIndex {
+ pub fn firstToken(self: *const For) TokenIndex {
if (self.label) |label| {
return label;
}
@@ -1310,7 +1316,7 @@ pub const Node = struct {
return self.for_token;
}
- pub fn lastToken(self: *For) TokenIndex {
+ pub fn lastToken(self: *const For) TokenIndex {
if (self.@"else") |@"else"| {
return @"else".body.lastToken();
}
@@ -1349,11 +1355,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *If) TokenIndex {
+ pub fn firstToken(self: *const If) TokenIndex {
return self.if_token;
}
- pub fn lastToken(self: *If) TokenIndex {
+ pub fn lastToken(self: *const If) TokenIndex {
if (self.@"else") |@"else"| {
return @"else".body.lastToken();
}
@@ -1480,11 +1486,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *InfixOp) TokenIndex {
+ pub fn firstToken(self: *const InfixOp) TokenIndex {
return self.lhs.firstToken();
}
- pub fn lastToken(self: *InfixOp) TokenIndex {
+ pub fn lastToken(self: *const InfixOp) TokenIndex {
return self.rhs.lastToken();
}
};
@@ -1570,11 +1576,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *PrefixOp) TokenIndex {
+ pub fn firstToken(self: *const PrefixOp) TokenIndex {
return self.op_token;
}
- pub fn lastToken(self: *PrefixOp) TokenIndex {
+ pub fn lastToken(self: *const PrefixOp) TokenIndex {
return self.rhs.lastToken();
}
};
@@ -1594,11 +1600,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *FieldInitializer) TokenIndex {
+ pub fn firstToken(self: *const FieldInitializer) TokenIndex {
return self.period_token;
}
- pub fn lastToken(self: *FieldInitializer) TokenIndex {
+ pub fn lastToken(self: *const FieldInitializer) TokenIndex {
return self.expr.lastToken();
}
};
@@ -1673,7 +1679,7 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *SuffixOp) TokenIndex {
+ pub fn firstToken(self: *const SuffixOp) TokenIndex {
switch (self.op) {
@TagType(Op).Call => |*call_info| if (call_info.async_attr) |async_attr| return async_attr.firstToken(),
else => {},
@@ -1681,7 +1687,7 @@ pub const Node = struct {
return self.lhs.firstToken();
}
- pub fn lastToken(self: *SuffixOp) TokenIndex {
+ pub fn lastToken(self: *const SuffixOp) TokenIndex {
return self.rtoken;
}
};
@@ -1701,11 +1707,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *GroupedExpression) TokenIndex {
+ pub fn firstToken(self: *const GroupedExpression) TokenIndex {
return self.lparen;
}
- pub fn lastToken(self: *GroupedExpression) TokenIndex {
+ pub fn lastToken(self: *const GroupedExpression) TokenIndex {
return self.rparen;
}
};
@@ -1749,11 +1755,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *ControlFlowExpression) TokenIndex {
+ pub fn firstToken(self: *const ControlFlowExpression) TokenIndex {
return self.ltoken;
}
- pub fn lastToken(self: *ControlFlowExpression) TokenIndex {
+ pub fn lastToken(self: *const ControlFlowExpression) TokenIndex {
if (self.rhs) |rhs| {
return rhs.lastToken();
}
@@ -1792,11 +1798,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Suspend) TokenIndex {
+ pub fn firstToken(self: *const Suspend) TokenIndex {
return self.suspend_token;
}
- pub fn lastToken(self: *Suspend) TokenIndex {
+ pub fn lastToken(self: *const Suspend) TokenIndex {
if (self.body) |body| {
return body.lastToken();
}
@@ -1813,11 +1819,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *IntegerLiteral) TokenIndex {
+ pub fn firstToken(self: *const IntegerLiteral) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *IntegerLiteral) TokenIndex {
+ pub fn lastToken(self: *const IntegerLiteral) TokenIndex {
return self.token;
}
};
@@ -1830,11 +1836,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *FloatLiteral) TokenIndex {
+ pub fn firstToken(self: *const FloatLiteral) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *FloatLiteral) TokenIndex {
+ pub fn lastToken(self: *const FloatLiteral) TokenIndex {
return self.token;
}
};
@@ -1856,11 +1862,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *BuiltinCall) TokenIndex {
+ pub fn firstToken(self: *const BuiltinCall) TokenIndex {
return self.builtin_token;
}
- pub fn lastToken(self: *BuiltinCall) TokenIndex {
+ pub fn lastToken(self: *const BuiltinCall) TokenIndex {
return self.rparen_token;
}
};
@@ -1873,11 +1879,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *StringLiteral) TokenIndex {
+ pub fn firstToken(self: *const StringLiteral) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *StringLiteral) TokenIndex {
+ pub fn lastToken(self: *const StringLiteral) TokenIndex {
return self.token;
}
};
@@ -1892,11 +1898,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *MultilineStringLiteral) TokenIndex {
+ pub fn firstToken(self: *const MultilineStringLiteral) TokenIndex {
return self.lines.at(0).*;
}
- pub fn lastToken(self: *MultilineStringLiteral) TokenIndex {
+ pub fn lastToken(self: *const MultilineStringLiteral) TokenIndex {
return self.lines.at(self.lines.len - 1).*;
}
};
@@ -1909,11 +1915,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *CharLiteral) TokenIndex {
+ pub fn firstToken(self: *const CharLiteral) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *CharLiteral) TokenIndex {
+ pub fn lastToken(self: *const CharLiteral) TokenIndex {
return self.token;
}
};
@@ -1926,11 +1932,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *BoolLiteral) TokenIndex {
+ pub fn firstToken(self: *const BoolLiteral) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *BoolLiteral) TokenIndex {
+ pub fn lastToken(self: *const BoolLiteral) TokenIndex {
return self.token;
}
};
@@ -1943,11 +1949,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *NullLiteral) TokenIndex {
+ pub fn firstToken(self: *const NullLiteral) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *NullLiteral) TokenIndex {
+ pub fn lastToken(self: *const NullLiteral) TokenIndex {
return self.token;
}
};
@@ -1960,11 +1966,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *UndefinedLiteral) TokenIndex {
+ pub fn firstToken(self: *const UndefinedLiteral) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *UndefinedLiteral) TokenIndex {
+ pub fn lastToken(self: *const UndefinedLiteral) TokenIndex {
return self.token;
}
};
@@ -1977,11 +1983,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *ThisLiteral) TokenIndex {
+ pub fn firstToken(self: *const ThisLiteral) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *ThisLiteral) TokenIndex {
+ pub fn lastToken(self: *const ThisLiteral) TokenIndex {
return self.token;
}
};
@@ -2022,11 +2028,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *AsmOutput) TokenIndex {
+ pub fn firstToken(self: *const AsmOutput) TokenIndex {
return self.lbracket;
}
- pub fn lastToken(self: *AsmOutput) TokenIndex {
+ pub fn lastToken(self: *const AsmOutput) TokenIndex {
return self.rparen;
}
};
@@ -2054,11 +2060,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *AsmInput) TokenIndex {
+ pub fn firstToken(self: *const AsmInput) TokenIndex {
return self.lbracket;
}
- pub fn lastToken(self: *AsmInput) TokenIndex {
+ pub fn lastToken(self: *const AsmInput) TokenIndex {
return self.rparen;
}
};
@@ -2089,11 +2095,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Asm) TokenIndex {
+ pub fn firstToken(self: *const Asm) TokenIndex {
return self.asm_token;
}
- pub fn lastToken(self: *Asm) TokenIndex {
+ pub fn lastToken(self: *const Asm) TokenIndex {
return self.rparen;
}
};
@@ -2106,11 +2112,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *Unreachable) TokenIndex {
+ pub fn firstToken(self: *const Unreachable) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *Unreachable) TokenIndex {
+ pub fn lastToken(self: *const Unreachable) TokenIndex {
return self.token;
}
};
@@ -2123,11 +2129,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *ErrorType) TokenIndex {
+ pub fn firstToken(self: *const ErrorType) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *ErrorType) TokenIndex {
+ pub fn lastToken(self: *const ErrorType) TokenIndex {
return self.token;
}
};
@@ -2140,11 +2146,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *VarType) TokenIndex {
+ pub fn firstToken(self: *const VarType) TokenIndex {
return self.token;
}
- pub fn lastToken(self: *VarType) TokenIndex {
+ pub fn lastToken(self: *const VarType) TokenIndex {
return self.token;
}
};
@@ -2159,11 +2165,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *DocComment) TokenIndex {
+ pub fn firstToken(self: *const DocComment) TokenIndex {
return self.lines.at(0).*;
}
- pub fn lastToken(self: *DocComment) TokenIndex {
+ pub fn lastToken(self: *const DocComment) TokenIndex {
return self.lines.at(self.lines.len - 1).*;
}
};
@@ -2184,11 +2190,11 @@ pub const Node = struct {
return null;
}
- pub fn firstToken(self: *TestDecl) TokenIndex {
+ pub fn firstToken(self: *const TestDecl) TokenIndex {
return self.test_token;
}
- pub fn lastToken(self: *TestDecl) TokenIndex {
+ pub fn lastToken(self: *const TestDecl) TokenIndex {
return self.body_node.lastToken();
}
};