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:
parent
8d3b7689ad
commit
c3d816a98e
@ -7,7 +7,6 @@ pub const RwLock = @import("event/rwlock.zig").RwLock;
|
|||||||
pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
|
pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
|
||||||
pub const Loop = @import("event/loop.zig").Loop;
|
pub const Loop = @import("event/loop.zig").Loop;
|
||||||
pub const fs = @import("event/fs.zig");
|
pub const fs = @import("event/fs.zig");
|
||||||
pub const net = @import("event/net.zig");
|
|
||||||
|
|
||||||
test "import event tests" {
|
test "import event tests" {
|
||||||
_ = @import("event/channel.zig");
|
_ = @import("event/channel.zig");
|
||||||
@ -19,5 +18,4 @@ test "import event tests" {
|
|||||||
_ = @import("event/rwlock.zig");
|
_ = @import("event/rwlock.zig");
|
||||||
_ = @import("event/rwlocked.zig");
|
_ = @import("event/rwlocked.zig");
|
||||||
_ = @import("event/loop.zig");
|
_ = @import("event/loop.zig");
|
||||||
_ = @import("event/net.zig");
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ const assert = std.debug.assert;
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const Loop = std.event.Loop;
|
const Loop = std.event.Loop;
|
||||||
|
|
||||||
/// many producer, many consumer, thread-safe, 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 empty, consumers suspend and are resumed by producers.
|
||||||
/// when buffer is full, producers suspend and are resumed by consumers
|
/// When buffer is full, producers suspend and are resumed by consumers.
|
||||||
|
/// TODO now that async function rewrite has landed, this API should be adjusted
|
||||||
|
/// to not use the event loop's allocator, and to not require allocation.
|
||||||
pub fn Channel(comptime T: type) type {
|
pub fn Channel(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
loop: *Loop,
|
loop: *Loop,
|
||||||
@ -48,7 +50,7 @@ pub fn Channel(comptime T: type) type {
|
|||||||
tick_node: *Loop.NextTickNode,
|
tick_node: *Loop.NextTickNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// call destroy when done
|
/// Call `destroy` when done.
|
||||||
pub fn create(loop: *Loop, capacity: usize) !*SelfChannel {
|
pub fn create(loop: *Loop, capacity: usize) !*SelfChannel {
|
||||||
const buffer_nodes = try loop.allocator.alloc(T, capacity);
|
const buffer_nodes = try loop.allocator.alloc(T, capacity);
|
||||||
errdefer loop.allocator.free(buffer_nodes);
|
errdefer loop.allocator.free(buffer_nodes);
|
||||||
|
@ -448,26 +448,67 @@ pub const Loop = struct {
|
|||||||
self.finishOneEvent();
|
self.finishOneEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
|
pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) void {
|
||||||
defer self.linuxRemoveFd(fd);
|
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 {
|
suspend {
|
||||||
var resume_node = ResumeNode.Basic{
|
if (self.linuxAddFd(fd, &resume_node.base, flags)) |_| {
|
||||||
.base = ResumeNode{
|
need_to_delete = true;
|
||||||
.id = .Basic,
|
} else |err| switch (err) {
|
||||||
.handle = @frame(),
|
error.FileDescriptorNotRegistered => unreachable,
|
||||||
.overlapped = ResumeNode.overlapped_init,
|
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 {
|
pub fn waitUntilFdReadable(self: *Loop, fd: os.fd_t) void {
|
||||||
return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN);
|
return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn waitUntilFdWritable(self: *Loop, fd: os.fd_t) !void {
|
pub fn waitUntilFdWritable(self: *Loop, fd: os.fd_t) void {
|
||||||
return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT);
|
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 {
|
pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !os.Kevent {
|
||||||
@ -645,7 +686,7 @@ pub const Loop = struct {
|
|||||||
.linux => {
|
.linux => {
|
||||||
self.posixFsRequest(&self.os_data.fs_end_request);
|
self.posixFsRequest(&self.os_data.fs_end_request);
|
||||||
// writing 8 bytes to an eventfd cannot fail
|
// 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;
|
return;
|
||||||
},
|
},
|
||||||
.macosx, .freebsd, .netbsd => {
|
.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 {
|
fn posixFsRun(self: *Loop) void {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (builtin.os == .linux) {
|
if (builtin.os == .linux) {
|
||||||
@ -802,27 +845,27 @@ pub const Loop = struct {
|
|||||||
switch (node.data.msg) {
|
switch (node.data.msg) {
|
||||||
.End => return,
|
.End => return,
|
||||||
.WriteV => |*msg| {
|
.WriteV => |*msg| {
|
||||||
msg.result = os.writev(msg.fd, msg.iov);
|
msg.result = noasync os.writev(msg.fd, msg.iov);
|
||||||
},
|
},
|
||||||
.PWriteV => |*msg| {
|
.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| {
|
.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| {
|
.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: {
|
.WriteFile => |*msg| blk: {
|
||||||
const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
|
const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
|
||||||
os.O_CLOEXEC | os.O_TRUNC;
|
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;
|
msg.result = err;
|
||||||
break :blk;
|
break :blk;
|
||||||
};
|
};
|
||||||
defer os.close(fd);
|
defer noasync os.close(fd);
|
||||||
msg.result = os.write(fd, msg.contents);
|
msg.result = noasync os.write(fd, msg.contents);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
switch (node.data.finish) {
|
switch (node.data.finish) {
|
||||||
|
@ -1,358 +0,0 @@
|
|||||||
const std = @import("../std.zig");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
const testing = std.testing;
|
|
||||||
const event = std.event;
|
|
||||||
const mem = std.mem;
|
|
||||||
const os = std.os;
|
|
||||||
const Loop = std.event.Loop;
|
|
||||||
const File = std.fs.File;
|
|
||||||
const fd_t = os.fd_t;
|
|
||||||
|
|
||||||
pub const Server = struct {
|
|
||||||
handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
|
|
||||||
|
|
||||||
loop: *Loop,
|
|
||||||
sockfd: ?i32,
|
|
||||||
accept_frame: ?anyframe,
|
|
||||||
listen_address: std.net.Address,
|
|
||||||
|
|
||||||
waiting_for_emfile_node: PromiseNode,
|
|
||||||
listen_resume_node: event.Loop.ResumeNode,
|
|
||||||
|
|
||||||
const PromiseNode = std.TailQueue(anyframe).Node;
|
|
||||||
|
|
||||||
pub fn init(loop: *Loop) Server {
|
|
||||||
// TODO can't initialize handler here because we need well defined copy elision
|
|
||||||
return Server{
|
|
||||||
.loop = loop,
|
|
||||||
.sockfd = null,
|
|
||||||
.accept_frame = null,
|
|
||||||
.handleRequestFn = undefined,
|
|
||||||
.waiting_for_emfile_node = undefined,
|
|
||||||
.listen_address = undefined,
|
|
||||||
.listen_resume_node = event.Loop.ResumeNode{
|
|
||||||
.id = event.Loop.ResumeNode.Id.Basic,
|
|
||||||
.handle = undefined,
|
|
||||||
.overlapped = event.Loop.ResumeNode.overlapped_init,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn listen(
|
|
||||||
self: *Server,
|
|
||||||
address: *const std.net.Address,
|
|
||||||
handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
|
|
||||||
) !void {
|
|
||||||
self.handleRequestFn = handleRequestFn;
|
|
||||||
|
|
||||||
const sockfd = try os.socket(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK, os.PROTO_tcp);
|
|
||||||
errdefer os.close(sockfd);
|
|
||||||
self.sockfd = sockfd;
|
|
||||||
|
|
||||||
try os.bind(sockfd, &address.os_addr);
|
|
||||||
try os.listen(sockfd, os.SOMAXCONN);
|
|
||||||
self.listen_address = std.net.Address.initPosix(try os.getsockname(sockfd));
|
|
||||||
|
|
||||||
self.accept_frame = async Server.handler(self);
|
|
||||||
errdefer await self.accept_frame.?;
|
|
||||||
|
|
||||||
self.listen_resume_node.handle = self.accept_frame.?;
|
|
||||||
try self.loop.linuxAddFd(sockfd, &self.listen_resume_node, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
|
||||||
errdefer self.loop.removeFd(sockfd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop listening
|
|
||||||
pub fn close(self: *Server) void {
|
|
||||||
self.loop.linuxRemoveFd(self.sockfd.?);
|
|
||||||
if (self.sockfd) |fd| {
|
|
||||||
os.close(fd);
|
|
||||||
self.sockfd = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Server) void {
|
|
||||||
if (self.accept_frame) |accept_frame| await accept_frame;
|
|
||||||
if (self.sockfd) |sockfd| os.close(sockfd);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handler(self: *Server) void {
|
|
||||||
while (true) {
|
|
||||||
var accepted_addr: std.net.Address = undefined;
|
|
||||||
// TODO just inline the following function here and don't expose it as posixAsyncAccept
|
|
||||||
if (os.accept4_async(self.sockfd.?, &accepted_addr.os_addr, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC)) |accepted_fd| {
|
|
||||||
if (accepted_fd == -1) {
|
|
||||||
// would block
|
|
||||||
suspend; // we will get resumed by epoll_wait in the event loop
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var socket = File.openHandle(accepted_fd);
|
|
||||||
self.handleRequestFn(self, &accepted_addr, socket);
|
|
||||||
} else |err| switch (err) {
|
|
||||||
error.ProcessFdQuotaExceeded => @panic("TODO handle this error"),
|
|
||||||
error.ConnectionAborted => continue,
|
|
||||||
|
|
||||||
error.FileDescriptorNotASocket => unreachable,
|
|
||||||
error.OperationNotSupported => unreachable,
|
|
||||||
|
|
||||||
error.SystemFdQuotaExceeded, error.SystemResources, error.ProtocolFailure, error.BlockedByFirewall, error.Unexpected => {
|
|
||||||
@panic("TODO handle this error");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn connectUnixSocket(loop: *Loop, path: []const u8) !i32 {
|
|
||||||
const sockfd = try os.socket(
|
|
||||||
os.AF_UNIX,
|
|
||||||
os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
errdefer os.close(sockfd);
|
|
||||||
|
|
||||||
var sock_addr = os.sockaddr_un{
|
|
||||||
.family = os.AF_UNIX,
|
|
||||||
.path = undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (path.len > @typeOf(sock_addr.path).len) return error.NameTooLong;
|
|
||||||
mem.copy(u8, sock_addr.path[0..], path);
|
|
||||||
const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
|
|
||||||
try os.connect_async(sockfd, &sock_addr, size);
|
|
||||||
try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
|
||||||
try os.getsockoptError(sockfd);
|
|
||||||
|
|
||||||
return sockfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const ReadError = error{
|
|
||||||
SystemResources,
|
|
||||||
Unexpected,
|
|
||||||
UserResourceLimitReached,
|
|
||||||
InputOutput,
|
|
||||||
|
|
||||||
FileDescriptorNotRegistered, // TODO remove this possibility
|
|
||||||
OperationCausesCircularLoop, // TODO remove this possibility
|
|
||||||
FileDescriptorAlreadyPresentInSet, // TODO remove this possibility
|
|
||||||
FileDescriptorIncompatibleWithEpoll, // TODO remove this possibility
|
|
||||||
};
|
|
||||||
|
|
||||||
/// returns number of bytes read. 0 means EOF.
|
|
||||||
pub async fn read(loop: *std.event.Loop, fd: fd_t, buffer: []u8) ReadError!usize {
|
|
||||||
const iov = os.iovec{
|
|
||||||
.iov_base = buffer.ptr,
|
|
||||||
.iov_len = buffer.len,
|
|
||||||
};
|
|
||||||
const iovs: *const [1]os.iovec = &iov;
|
|
||||||
return readvPosix(loop, fd, iovs, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const WriteError = error{};
|
|
||||||
|
|
||||||
pub async fn write(loop: *std.event.Loop, fd: fd_t, buffer: []const u8) WriteError!void {
|
|
||||||
const iov = os.iovec_const{
|
|
||||||
.iov_base = buffer.ptr,
|
|
||||||
.iov_len = buffer.len,
|
|
||||||
};
|
|
||||||
const iovs: *const [1]os.iovec_const = &iov;
|
|
||||||
return writevPosix(loop, fd, iovs, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn writevPosix(loop: *Loop, fd: i32, iov: [*]const os.iovec_const, count: usize) !void {
|
|
||||||
while (true) {
|
|
||||||
switch (builtin.os) {
|
|
||||||
.macosx, .linux => {
|
|
||||||
switch (os.errno(os.system.writev(fd, iov, count))) {
|
|
||||||
0 => return,
|
|
||||||
os.EINTR => continue,
|
|
||||||
os.ESPIPE => unreachable,
|
|
||||||
os.EINVAL => unreachable,
|
|
||||||
os.EFAULT => unreachable,
|
|
||||||
os.EAGAIN => {
|
|
||||||
try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
os.EBADF => unreachable, // always a race condition
|
|
||||||
os.EDESTADDRREQ => unreachable, // connect was never called
|
|
||||||
os.EDQUOT => unreachable,
|
|
||||||
os.EFBIG => unreachable,
|
|
||||||
os.EIO => return error.InputOutput,
|
|
||||||
os.ENOSPC => unreachable,
|
|
||||||
os.EPERM => return error.AccessDenied,
|
|
||||||
os.EPIPE => unreachable,
|
|
||||||
else => |err| return os.unexpectedErrno(err),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => @compileError("Unsupported OS"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns number of bytes read. 0 means EOF.
|
|
||||||
pub async fn readvPosix(loop: *std.event.Loop, fd: i32, iov: [*]os.iovec, count: usize) !usize {
|
|
||||||
while (true) {
|
|
||||||
switch (builtin.os) {
|
|
||||||
builtin.Os.linux, builtin.Os.freebsd, builtin.Os.macosx => {
|
|
||||||
const rc = os.system.readv(fd, iov, count);
|
|
||||||
switch (os.errno(rc)) {
|
|
||||||
0 => return rc,
|
|
||||||
os.EINTR => continue,
|
|
||||||
os.EINVAL => unreachable,
|
|
||||||
os.EFAULT => unreachable,
|
|
||||||
os.EAGAIN => {
|
|
||||||
try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
os.EBADF => unreachable, // always a race condition
|
|
||||||
os.EIO => return error.InputOutput,
|
|
||||||
os.EISDIR => unreachable,
|
|
||||||
os.ENOBUFS => return error.SystemResources,
|
|
||||||
os.ENOMEM => return error.SystemResources,
|
|
||||||
else => |err| return os.unexpectedErrno(err),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => @compileError("Unsupported OS"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn writev(loop: *Loop, fd: fd_t, data: []const []const u8) !void {
|
|
||||||
const iovecs = try loop.allocator.alloc(os.iovec_const, data.len);
|
|
||||||
defer loop.allocator.free(iovecs);
|
|
||||||
|
|
||||||
for (data) |buf, i| {
|
|
||||||
iovecs[i] = os.iovec_const{
|
|
||||||
.iov_base = buf.ptr,
|
|
||||||
.iov_len = buf.len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return writevPosix(loop, fd, iovecs.ptr, data.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn readv(loop: *Loop, fd: fd_t, data: []const []u8) !usize {
|
|
||||||
const iovecs = try loop.allocator.alloc(os.iovec, data.len);
|
|
||||||
defer loop.allocator.free(iovecs);
|
|
||||||
|
|
||||||
for (data) |buf, i| {
|
|
||||||
iovecs[i] = os.iovec{
|
|
||||||
.iov_base = buf.ptr,
|
|
||||||
.iov_len = buf.len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return readvPosix(loop, fd, iovecs.ptr, data.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect(loop: *Loop, _address: *const std.net.Address) !File {
|
|
||||||
var address = _address.*; // TODO https://github.com/ziglang/zig/issues/1592
|
|
||||||
|
|
||||||
const sockfd = try os.socket(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK, os.PROTO_tcp);
|
|
||||||
errdefer os.close(sockfd);
|
|
||||||
|
|
||||||
try os.connect_async(sockfd, &address.os_addr, @sizeOf(os.sockaddr_in));
|
|
||||||
try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
|
||||||
try os.getsockoptError(sockfd);
|
|
||||||
|
|
||||||
return File.openHandle(sockfd);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "listen on a port, send bytes, receive bytes" {
|
|
||||||
// https://github.com/ziglang/zig/issues/2377
|
|
||||||
if (true) return error.SkipZigTest;
|
|
||||||
|
|
||||||
if (builtin.os != builtin.Os.linux) {
|
|
||||||
// TODO build abstractions for other operating systems
|
|
||||||
return error.SkipZigTest;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MyServer = struct {
|
|
||||||
tcp_server: Server,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
async fn handler(tcp_server: *Server, _addr: *const std.net.Address, _socket: File) void {
|
|
||||||
const self = @fieldParentPtr(Self, "tcp_server", tcp_server);
|
|
||||||
var socket = _socket; // TODO https://github.com/ziglang/zig/issues/1592
|
|
||||||
defer socket.close();
|
|
||||||
const next_handler = errorableHandler(self, _addr, socket) catch |err| {
|
|
||||||
std.debug.panic("unable to handle connection: {}\n", err);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: File) !void {
|
|
||||||
const addr = _addr.*; // TODO https://github.com/ziglang/zig/issues/1592
|
|
||||||
var socket = _socket; // TODO https://github.com/ziglang/zig/issues/1592
|
|
||||||
|
|
||||||
const stream = &socket.outStream().stream;
|
|
||||||
try stream.print("hello from server\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ip4addr = std.net.parseIp4("127.0.0.1") catch unreachable;
|
|
||||||
const addr = std.net.Address.initIp4(ip4addr, 0);
|
|
||||||
|
|
||||||
var loop: Loop = undefined;
|
|
||||||
try loop.initSingleThreaded(std.debug.global_allocator);
|
|
||||||
var server = MyServer{ .tcp_server = Server.init(&loop) };
|
|
||||||
defer server.tcp_server.deinit();
|
|
||||||
try server.tcp_server.listen(&addr, MyServer.handler);
|
|
||||||
|
|
||||||
_ = async doAsyncTest(&loop, &server.tcp_server.listen_address, &server.tcp_server);
|
|
||||||
loop.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Server) void {
|
|
||||||
errdefer @panic("test failure");
|
|
||||||
|
|
||||||
var socket_file = try connect(loop, address);
|
|
||||||
defer socket_file.close();
|
|
||||||
|
|
||||||
var buf: [512]u8 = undefined;
|
|
||||||
const amt_read = try socket_file.read(buf[0..]);
|
|
||||||
const msg = buf[0..amt_read];
|
|
||||||
testing.expect(mem.eql(u8, msg, "hello from server\n"));
|
|
||||||
server.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const OutStream = struct {
|
|
||||||
fd: fd_t,
|
|
||||||
stream: Stream,
|
|
||||||
loop: *Loop,
|
|
||||||
|
|
||||||
pub const Error = WriteError;
|
|
||||||
pub const Stream = event.io.OutStream(Error);
|
|
||||||
|
|
||||||
pub fn init(loop: *Loop, fd: fd_t) OutStream {
|
|
||||||
return OutStream{
|
|
||||||
.fd = fd,
|
|
||||||
.loop = loop,
|
|
||||||
.stream = Stream{ .writeFn = writeFn },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
|
|
||||||
const self = @fieldParentPtr(OutStream, "stream", out_stream);
|
|
||||||
return write(self.loop, self.fd, bytes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const InStream = struct {
|
|
||||||
fd: fd_t,
|
|
||||||
stream: Stream,
|
|
||||||
loop: *Loop,
|
|
||||||
|
|
||||||
pub const Error = ReadError;
|
|
||||||
pub const Stream = event.io.InStream(Error);
|
|
||||||
|
|
||||||
pub fn init(loop: *Loop, fd: fd_t) InStream {
|
|
||||||
return InStream{
|
|
||||||
.fd = fd,
|
|
||||||
.loop = loop,
|
|
||||||
.stream = Stream{ .readFn = readFn },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
|
|
||||||
const self = @fieldParentPtr(InStream, "stream", in_stream);
|
|
||||||
return read(self.loop, self.fd, bytes);
|
|
||||||
}
|
|
||||||
};
|
|
@ -64,68 +64,7 @@ pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
|
|||||||
pub const SliceSeekableInStream = @import("io/seekable_stream.zig").SliceSeekableInStream;
|
pub const SliceSeekableInStream = @import("io/seekable_stream.zig").SliceSeekableInStream;
|
||||||
pub const COutStream = @import("io/c_out_stream.zig").COutStream;
|
pub const COutStream = @import("io/c_out_stream.zig").COutStream;
|
||||||
pub const InStream = @import("io/in_stream.zig").InStream;
|
pub const InStream = @import("io/in_stream.zig").InStream;
|
||||||
|
pub const OutStream = @import("io/out_stream.zig").OutStream;
|
||||||
pub fn OutStream(comptime WriteError: type) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
pub const Error = WriteError;
|
|
||||||
|
|
||||||
writeFn: fn (self: *Self, bytes: []const u8) Error!void,
|
|
||||||
|
|
||||||
pub fn print(self: *Self, comptime format: []const u8, args: ...) Error!void {
|
|
||||||
return std.fmt.format(self, Error, self.writeFn, format, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(self: *Self, bytes: []const u8) Error!void {
|
|
||||||
return self.writeFn(self, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeByte(self: *Self, byte: u8) Error!void {
|
|
||||||
const slice = (*const [1]u8)(&byte)[0..];
|
|
||||||
return self.writeFn(self, slice);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
|
|
||||||
const slice = (*const [1]u8)(&byte)[0..];
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < n) : (i += 1) {
|
|
||||||
try self.writeFn(self, slice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a native-endian integer.
|
|
||||||
pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
|
|
||||||
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
|
||||||
mem.writeIntNative(T, &bytes, value);
|
|
||||||
return self.writeFn(self, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a foreign-endian integer.
|
|
||||||
pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
|
|
||||||
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
|
||||||
mem.writeIntForeign(T, &bytes, value);
|
|
||||||
return self.writeFn(self, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
|
|
||||||
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
|
||||||
mem.writeIntLittle(T, &bytes, value);
|
|
||||||
return self.writeFn(self, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
|
|
||||||
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
|
||||||
mem.writeIntBig(T, &bytes, value);
|
|
||||||
return self.writeFn(self, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
|
|
||||||
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
|
||||||
mem.writeInt(T, &bytes, value, endian);
|
|
||||||
return self.writeFn(self, bytes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
|
/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
|
||||||
pub fn writeFile(path: []const u8, data: []const u8) !void {
|
pub fn writeFile(path: []const u8, data: []const u8) !void {
|
||||||
|
@ -11,7 +11,6 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_InStream"))
|
|||||||
root.stack_size_std_io_InStream
|
root.stack_size_std_io_InStream
|
||||||
else
|
else
|
||||||
default_stack_size;
|
default_stack_size;
|
||||||
pub const stack_align = 16;
|
|
||||||
|
|
||||||
pub fn InStream(comptime ReadError: type) type {
|
pub fn InStream(comptime ReadError: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
@ -34,7 +33,7 @@ pub fn InStream(comptime ReadError: type) type {
|
|||||||
if (std.io.is_async) {
|
if (std.io.is_async) {
|
||||||
// Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream read.
|
// Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream read.
|
||||||
@setRuntimeSafety(false);
|
@setRuntimeSafety(false);
|
||||||
var stack_frame: [stack_size]u8 align(stack_align) = undefined;
|
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
|
||||||
return await @asyncCall(&stack_frame, {}, self.readFn, self, buffer);
|
return await @asyncCall(&stack_frame, {}, self.readFn, self, buffer);
|
||||||
} else {
|
} else {
|
||||||
return self.readFn(self, buffer);
|
return self.readFn(self, buffer);
|
||||||
|
87
lib/std/io/out_stream.zig
Normal file
87
lib/std/io/out_stream.zig
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
const std = @import("../std.zig");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const root = @import("root");
|
||||||
|
const mem = std.mem;
|
||||||
|
|
||||||
|
pub const default_stack_size = 1 * 1024 * 1024;
|
||||||
|
pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))
|
||||||
|
root.stack_size_std_io_OutStream
|
||||||
|
else
|
||||||
|
default_stack_size;
|
||||||
|
|
||||||
|
/// TODO this is not integrated with evented I/O yet.
|
||||||
|
/// https://github.com/ziglang/zig/issues/3557
|
||||||
|
pub fn OutStream(comptime WriteError: type) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
pub const Error = WriteError;
|
||||||
|
// TODO https://github.com/ziglang/zig/issues/3557
|
||||||
|
pub const WriteFn = if (std.io.is_async and false)
|
||||||
|
async fn (self: *Self, bytes: []const u8) Error!void
|
||||||
|
else
|
||||||
|
fn (self: *Self, bytes: []const u8) Error!void;
|
||||||
|
|
||||||
|
writeFn: WriteFn,
|
||||||
|
|
||||||
|
pub fn write(self: *Self, bytes: []const u8) Error!void {
|
||||||
|
// TODO https://github.com/ziglang/zig/issues/3557
|
||||||
|
if (std.io.is_async and false) {
|
||||||
|
// Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
|
||||||
|
@setRuntimeSafety(false);
|
||||||
|
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
|
||||||
|
return await @asyncCall(&stack_frame, {}, self.writeFn, self, bytes);
|
||||||
|
} else {
|
||||||
|
return self.writeFn(self, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print(self: *Self, comptime format: []const u8, args: ...) Error!void {
|
||||||
|
return std.fmt.format(self, Error, self.writeFn, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeByte(self: *Self, byte: u8) Error!void {
|
||||||
|
const slice = (*const [1]u8)(&byte)[0..];
|
||||||
|
return self.writeFn(self, slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
|
||||||
|
const slice = (*const [1]u8)(&byte)[0..];
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < n) : (i += 1) {
|
||||||
|
try self.writeFn(self, slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a native-endian integer.
|
||||||
|
pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
|
||||||
|
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
||||||
|
mem.writeIntNative(T, &bytes, value);
|
||||||
|
return self.writeFn(self, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a foreign-endian integer.
|
||||||
|
pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
|
||||||
|
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
||||||
|
mem.writeIntForeign(T, &bytes, value);
|
||||||
|
return self.writeFn(self, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
|
||||||
|
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
||||||
|
mem.writeIntLittle(T, &bytes, value);
|
||||||
|
return self.writeFn(self, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
|
||||||
|
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
||||||
|
mem.writeIntBig(T, &bytes, value);
|
||||||
|
return self.writeFn(self, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
|
||||||
|
var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
|
||||||
|
mem.writeInt(T, &bytes, value, endian);
|
||||||
|
return self.writeFn(self, bytes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
198
lib/std/net.zig
198
lib/std/net.zig
@ -6,6 +6,10 @@ const mem = std.mem;
|
|||||||
const os = std.os;
|
const os = std.os;
|
||||||
const fs = std.fs;
|
const fs = std.fs;
|
||||||
|
|
||||||
|
test "" {
|
||||||
|
_ = @import("net/test.zig");
|
||||||
|
}
|
||||||
|
|
||||||
pub const TmpWinAddr = struct {
|
pub const TmpWinAddr = struct {
|
||||||
family: u8,
|
family: u8,
|
||||||
data: [14]u8,
|
data: [14]u8,
|
||||||
@ -21,6 +25,9 @@ pub const OsAddress = switch (builtin.os) {
|
|||||||
pub const Address = struct {
|
pub const Address = struct {
|
||||||
os_addr: OsAddress,
|
os_addr: OsAddress,
|
||||||
|
|
||||||
|
// TODO this crashed the compiler
|
||||||
|
//pub const localhost = initIp4(parseIp4("127.0.0.1") catch unreachable, 0);
|
||||||
|
|
||||||
pub fn initIp4(ip4: u32, _port: u16) Address {
|
pub fn initIp4(ip4: u32, _port: u16) Address {
|
||||||
return Address{
|
return Address{
|
||||||
.os_addr = os.sockaddr{
|
.os_addr = os.sockaddr{
|
||||||
@ -141,6 +148,14 @@ pub const Address = struct {
|
|||||||
else => return output(context, "(unrecognized address family)"),
|
else => return output(context, "(unrecognized address family)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getOsSockLen(self: Address) os.socklen_t {
|
||||||
|
switch (self.os_addr.un.family) {
|
||||||
|
os.AF_INET => return @sizeOf(os.sockaddr_in),
|
||||||
|
os.AF_INET6 => return @sizeOf(os.sockaddr_in6),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parseIp4(buf: []const u8) !u32 {
|
pub fn parseIp4(buf: []const u8) !u32 {
|
||||||
@ -260,34 +275,8 @@ pub fn parseIp6(buf: []const u8) !Ip6Addr {
|
|||||||
return error.Incomplete;
|
return error.Incomplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
test "std.net.parseIp4" {
|
|
||||||
assert((try parseIp4("127.0.0.1")) == mem.bigToNative(u32, 0x7f000001));
|
|
||||||
|
|
||||||
testParseIp4Fail("256.0.0.1", error.Overflow);
|
|
||||||
testParseIp4Fail("x.0.0.1", error.InvalidCharacter);
|
|
||||||
testParseIp4Fail("127.0.0.1.1", error.InvalidEnd);
|
|
||||||
testParseIp4Fail("127.0.0.", error.Incomplete);
|
|
||||||
testParseIp4Fail("100..0.1", error.InvalidCharacter);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testParseIp4Fail(buf: []const u8, expected_err: anyerror) void {
|
|
||||||
if (parseIp4(buf)) |_| {
|
|
||||||
@panic("expected error");
|
|
||||||
} else |e| {
|
|
||||||
assert(e == expected_err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "std.net.parseIp6" {
|
|
||||||
const ip6 = try parseIp6("FF01:0:0:0:0:0:0:FB");
|
|
||||||
const addr = Address.initIp6(ip6, 80);
|
|
||||||
var buf: [100]u8 = undefined;
|
|
||||||
const printed = try std.fmt.bufPrint(&buf, "{}", addr);
|
|
||||||
std.testing.expect(mem.eql(u8, "[ff01::fb]:80", printed));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connectUnixSocket(path: []const u8) !fs.File {
|
pub fn connectUnixSocket(path: []const u8) !fs.File {
|
||||||
const opt_non_block = if (std.event.Loop.instance != null) os.SOCK_NONBLOCK else 0;
|
const opt_non_block = if (std.io.mode == .evented) os.SOCK_NONBLOCK else 0;
|
||||||
const sockfd = try os.socket(
|
const sockfd = try os.socket(
|
||||||
os.AF_UNIX,
|
os.AF_UNIX,
|
||||||
os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
|
os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
|
||||||
@ -305,13 +294,7 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
|
|||||||
if (path.len > @typeOf(sock_addr.un.path).len) return error.NameTooLong;
|
if (path.len > @typeOf(sock_addr.un.path).len) return error.NameTooLong;
|
||||||
mem.copy(u8, sock_addr.un.path[0..], path);
|
mem.copy(u8, sock_addr.un.path[0..], path);
|
||||||
const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
|
const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
|
||||||
if (std.event.Loop.instance) |loop| {
|
try os.connect(sockfd, &sock_addr, size);
|
||||||
try os.connect_async(sockfd, &sock_addr, size);
|
|
||||||
try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
|
||||||
try os.getsockoptError(sockfd);
|
|
||||||
} else {
|
|
||||||
try os.connect(sockfd, &sock_addr, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.File.openHandle(sockfd);
|
return fs.File.openHandle(sockfd);
|
||||||
}
|
}
|
||||||
@ -330,6 +313,27 @@ pub const AddressList = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// All memory allocated with `allocator` will be freed before this function returns.
|
||||||
|
pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) !fs.File {
|
||||||
|
const list = getAddressList(allocator, name, port);
|
||||||
|
defer list.deinit();
|
||||||
|
|
||||||
|
const addrs = list.addrs.toSliceConst();
|
||||||
|
if (addrs.len == 0) return error.UnknownHostName;
|
||||||
|
|
||||||
|
return tcpConnectToAddress(addrs[0], port);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tcpConnectToAddress(address: Address) !fs.File {
|
||||||
|
const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
|
||||||
|
const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | nonblock;
|
||||||
|
const sockfd = try os.socket(address.os_addr.un.family, sock_flags, os.IPPROTO_TCP);
|
||||||
|
errdefer os.close(sockfd);
|
||||||
|
try os.connect(sockfd, address.os_addr, address.getOsSockLen());
|
||||||
|
|
||||||
|
return fs.File{ .handle = sockfd };
|
||||||
|
}
|
||||||
|
|
||||||
/// Call `AddressList.deinit` on the result.
|
/// Call `AddressList.deinit` on the result.
|
||||||
pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*AddressList {
|
pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*AddressList {
|
||||||
const result = blk: {
|
const result = blk: {
|
||||||
@ -375,7 +379,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*
|
|||||||
c.EAI_FAMILY => return error.AddressFamilyNotSupported,
|
c.EAI_FAMILY => return error.AddressFamilyNotSupported,
|
||||||
c.EAI_MEMORY => return error.OutOfMemory,
|
c.EAI_MEMORY => return error.OutOfMemory,
|
||||||
c.EAI_NODATA => return error.HostLacksNetworkAddresses,
|
c.EAI_NODATA => return error.HostLacksNetworkAddresses,
|
||||||
c.EAI_NONAME => return error.UnknownName,
|
c.EAI_NONAME => return error.UnknownHostName,
|
||||||
c.EAI_SERVICE => return error.ServiceUnavailable,
|
c.EAI_SERVICE => return error.ServiceUnavailable,
|
||||||
c.EAI_SOCKTYPE => unreachable, // Invalid socket type requested in hints
|
c.EAI_SOCKTYPE => unreachable, // Invalid socket type requested in hints
|
||||||
c.EAI_SYSTEM => switch (os.errno(-1)) {
|
c.EAI_SYSTEM => switch (os.errno(-1)) {
|
||||||
@ -493,7 +497,7 @@ fn linuxLookupName(
|
|||||||
try canon.resize(0);
|
try canon.resize(0);
|
||||||
try linuxLookupNameFromNull(addrs, family, flags);
|
try linuxLookupNameFromNull(addrs, family, flags);
|
||||||
}
|
}
|
||||||
if (addrs.len == 0) return error.UnknownName;
|
if (addrs.len == 0) return error.UnknownHostName;
|
||||||
|
|
||||||
// No further processing is needed if there are fewer than 2
|
// No further processing is needed if there are fewer than 2
|
||||||
// results or if there are only IPv4 results.
|
// results or if there are only IPv4 results.
|
||||||
@ -858,7 +862,7 @@ fn linuxLookupNameFromDnsSearch(
|
|||||||
|
|
||||||
// Strip final dot for canon, fail if multiple trailing dots.
|
// Strip final dot for canon, fail if multiple trailing dots.
|
||||||
if (mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
|
if (mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
|
||||||
if (mem.endsWith(u8, canon_name, ".")) return error.UnknownName;
|
if (mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
|
||||||
|
|
||||||
// Name with search domain appended is setup in canon[]. This both
|
// Name with search domain appended is setup in canon[]. This both
|
||||||
// provides the desired default canonical name (if the requested
|
// provides the desired default canonical name (if the requested
|
||||||
@ -928,7 +932,7 @@ fn linuxLookupNameFromDns(
|
|||||||
|
|
||||||
if (addrs.len != 0) return;
|
if (addrs.len != 0) return;
|
||||||
if (ap[0].len < 4 or (ap[0][3] & 15) == 2) return error.TemporaryNameServerFailure;
|
if (ap[0].len < 4 or (ap[0][3] & 15) == 2) return error.TemporaryNameServerFailure;
|
||||||
if ((ap[0][3] & 15) == 0) return error.UnknownName;
|
if ((ap[0][3] & 15) == 0) return error.UnknownHostName;
|
||||||
if ((ap[0][3] & 15) == 3) return;
|
if ((ap[0][3] & 15) == 3) return;
|
||||||
return error.NameServerFailure;
|
return error.NameServerFailure;
|
||||||
}
|
}
|
||||||
@ -1247,3 +1251,121 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
|
|||||||
else => return,
|
else => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This API only works when `std.io.mode` is `std.io.Mode.evented`.
|
||||||
|
/// This struct is immovable after calling `listen`.
|
||||||
|
pub const Server = struct {
|
||||||
|
/// This field is meant to be accessed directly.
|
||||||
|
/// Call `connections.get` to accept a connection.
|
||||||
|
connections: *ConnectionChannel,
|
||||||
|
|
||||||
|
/// Copied from `Options` on `init`.
|
||||||
|
kernel_backlog: u32,
|
||||||
|
|
||||||
|
/// `undefined` until `listen` returns successfully.
|
||||||
|
listen_address: Address,
|
||||||
|
|
||||||
|
sockfd: ?os.fd_t,
|
||||||
|
accept_frame: @Frame(acceptConnections),
|
||||||
|
|
||||||
|
pub const ConnectionChannel = std.event.Channel(AcceptError!fs.File);
|
||||||
|
|
||||||
|
pub const AcceptError = error{
|
||||||
|
ConnectionAborted,
|
||||||
|
|
||||||
|
/// The per-process limit on the number of open file descriptors has been reached.
|
||||||
|
ProcessFdQuotaExceeded,
|
||||||
|
|
||||||
|
/// The system-wide limit on the total number of open files has been reached.
|
||||||
|
SystemFdQuotaExceeded,
|
||||||
|
|
||||||
|
/// Not enough free memory. This often means that the memory allocation is limited
|
||||||
|
/// by the socket buffer limits, not by the system memory.
|
||||||
|
SystemResources,
|
||||||
|
|
||||||
|
ProtocolFailure,
|
||||||
|
|
||||||
|
/// Firewall rules forbid connection.
|
||||||
|
BlockedByFirewall,
|
||||||
|
} || os.UnexpectedError;
|
||||||
|
|
||||||
|
pub const Options = struct {
|
||||||
|
/// How many connections the kernel will accept on the application's behalf.
|
||||||
|
/// If more than this many connections pool in the kernel, clients will start
|
||||||
|
/// seeing "Connection refused".
|
||||||
|
kernel_backlog: u32 = 128,
|
||||||
|
|
||||||
|
/// How many connections this `Server` will accept from the kernel even before
|
||||||
|
/// they are requested from the `connections` channel.
|
||||||
|
eager_connections: usize = 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// After this call succeeds, resources have been acquired and must
|
||||||
|
/// be released with `deinit`.
|
||||||
|
pub fn init(options: Options) !Server {
|
||||||
|
const loop = std.event.Loop.instance orelse
|
||||||
|
@compileError("std.net.Server only works in evented I/O mode");
|
||||||
|
return Server{
|
||||||
|
.connections = try ConnectionChannel.create(loop, options.eager_connections),
|
||||||
|
.sockfd = null,
|
||||||
|
.kernel_backlog = options.kernel_backlog,
|
||||||
|
.listen_address = undefined,
|
||||||
|
.accept_frame = undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// After calling this function, one must call `init` to do anything else with this `Server`.
|
||||||
|
pub fn deinit(self: *Server) void {
|
||||||
|
self.close();
|
||||||
|
self.connections.destroy();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen(self: *Server, address: Address) !void {
|
||||||
|
const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK;
|
||||||
|
const sockfd = try os.socket(os.AF_INET, sock_flags, os.PROTO_tcp);
|
||||||
|
self.sockfd = sockfd;
|
||||||
|
errdefer {
|
||||||
|
os.close(sockfd);
|
||||||
|
self.sockfd = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var socklen = address.getOsSockLen();
|
||||||
|
try os.bind(sockfd, &address.os_addr, socklen);
|
||||||
|
try os.listen(sockfd, self.kernel_backlog);
|
||||||
|
try os.getsockname(sockfd, &self.listen_address.os_addr, &socklen);
|
||||||
|
|
||||||
|
// acceptConnections loops, calling os.accept().
|
||||||
|
self.accept_frame = async self.acceptConnections();
|
||||||
|
errdefer await self.accept_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop listening. It is still necessary to call `deinit` after stopping listening.
|
||||||
|
/// Calling `deinit` will automatically call `close`. It is safe to call `close` when
|
||||||
|
/// not listening.
|
||||||
|
pub fn close(self: *Server) void {
|
||||||
|
if (self.sockfd) |fd| {
|
||||||
|
os.close(fd);
|
||||||
|
self.sockfd = null;
|
||||||
|
await self.accept_frame;
|
||||||
|
self.accept_frame = undefined;
|
||||||
|
self.listen_address = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acceptConnections(self: *Server) void {
|
||||||
|
const sockfd = self.sockfd.?;
|
||||||
|
const accept_flags = os.SOCK_NONBLOCK | os.SOCK_CLOEXEC;
|
||||||
|
while (true) {
|
||||||
|
var accepted_addr: Address = undefined;
|
||||||
|
var addr_len: os.socklen_t = @sizeOf(os.sockaddr);
|
||||||
|
const conn = if (os.accept4(sockfd, &accepted_addr.os_addr, &addr_len, accept_flags)) |fd|
|
||||||
|
fs.File.openHandle(fd)
|
||||||
|
else |err| switch (err) {
|
||||||
|
error.WouldBlock => unreachable, // we asserted earlier about non-blocking I/O mode
|
||||||
|
else => |e| e,
|
||||||
|
};
|
||||||
|
self.connections.put(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
71
lib/std/net/test.zig
Normal file
71
lib/std/net/test.zig
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("../std.zig");
|
||||||
|
const net = std.net;
|
||||||
|
const mem = std.mem;
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
test "std.net.parseIp4" {
|
||||||
|
assert((try parseIp4("127.0.0.1")) == mem.bigToNative(u32, 0x7f000001));
|
||||||
|
|
||||||
|
testParseIp4Fail("256.0.0.1", error.Overflow);
|
||||||
|
testParseIp4Fail("x.0.0.1", error.InvalidCharacter);
|
||||||
|
testParseIp4Fail("127.0.0.1.1", error.InvalidEnd);
|
||||||
|
testParseIp4Fail("127.0.0.", error.Incomplete);
|
||||||
|
testParseIp4Fail("100..0.1", error.InvalidCharacter);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testParseIp4Fail(buf: []const u8, expected_err: anyerror) void {
|
||||||
|
if (parseIp4(buf)) |_| {
|
||||||
|
@panic("expected error");
|
||||||
|
} else |e| {
|
||||||
|
assert(e == expected_err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "std.net.parseIp6" {
|
||||||
|
const ip6 = try parseIp6("FF01:0:0:0:0:0:0:FB");
|
||||||
|
const addr = Address.initIp6(ip6, 80);
|
||||||
|
var buf: [100]u8 = undefined;
|
||||||
|
const printed = try std.fmt.bufPrint(&buf, "{}", addr);
|
||||||
|
std.testing.expect(mem.eql(u8, "[ff01::fb]:80", printed));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "listen on a port, send bytes, receive bytes" {
|
||||||
|
if (std.builtin.os != .linux) {
|
||||||
|
// TODO build abstractions for other operating systems
|
||||||
|
return error.SkipZigTest;
|
||||||
|
}
|
||||||
|
if (std.io.mode != .evented) {
|
||||||
|
// TODO add ability to run tests in non-blocking I/O mode
|
||||||
|
return error.SkipZigTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO doing this at comptime crashed the compiler
|
||||||
|
const localhost = net.Address.initIp4(net.parseIp4("127.0.0.1") catch unreachable, 0);
|
||||||
|
|
||||||
|
var server = try net.Server.init(net.Server.Options{});
|
||||||
|
defer server.deinit();
|
||||||
|
try server.listen(localhost);
|
||||||
|
|
||||||
|
var server_frame = async testServer(&server);
|
||||||
|
var client_frame = async testClient(server.listen_address);
|
||||||
|
|
||||||
|
try await server_frame;
|
||||||
|
try await client_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testClient(addr: net.Address) anyerror!void {
|
||||||
|
const socket_file = try net.tcpConnectToAddress(addr);
|
||||||
|
defer socket_file.close();
|
||||||
|
|
||||||
|
var buf: [100]u8 = undefined;
|
||||||
|
const len = try socket_file.read(&buf);
|
||||||
|
const msg = buf[0..len];
|
||||||
|
testing.expect(mem.eql(u8, msg, "hello from server\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testServer(server: *net.Server) anyerror!void {
|
||||||
|
var client_file = try server.connections.get();
|
||||||
|
|
||||||
|
const stream = &client_file.outStream().stream;
|
||||||
|
try stream.print("hello from server\n");
|
||||||
|
}
|
161
lib/std/os.zig
161
lib/std/os.zig
@ -308,7 +308,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
|
|||||||
EINVAL => unreachable,
|
EINVAL => unreachable,
|
||||||
EFAULT => unreachable,
|
EFAULT => unreachable,
|
||||||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
loop.waitUntilFdReadable(fd) catch return error.WouldBlock;
|
loop.waitUntilFdReadable(fd);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return error.WouldBlock;
|
return error.WouldBlock;
|
||||||
@ -325,7 +325,36 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
|
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
|
||||||
/// This function is for blocking file descriptors only.
|
/// If the application has a global event loop enabled, EAGAIN is handled
|
||||||
|
/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
|
||||||
|
pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
|
||||||
|
while (true) {
|
||||||
|
// TODO handle the case when iov_len is too large and get rid of this @intCast
|
||||||
|
const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));
|
||||||
|
switch (errno(rc)) {
|
||||||
|
0 => return @bitCast(usize, rc),
|
||||||
|
EINTR => continue,
|
||||||
|
EINVAL => unreachable,
|
||||||
|
EFAULT => unreachable,
|
||||||
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
|
loop.waitUntilFdReadable(fd);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return error.WouldBlock;
|
||||||
|
},
|
||||||
|
EBADF => unreachable, // always a race condition
|
||||||
|
EIO => return error.InputOutput,
|
||||||
|
EISDIR => return error.IsDir,
|
||||||
|
ENOBUFS => return error.SystemResources,
|
||||||
|
ENOMEM => return error.SystemResources,
|
||||||
|
else => |err| return unexpectedErrno(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
|
||||||
|
/// If the application has a global event loop enabled, EAGAIN is handled
|
||||||
|
/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
|
||||||
pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
|
pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
|
||||||
if (comptime std.Target.current.isDarwin()) {
|
if (comptime std.Target.current.isDarwin()) {
|
||||||
// Darwin does not have preadv but it does have pread.
|
// Darwin does not have preadv but it does have pread.
|
||||||
@ -355,7 +384,12 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
|
|||||||
EINVAL => unreachable,
|
EINVAL => unreachable,
|
||||||
EFAULT => unreachable,
|
EFAULT => unreachable,
|
||||||
ESPIPE => unreachable, // fd is not seekable
|
ESPIPE => unreachable, // fd is not seekable
|
||||||
EAGAIN => unreachable, // This function is for blocking reads.
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
|
loop.waitUntilFdReadable(fd);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return error.WouldBlock;
|
||||||
|
},
|
||||||
EBADF => unreachable, // always a race condition
|
EBADF => unreachable, // always a race condition
|
||||||
EIO => return error.InputOutput,
|
EIO => return error.InputOutput,
|
||||||
EISDIR => return error.IsDir,
|
EISDIR => return error.IsDir,
|
||||||
@ -373,7 +407,12 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
|
|||||||
EINTR => continue,
|
EINTR => continue,
|
||||||
EINVAL => unreachable,
|
EINVAL => unreachable,
|
||||||
EFAULT => unreachable,
|
EFAULT => unreachable,
|
||||||
EAGAIN => unreachable, // This function is for blocking reads.
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
|
loop.waitUntilFdReadable(fd);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return error.WouldBlock;
|
||||||
|
},
|
||||||
EBADF => unreachable, // always a race condition
|
EBADF => unreachable, // always a race condition
|
||||||
EIO => return error.InputOutput,
|
EIO => return error.InputOutput,
|
||||||
EISDIR => return error.IsDir,
|
EISDIR => return error.IsDir,
|
||||||
@ -393,10 +432,17 @@ pub const WriteError = error{
|
|||||||
BrokenPipe,
|
BrokenPipe,
|
||||||
SystemResources,
|
SystemResources,
|
||||||
OperationAborted,
|
OperationAborted,
|
||||||
|
|
||||||
|
/// This error occurs when no global event loop is configured,
|
||||||
|
/// and reading from the file descriptor would block.
|
||||||
|
WouldBlock,
|
||||||
} || UnexpectedError;
|
} || UnexpectedError;
|
||||||
|
|
||||||
/// Write to a file descriptor. Keeps trying if it gets interrupted.
|
/// Write to a file descriptor. Keeps trying if it gets interrupted.
|
||||||
/// This function is for blocking file descriptors only.
|
/// If the application has a global event loop enabled, EAGAIN is handled
|
||||||
|
/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
|
||||||
|
/// TODO evented I/O integration is disabled until
|
||||||
|
/// https://github.com/ziglang/zig/issues/3557 is solved.
|
||||||
pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
|
pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
|
||||||
if (builtin.os == .windows) {
|
if (builtin.os == .windows) {
|
||||||
return windows.WriteFile(fd, bytes);
|
return windows.WriteFile(fd, bytes);
|
||||||
@ -432,7 +478,14 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
|
|||||||
EINTR => continue,
|
EINTR => continue,
|
||||||
EINVAL => unreachable,
|
EINVAL => unreachable,
|
||||||
EFAULT => unreachable,
|
EFAULT => unreachable,
|
||||||
EAGAIN => unreachable, // This function is for blocking writes.
|
// TODO https://github.com/ziglang/zig/issues/3557
|
||||||
|
EAGAIN => return error.WouldBlock,
|
||||||
|
//EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
|
// loop.waitUntilFdWritable(fd);
|
||||||
|
// continue;
|
||||||
|
//} else {
|
||||||
|
// return error.WouldBlock;
|
||||||
|
//},
|
||||||
EBADF => unreachable, // Always a race condition.
|
EBADF => unreachable, // Always a race condition.
|
||||||
EDESTADDRREQ => unreachable, // `connect` was never called.
|
EDESTADDRREQ => unreachable, // `connect` was never called.
|
||||||
EDQUOT => return error.DiskQuota,
|
EDQUOT => return error.DiskQuota,
|
||||||
@ -446,9 +499,9 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted.
|
/// Write multiple buffers to a file descriptor.
|
||||||
/// This function is for blocking file descriptors only. For non-blocking, see
|
/// If the application has a global event loop enabled, EAGAIN is handled
|
||||||
/// `writevAsync`.
|
/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
|
||||||
pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
|
pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
|
||||||
while (true) {
|
while (true) {
|
||||||
// TODO handle the case when iov_len is too large and get rid of this @intCast
|
// TODO handle the case when iov_len is too large and get rid of this @intCast
|
||||||
@ -458,7 +511,12 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
|
|||||||
EINTR => continue,
|
EINTR => continue,
|
||||||
EINVAL => unreachable,
|
EINVAL => unreachable,
|
||||||
EFAULT => unreachable,
|
EFAULT => unreachable,
|
||||||
EAGAIN => unreachable, // This function is for blocking writes.
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
|
loop.waitUntilFdWritable(fd);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return error.WouldBlock;
|
||||||
|
},
|
||||||
EBADF => unreachable, // Always a race condition.
|
EBADF => unreachable, // Always a race condition.
|
||||||
EDESTADDRREQ => unreachable, // `connect` was never called.
|
EDESTADDRREQ => unreachable, // `connect` was never called.
|
||||||
EDQUOT => return error.DiskQuota,
|
EDQUOT => return error.DiskQuota,
|
||||||
@ -474,8 +532,6 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
|
|||||||
|
|
||||||
/// Write multiple buffers to a file descriptor, with a position offset.
|
/// Write multiple buffers to a file descriptor, with a position offset.
|
||||||
/// Keeps trying if it gets interrupted.
|
/// Keeps trying if it gets interrupted.
|
||||||
/// This function is for blocking file descriptors only. For non-blocking, see
|
|
||||||
/// `pwritevAsync`.
|
|
||||||
pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
|
pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
|
||||||
if (comptime std.Target.current.isDarwin()) {
|
if (comptime std.Target.current.isDarwin()) {
|
||||||
// Darwin does not have pwritev but it does have pwrite.
|
// Darwin does not have pwritev but it does have pwrite.
|
||||||
@ -504,7 +560,12 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
|
|||||||
ESPIPE => unreachable, // `fd` is not seekable.
|
ESPIPE => unreachable, // `fd` is not seekable.
|
||||||
EINVAL => unreachable,
|
EINVAL => unreachable,
|
||||||
EFAULT => unreachable,
|
EFAULT => unreachable,
|
||||||
EAGAIN => unreachable, // This function is for blocking writes.
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
|
loop.waitUntilFdWritable(fd);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return error.WouldBlock;
|
||||||
|
},
|
||||||
EBADF => unreachable, // Always a race condition.
|
EBADF => unreachable, // Always a race condition.
|
||||||
EDESTADDRREQ => unreachable, // `connect` was never called.
|
EDESTADDRREQ => unreachable, // `connect` was never called.
|
||||||
EDQUOT => return error.DiskQuota,
|
EDQUOT => return error.DiskQuota,
|
||||||
@ -526,7 +587,12 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
|
|||||||
EINTR => continue,
|
EINTR => continue,
|
||||||
EINVAL => unreachable,
|
EINVAL => unreachable,
|
||||||
EFAULT => unreachable,
|
EFAULT => unreachable,
|
||||||
EAGAIN => unreachable, // This function is for blocking writes.
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
|
loop.waitUntilFdWritable(fd);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return error.WouldBlock;
|
||||||
|
},
|
||||||
EBADF => unreachable, // Always a race condition.
|
EBADF => unreachable, // Always a race condition.
|
||||||
EDESTADDRREQ => unreachable, // `connect` was never called.
|
EDESTADDRREQ => unreachable, // `connect` was never called.
|
||||||
EDQUOT => return error.DiskQuota,
|
EDQUOT => return error.DiskQuota,
|
||||||
@ -1621,12 +1687,6 @@ pub const AcceptError = error{
|
|||||||
/// by the socket buffer limits, not by the system memory.
|
/// by the socket buffer limits, not by the system memory.
|
||||||
SystemResources,
|
SystemResources,
|
||||||
|
|
||||||
/// The file descriptor sockfd does not refer to a socket.
|
|
||||||
FileDescriptorNotASocket,
|
|
||||||
|
|
||||||
/// The referenced socket is not of type SOCK_STREAM.
|
|
||||||
OperationNotSupported,
|
|
||||||
|
|
||||||
ProtocolFailure,
|
ProtocolFailure,
|
||||||
|
|
||||||
/// Firewall rules forbid connection.
|
/// Firewall rules forbid connection.
|
||||||
@ -1643,7 +1703,7 @@ pub const AcceptError = error{
|
|||||||
pub fn accept4(
|
pub fn accept4(
|
||||||
/// This argument is a socket that has been created with `socket`, bound to a local address
|
/// This argument is a socket that has been created with `socket`, bound to a local address
|
||||||
/// with `bind`, and is listening for connections after a `listen`.
|
/// with `bind`, and is listening for connections after a `listen`.
|
||||||
sockfd: i32,
|
sockfd: fd_t,
|
||||||
/// This argument is a pointer to a sockaddr structure. This structure is filled in with the
|
/// This argument is a pointer to a sockaddr structure. This structure is filled in with the
|
||||||
/// address of the peer socket, as known to the communications layer. The exact format of the
|
/// address of the peer socket, as known to the communications layer. The exact format of the
|
||||||
/// address returned addr is determined by the socket's address family (see `socket` and the
|
/// address returned addr is determined by the socket's address family (see `socket` and the
|
||||||
@ -1664,15 +1724,15 @@ pub fn accept4(
|
|||||||
/// * `SOCK_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
|
/// * `SOCK_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
|
||||||
/// description of the `O_CLOEXEC` flag in `open` for reasons why this may be useful.
|
/// description of the `O_CLOEXEC` flag in `open` for reasons why this may be useful.
|
||||||
flags: u32,
|
flags: u32,
|
||||||
) AcceptError!i32 {
|
) AcceptError!fd_t {
|
||||||
while (true) {
|
while (true) {
|
||||||
const rc = system.accept4(sockfd, addr, addr_size, flags);
|
const rc = system.accept4(sockfd, addr, addr_size, flags);
|
||||||
switch (errno(rc)) {
|
switch (errno(rc)) {
|
||||||
0 => return @intCast(i32, rc),
|
0 => return @intCast(fd_t, rc),
|
||||||
EINTR => continue,
|
EINTR => continue,
|
||||||
|
|
||||||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
loop.waitUntilFdReadable(sockfd) catch return error.WouldBlock;
|
loop.waitUntilFdReadable(sockfd);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return error.WouldBlock;
|
return error.WouldBlock;
|
||||||
@ -1681,12 +1741,12 @@ pub fn accept4(
|
|||||||
ECONNABORTED => return error.ConnectionAborted,
|
ECONNABORTED => return error.ConnectionAborted,
|
||||||
EFAULT => unreachable,
|
EFAULT => unreachable,
|
||||||
EINVAL => unreachable,
|
EINVAL => unreachable,
|
||||||
|
ENOTSOCK => unreachable,
|
||||||
EMFILE => return error.ProcessFdQuotaExceeded,
|
EMFILE => return error.ProcessFdQuotaExceeded,
|
||||||
ENFILE => return error.SystemFdQuotaExceeded,
|
ENFILE => return error.SystemFdQuotaExceeded,
|
||||||
ENOBUFS => return error.SystemResources,
|
ENOBUFS => return error.SystemResources,
|
||||||
ENOMEM => return error.SystemResources,
|
ENOMEM => return error.SystemResources,
|
||||||
ENOTSOCK => return error.FileDescriptorNotASocket,
|
EOPNOTSUPP => unreachable,
|
||||||
EOPNOTSUPP => return error.OperationNotSupported,
|
|
||||||
EPROTO => return error.ProtocolFailure,
|
EPROTO => return error.ProtocolFailure,
|
||||||
EPERM => return error.BlockedByFirewall,
|
EPERM => return error.BlockedByFirewall,
|
||||||
|
|
||||||
@ -1853,26 +1913,31 @@ pub const ConnectError = error{
|
|||||||
/// Timeout while attempting connection. The server may be too busy to accept new connections. Note
|
/// Timeout while attempting connection. The server may be too busy to accept new connections. Note
|
||||||
/// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
|
/// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
|
||||||
ConnectionTimedOut,
|
ConnectionTimedOut,
|
||||||
|
|
||||||
|
/// This error occurs when no global event loop is configured,
|
||||||
|
/// and connecting to the socket would block.
|
||||||
|
WouldBlock,
|
||||||
} || UnexpectedError;
|
} || UnexpectedError;
|
||||||
|
|
||||||
/// Initiate a connection on a socket.
|
/// Initiate a connection on a socket.
|
||||||
/// This is for blocking file descriptors only.
|
pub fn connect(sockfd: fd_t, sock_addr: sockaddr, len: socklen_t) ConnectError!void {
|
||||||
/// For non-blocking, see `connect_async`.
|
|
||||||
pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void {
|
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (errno(system.connect(sockfd, sock_addr, len))) {
|
switch (errno(system.connect(sockfd, &sock_addr, len))) {
|
||||||
0 => return,
|
0 => return,
|
||||||
EACCES => return error.PermissionDenied,
|
EACCES => return error.PermissionDenied,
|
||||||
EPERM => return error.PermissionDenied,
|
EPERM => return error.PermissionDenied,
|
||||||
EADDRINUSE => return error.AddressInUse,
|
EADDRINUSE => return error.AddressInUse,
|
||||||
EADDRNOTAVAIL => return error.AddressNotAvailable,
|
EADDRNOTAVAIL => return error.AddressNotAvailable,
|
||||||
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
|
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
|
||||||
EAGAIN => return error.SystemResources,
|
EAGAIN, EINPROGRESS => {
|
||||||
|
const loop = std.event.Loop.instance orelse return error.WouldBlock;
|
||||||
|
loop.waitUntilFdWritableOrReadable(sockfd);
|
||||||
|
return getsockoptError(sockfd);
|
||||||
|
},
|
||||||
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
|
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
|
||||||
EBADF => unreachable, // sockfd is not a valid open file descriptor.
|
EBADF => unreachable, // sockfd is not a valid open file descriptor.
|
||||||
ECONNREFUSED => return error.ConnectionRefused,
|
ECONNREFUSED => return error.ConnectionRefused,
|
||||||
EFAULT => unreachable, // The socket structure address is outside the user's address space.
|
EFAULT => unreachable, // The socket structure address is outside the user's address space.
|
||||||
EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately.
|
|
||||||
EINTR => continue,
|
EINTR => continue,
|
||||||
EISCONN => unreachable, // The socket is already connected.
|
EISCONN => unreachable, // The socket is already connected.
|
||||||
ENETUNREACH => return error.NetworkUnreachable,
|
ENETUNREACH => return error.NetworkUnreachable,
|
||||||
@ -1884,34 +1949,6 @@ pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!v
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `connect` except it is for non-blocking socket file descriptors.
|
|
||||||
/// It expects to receive EINPROGRESS`.
|
|
||||||
pub fn connect_async(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void {
|
|
||||||
while (true) {
|
|
||||||
switch (errno(system.connect(sockfd, sock_addr, len))) {
|
|
||||||
EINVAL => unreachable,
|
|
||||||
EINTR => continue,
|
|
||||||
0, EINPROGRESS => return,
|
|
||||||
EACCES => return error.PermissionDenied,
|
|
||||||
EPERM => return error.PermissionDenied,
|
|
||||||
EADDRINUSE => return error.AddressInUse,
|
|
||||||
EADDRNOTAVAIL => return error.AddressNotAvailable,
|
|
||||||
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
|
|
||||||
EAGAIN => return error.SystemResources,
|
|
||||||
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
|
|
||||||
EBADF => unreachable, // sockfd is not a valid open file descriptor.
|
|
||||||
ECONNREFUSED => return error.ConnectionRefused,
|
|
||||||
EFAULT => unreachable, // The socket structure address is outside the user's address space.
|
|
||||||
EISCONN => unreachable, // The socket is already connected.
|
|
||||||
ENETUNREACH => return error.NetworkUnreachable,
|
|
||||||
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
|
||||||
EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
|
|
||||||
ETIMEDOUT => return error.ConnectionTimedOut,
|
|
||||||
else => |err| return unexpectedErrno(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getsockoptError(sockfd: i32) ConnectError!void {
|
pub fn getsockoptError(sockfd: i32) ConnectError!void {
|
||||||
var err_code: u32 = undefined;
|
var err_code: u32 = undefined;
|
||||||
var size: u32 = @sizeOf(u32);
|
var size: u32 = @sizeOf(u32);
|
||||||
@ -2962,7 +2999,7 @@ pub fn sendto(
|
|||||||
|
|
||||||
EACCES => return error.AccessDenied,
|
EACCES => return error.AccessDenied,
|
||||||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
loop.waitUntilFdWritable(sockfd) catch return error.WouldBlock;
|
loop.waitUntilFdWritable(sockfd);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return error.WouldBlock;
|
return error.WouldBlock;
|
||||||
@ -3065,7 +3102,7 @@ pub fn recvfrom(
|
|||||||
ENOTSOCK => unreachable,
|
ENOTSOCK => unreachable,
|
||||||
EINTR => continue,
|
EINTR => continue,
|
||||||
EAGAIN => if (std.event.Loop.instance) |loop| {
|
EAGAIN => if (std.event.Loop.instance) |loop| {
|
||||||
loop.waitUntilFdReadable(sockfd) catch return error.WouldBlock;
|
loop.waitUntilFdReadable(sockfd);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return error.WouldBlock;
|
return error.WouldBlock;
|
||||||
|
@ -205,6 +205,8 @@ pub const Target = union(enum) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const stack_align = 16;
|
||||||
|
|
||||||
pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
|
pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
|
||||||
return std.fmt.allocPrint(
|
return std.fmt.allocPrint(
|
||||||
allocator,
|
allocator,
|
||||||
|
@ -128,6 +128,7 @@ export fn stage2_free_clang_errors(errors_ptr: [*]translate_c.ClangErrMsg, error
|
|||||||
export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
|
export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
|
||||||
const c_out_stream = &std.io.COutStream.init(output_file).stream;
|
const c_out_stream = &std.io.COutStream.init(output_file).stream;
|
||||||
_ = std.zig.render(std.heap.c_allocator, c_out_stream, tree) catch |e| switch (e) {
|
_ = std.zig.render(std.heap.c_allocator, c_out_stream, tree) catch |e| switch (e) {
|
||||||
|
error.WouldBlock => unreachable, // stage1 opens stuff in exclusively blocking mode
|
||||||
error.SystemResources => return Error.SystemResources,
|
error.SystemResources => return Error.SystemResources,
|
||||||
error.OperationAborted => return Error.OperationAborted,
|
error.OperationAborted => return Error.OperationAborted,
|
||||||
error.BrokenPipe => return Error.BrokenPipe,
|
error.BrokenPipe => return Error.BrokenPipe,
|
||||||
|
@ -4381,6 +4381,10 @@ static Error analyze_callee_async(CodeGen *g, ZigFn *fn, ZigFn *callee, AstNode
|
|||||||
if (callee->anal_state == FnAnalStateComplete) {
|
if (callee->anal_state == FnAnalStateComplete) {
|
||||||
analyze_fn_async(g, callee, true);
|
analyze_fn_async(g, callee, true);
|
||||||
if (callee->anal_state == FnAnalStateInvalid) {
|
if (callee->anal_state == FnAnalStateInvalid) {
|
||||||
|
if (g->trace_err != nullptr) {
|
||||||
|
g->trace_err = add_error_note(g, g->trace_err, call_node,
|
||||||
|
buf_sprintf("while checking if '%s' is async", buf_ptr(&fn->symbol_name)));
|
||||||
|
}
|
||||||
return ErrorSemanticAnalyzeFail;
|
return ErrorSemanticAnalyzeFail;
|
||||||
}
|
}
|
||||||
callee_is_async = fn_is_async(callee);
|
callee_is_async = fn_is_async(callee);
|
||||||
@ -7538,7 +7542,9 @@ bool type_is_c_abi_int(CodeGen *g, ZigType *ty) {
|
|||||||
|
|
||||||
uint32_t get_host_int_bytes(CodeGen *g, ZigType *struct_type, TypeStructField *field) {
|
uint32_t get_host_int_bytes(CodeGen *g, ZigType *struct_type, TypeStructField *field) {
|
||||||
assert(struct_type->id == ZigTypeIdStruct);
|
assert(struct_type->id == ZigTypeIdStruct);
|
||||||
assert(type_is_resolved(struct_type, ResolveStatusSizeKnown));
|
if (struct_type->data.structure.layout != ContainerLayoutAuto) {
|
||||||
|
assert(type_is_resolved(struct_type, ResolveStatusSizeKnown));
|
||||||
|
}
|
||||||
if (struct_type->data.structure.host_int_bytes == nullptr)
|
if (struct_type->data.structure.host_int_bytes == nullptr)
|
||||||
return 0;
|
return 0;
|
||||||
return struct_type->data.structure.host_int_bytes[field->gen_index];
|
return struct_type->data.structure.host_int_bytes[field->gen_index];
|
||||||
|
13
src/ir.cpp
13
src/ir.cpp
@ -17692,7 +17692,8 @@ static IrInstruction *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstruct
|
|||||||
{
|
{
|
||||||
size_t offset = ptr_field->data.x_ptr.data.base_array.elem_index;
|
size_t offset = ptr_field->data.x_ptr.data.base_array.elem_index;
|
||||||
uint64_t new_index = offset + index;
|
uint64_t new_index = offset + index;
|
||||||
assert(new_index < ptr_field->data.x_ptr.data.base_array.array_val->type->data.array.len);
|
ir_assert(new_index < ptr_field->data.x_ptr.data.base_array.array_val->type->data.array.len,
|
||||||
|
&elem_ptr_instruction->base);
|
||||||
out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
|
out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
|
||||||
out_val->data.x_ptr.data.base_array.array_val =
|
out_val->data.x_ptr.data.base_array.array_val =
|
||||||
ptr_field->data.x_ptr.data.base_array.array_val;
|
ptr_field->data.x_ptr.data.base_array.array_val;
|
||||||
@ -17854,7 +17855,10 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction
|
|||||||
case OnePossibleValueNo:
|
case OnePossibleValueNo:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ((err = type_resolve(ira->codegen, struct_type, ResolveStatusAlignmentKnown)))
|
ResolveStatus needed_resolve_status =
|
||||||
|
(struct_type->data.structure.layout == ContainerLayoutAuto) ?
|
||||||
|
ResolveStatusZeroBitsKnown : ResolveStatusSizeKnown;
|
||||||
|
if ((err = type_resolve(ira->codegen, struct_type, needed_resolve_status)))
|
||||||
return ira->codegen->invalid_instruction;
|
return ira->codegen->invalid_instruction;
|
||||||
assert(struct_ptr->value.type->id == ZigTypeIdPointer);
|
assert(struct_ptr->value.type->id == ZigTypeIdPointer);
|
||||||
uint32_t ptr_bit_offset = struct_ptr->value.type->data.pointer.bit_offset_in_host;
|
uint32_t ptr_bit_offset = struct_ptr->value.type->data.pointer.bit_offset_in_host;
|
||||||
@ -17873,6 +17877,9 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction
|
|||||||
return ira->codegen->invalid_instruction;
|
return ira->codegen->invalid_instruction;
|
||||||
|
|
||||||
if (ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
|
if (ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
|
||||||
|
if ((err = type_resolve(ira->codegen, struct_type, ResolveStatusSizeKnown)))
|
||||||
|
return ira->codegen->invalid_instruction;
|
||||||
|
|
||||||
ConstExprValue *struct_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_instr->source_node);
|
ConstExprValue *struct_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_instr->source_node);
|
||||||
if (struct_val == nullptr)
|
if (struct_val == nullptr)
|
||||||
return ira->codegen->invalid_instruction;
|
return ira->codegen->invalid_instruction;
|
||||||
@ -17919,7 +17926,7 @@ static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_
|
|||||||
Error err;
|
Error err;
|
||||||
|
|
||||||
ZigType *bare_type = container_ref_type(container_type);
|
ZigType *bare_type = container_ref_type(container_type);
|
||||||
if ((err = type_resolve(ira->codegen, bare_type, ResolveStatusSizeKnown)))
|
if ((err = type_resolve(ira->codegen, bare_type, ResolveStatusZeroBitsKnown)))
|
||||||
return ira->codegen->invalid_instruction;
|
return ira->codegen->invalid_instruction;
|
||||||
|
|
||||||
assert(container_ptr->value.type->id == ZigTypeIdPointer);
|
assert(container_ptr->value.type->id == ZigTypeIdPointer);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user