std lib networking improvements, especially non-blocking I/O

* delete the std/event/net directory
 * `std.event.Loop.waitUntilFdReadable` and related functions
   no longer have possibility of failure. On Linux, they fall
   back to poll() and then fall back to sleep().
 * add some missing `noasync` decorations in `std.event.Loop`
 * redo the `std.net.Server` API. it's quite nice now, but
   shutdown does not work cleanly. There is a race condition with
   close() that I am actively working on.
 * move `std.io.OutStream` to its own file to match `std.io.InStream`.
   I started working on making `write` integrated with evented I/O,
   but it got tricky so I backed off and filed #3557. However
   I did integrate `std.os.writev` and `std.os.pwritev` with evented I/O.
 * add `std.Target.stack_align`
 * move networking tests to `lib/std/net/test.zig`
 * add `std.net.tcpConnectToHost` and `std.net.tcpConnectToAddress`.
 * rename `error.UnknownName` to `error.UnknownHostName` within the
   context of DNS resolution.
 * add `std.os.readv`, which is integrated with evented I/O.
 * `std.os.preadv`, is now integrated with evented I/O.
 * `std.os.accept4` now asserts that ENOTSOCK and EOPNOTSUPP never
    occur (misuse of API), instead of returning errors.
 * `std.os.connect` is now integrated with evented I/O.
   `std.os.connect_async` is gone. Just use `std.os.connect`.
 * fix false positive dependency loop regarding async function frames
 * add more compile notes to help when dependency loops occur
   in determining whether a function is async.
 * ir: change an assert to ir_assert to make it easier to find
   workarounds for when such an assert is triggered. In this case
   it was trying to parse an IPv4 address at comptime.
This commit is contained in:
Andrew Kelley
2019-10-29 22:59:30 -04:00
parent 8d3b7689ad
commit c3d816a98e
15 changed files with 512 additions and 556 deletions

View File

@@ -448,26 +448,67 @@ pub const Loop = struct {
self.finishOneEvent();
}
pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
defer self.linuxRemoveFd(fd);
pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) void {
assert(flags & os.EPOLLET == os.EPOLLET);
assert(flags & os.EPOLLONESHOT == os.EPOLLONESHOT);
var resume_node = ResumeNode.Basic{
.base = ResumeNode{
.id = .Basic,
.handle = @frame(),
.overlapped = ResumeNode.overlapped_init,
},
};
var need_to_delete = false;
defer if (need_to_delete) self.linuxRemoveFd(fd);
suspend {
var resume_node = ResumeNode.Basic{
.base = ResumeNode{
.id = .Basic,
.handle = @frame(),
.overlapped = ResumeNode.overlapped_init,
if (self.linuxAddFd(fd, &resume_node.base, flags)) |_| {
need_to_delete = true;
} else |err| switch (err) {
error.FileDescriptorNotRegistered => unreachable,
error.OperationCausesCircularLoop => unreachable,
error.FileDescriptorIncompatibleWithEpoll => unreachable,
error.FileDescriptorAlreadyPresentInSet => unreachable, // evented writes to the same fd is not thread-safe
error.SystemResources,
error.UserResourceLimitReached,
error.Unexpected,
=> {
// Fall back to a blocking poll(). Ideally this codepath is never hit, since
// epoll should be just fine. But this is better than incorrect behavior.
var poll_flags: i16 = 0;
if ((flags & os.EPOLLIN) != 0) poll_flags |= os.POLLIN;
if ((flags & os.EPOLLOUT) != 0) poll_flags |= os.POLLOUT;
var pfd = [1]os.pollfd{os.pollfd{
.fd = fd,
.events = poll_flags,
.revents = undefined,
}};
_ = os.poll(&pfd, -1) catch |poll_err| switch (poll_err) {
error.SystemResources,
error.Unexpected,
=> {
// Even poll() didn't work. The best we can do now is sleep for a
// small duration and then hope that something changed.
std.time.sleep(1 * std.time.millisecond);
},
};
resume @frame();
},
};
try self.linuxAddFd(fd, &resume_node.base, flags);
}
}
}
pub fn waitUntilFdReadable(self: *Loop, fd: os.fd_t) !void {
return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN);
pub fn waitUntilFdReadable(self: *Loop, fd: os.fd_t) void {
return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLIN);
}
pub fn waitUntilFdWritable(self: *Loop, fd: os.fd_t) !void {
return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT);
pub fn waitUntilFdWritable(self: *Loop, fd: os.fd_t) void {
return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLOUT);
}
pub fn waitUntilFdWritableOrReadable(self: *Loop, fd: os.fd_t) void {
return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLOUT | os.EPOLLIN);
}
pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !os.Kevent {
@@ -645,7 +686,7 @@ pub const Loop = struct {
.linux => {
self.posixFsRequest(&self.os_data.fs_end_request);
// writing 8 bytes to an eventfd cannot fail
os.write(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
noasync os.write(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
return;
},
.macosx, .freebsd, .netbsd => {
@@ -793,6 +834,8 @@ pub const Loop = struct {
}
}
// TODO make this whole function noasync
// https://github.com/ziglang/zig/issues/3157
fn posixFsRun(self: *Loop) void {
while (true) {
if (builtin.os == .linux) {
@@ -802,27 +845,27 @@ pub const Loop = struct {
switch (node.data.msg) {
.End => return,
.WriteV => |*msg| {
msg.result = os.writev(msg.fd, msg.iov);
msg.result = noasync os.writev(msg.fd, msg.iov);
},
.PWriteV => |*msg| {
msg.result = os.pwritev(msg.fd, msg.iov, msg.offset);
msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);
},
.PReadV => |*msg| {
msg.result = os.preadv(msg.fd, msg.iov, msg.offset);
msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);
},
.Open => |*msg| {
msg.result = os.openC(msg.path.ptr, msg.flags, msg.mode);
msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode);
},
.Close => |*msg| os.close(msg.fd),
.Close => |*msg| noasync os.close(msg.fd),
.WriteFile => |*msg| blk: {
const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
os.O_CLOEXEC | os.O_TRUNC;
const fd = os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
msg.result = err;
break :blk;
};
defer os.close(fd);
msg.result = os.write(fd, msg.contents);
defer noasync os.close(fd);
msg.result = noasync os.write(fd, msg.contents);
},
}
switch (node.data.finish) {