zig/lib/std/event/net.zig

359 lines
12 KiB
Zig
Raw Normal View History

2019-03-03 05:46:04 +08:00
const std = @import("../std.zig");
2018-07-10 10:22:44 +08:00
const builtin = @import("builtin");
const testing = std.testing;
2018-07-10 10:22:44 +08:00
const event = std.event;
const mem = std.mem;
const os = std.os;
2018-07-10 10:22:44 +08:00
const Loop = std.event.Loop;
2019-05-25 10:52:07 +08:00
const File = std.fs.File;
2019-05-26 01:07:44 +08:00
const fd_t = os.fd_t;
pub const Server = struct {
2019-08-12 07:53:10 +08:00
handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
2018-07-10 10:22:44 +08:00
loop: *Loop,
sockfd: ?i32,
accept_frame: ?anyframe,
2018-07-10 10:22:44 +08:00
listen_address: std.net.Address,
waiting_for_emfile_node: PromiseNode,
listen_resume_node: event.Loop.ResumeNode,
2019-08-12 07:53:10 +08:00
const PromiseNode = std.TailQueue(anyframe).Node;
2018-07-10 10:22:44 +08:00
pub fn init(loop: *Loop) Server {
// TODO can't initialize handler here because we need well defined copy elision
return Server{
2018-07-10 10:22:44 +08:00
.loop = loop,
.sockfd = null,
.accept_frame = null,
2018-07-10 10:22:44 +08:00
.handleRequestFn = undefined,
.waiting_for_emfile_node = undefined,
.listen_address = undefined,
.listen_resume_node = event.Loop.ResumeNode{
2018-07-10 10:22:44 +08:00
.id = event.Loop.ResumeNode.Id.Basic,
.handle = undefined,
2018-09-13 02:26:21 +08:00
.overlapped = event.Loop.ResumeNode.overlapped_init,
2018-07-10 10:22:44 +08:00
},
};
}
pub fn listen(
self: *Server,
address: *const std.net.Address,
2019-08-12 07:53:10 +08:00
handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
2018-07-10 10:22:44 +08:00
) !void {
self.handleRequestFn = handleRequestFn;
2019-05-27 11:35:26 +08:00
const sockfd = try os.socket(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK, os.PROTO_tcp);
errdefer os.close(sockfd);
2018-07-10 10:22:44 +08:00
self.sockfd = sockfd;
2019-05-27 11:35:26 +08:00
try os.bind(sockfd, &address.os_addr);
try os.listen(sockfd, os.SOMAXCONN);
self.listen_address = std.net.Address.initPosix(try os.getsockname(sockfd));
2018-07-10 10:22:44 +08:00
self.accept_frame = async Server.handler(self);
2019-08-16 02:05:12 +08:00
errdefer await self.accept_frame.?;
2018-07-10 10:22:44 +08:00
self.listen_resume_node.handle = self.accept_frame.?;
2019-05-26 01:07:44 +08:00
try self.loop.linuxAddFd(sockfd, &self.listen_resume_node, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
2018-07-10 10:22:44 +08:00
errdefer self.loop.removeFd(sockfd);
}
/// Stop listening
pub fn close(self: *Server) void {
self.loop.linuxRemoveFd(self.sockfd.?);
2019-05-27 11:35:26 +08:00
if (self.sockfd) |fd| {
os.close(fd);
self.sockfd = null;
}
2018-07-10 10:22:44 +08:00
}
pub fn deinit(self: *Server) void {
2019-08-16 02:05:12 +08:00
if (self.accept_frame) |accept_frame| await accept_frame;
if (self.sockfd) |sockfd| os.close(sockfd);
2018-07-10 10:22:44 +08:00
}
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
2019-05-27 11:35:26 +08:00
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;
}
2019-05-25 10:52:07 +08:00
var socket = File.openHandle(accepted_fd);
2019-08-12 07:53:10 +08:00
self.handleRequestFn(self, &accepted_addr, socket);
2018-07-10 10:22:44 +08:00
} else |err| switch (err) {
error.ProcessFdQuotaExceeded => @panic("TODO handle this error"),
error.ConnectionAborted => continue,
2018-07-10 10:22:44 +08:00
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 {
2019-05-27 11:35:26 +08:00
const sockfd = try os.socket(
2019-05-26 01:07:44 +08:00
os.AF_UNIX,
os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK,
0,
);
errdefer os.close(sockfd);
2019-05-26 01:07:44 +08:00
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);
2019-05-26 01:07:44 +08:00
const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
2019-05-27 11:35:26 +08:00
try os.connect_async(sockfd, &sock_addr, size);
2019-08-12 07:53:10 +08:00
try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
2019-05-27 11:35:26 +08:00
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 {
2019-05-26 01:07:44 +08:00
const iov = os.iovec{
.iov_base = buffer.ptr,
.iov_len = buffer.len,
};
2019-05-26 01:07:44 +08:00
const iovs: *const [1]os.iovec = &iov;
2019-08-12 07:53:10 +08:00
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 {
2019-05-26 01:07:44 +08:00
const iov = os.iovec_const{
.iov_base = buffer.ptr,
.iov_len = buffer.len,
};
2019-05-26 01:07:44 +08:00
const iovs: *const [1]os.iovec_const = &iov;
2019-08-12 07:53:10 +08:00
return writevPosix(loop, fd, iovs, 1);
}
2019-05-26 01:07:44 +08:00
pub async fn writevPosix(loop: *Loop, fd: i32, iov: [*]const os.iovec_const, count: usize) !void {
while (true) {
switch (builtin.os) {
2019-05-26 01:07:44 +08:00
.macosx, .linux => {
switch (os.errno(os.system.writev(fd, iov, count))) {
0 => return,
2019-05-26 01:07:44 +08:00
os.EINTR => continue,
os.ESPIPE => unreachable,
os.EINVAL => unreachable,
os.EFAULT => unreachable,
os.EAGAIN => {
2019-08-12 07:53:10 +08:00
try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT);
continue;
},
2019-05-26 01:07:44 +08:00
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.
2019-05-26 01:07:44 +08:00
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 => {
2019-05-26 01:07:44 +08:00
const rc = os.system.readv(fd, iov, count);
switch (os.errno(rc)) {
0 => return rc,
2019-05-26 01:07:44 +08:00
os.EINTR => continue,
os.EINVAL => unreachable,
os.EFAULT => unreachable,
os.EAGAIN => {
2019-08-12 07:53:10 +08:00
try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN);
continue;
},
2019-05-26 01:07:44 +08:00
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 {
2019-05-26 01:07:44 +08:00
const iovecs = try loop.allocator.alloc(os.iovec_const, data.len);
defer loop.allocator.free(iovecs);
for (data) |buf, i| {
2019-05-26 01:07:44 +08:00
iovecs[i] = os.iovec_const{
.iov_base = buf.ptr,
.iov_len = buf.len,
};
}
2019-08-12 07:53:10 +08:00
return writevPosix(loop, fd, iovecs.ptr, data.len);
}
pub async fn readv(loop: *Loop, fd: fd_t, data: []const []u8) !usize {
2019-05-26 01:07:44 +08:00
const iovecs = try loop.allocator.alloc(os.iovec, data.len);
defer loop.allocator.free(iovecs);
for (data) |buf, i| {
2019-05-26 01:07:44 +08:00
iovecs[i] = os.iovec{
.iov_base = buf.ptr,
.iov_len = buf.len,
};
}
2019-08-12 07:53:10 +08:00
return readvPosix(loop, fd, iovecs.ptr, data.len);
}
2019-05-25 10:52:07 +08:00
pub async fn connect(loop: *Loop, _address: *const std.net.Address) !File {
var address = _address.*; // TODO https://github.com/ziglang/zig/issues/1592
2018-07-10 10:22:44 +08:00
2019-05-27 11:35:26 +08:00
const sockfd = try os.socket(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK, os.PROTO_tcp);
errdefer os.close(sockfd);
2018-07-10 10:22:44 +08:00
2019-05-27 11:35:26 +08:00
try os.connect_async(sockfd, &address.os_addr, @sizeOf(os.sockaddr_in));
2019-08-12 07:53:10 +08:00
try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
2019-05-27 11:35:26 +08:00
try os.getsockoptError(sockfd);
2018-07-10 10:22:44 +08:00
2019-05-25 10:52:07 +08:00
return File.openHandle(sockfd);
2018-07-10 10:22:44 +08:00
}
test "listen on a port, send bytes, receive bytes" {
2019-06-23 12:41:11 +08:00
// 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;
2018-07-10 10:22:44 +08:00
}
const MyServer = struct {
2018-07-10 10:22:44 +08:00
tcp_server: Server,
const Self = @This();
2019-08-12 07:53:10 +08:00
async fn handler(tcp_server: *Server, _addr: *const std.net.Address, _socket: File) void {
2018-07-10 10:22:44 +08:00
const self = @fieldParentPtr(Self, "tcp_server", tcp_server);
var socket = _socket; // TODO https://github.com/ziglang/zig/issues/1592
2018-07-10 10:22:44 +08:00
defer socket.close();
2019-08-12 07:53:10 +08:00
const next_handler = errorableHandler(self, _addr, socket) catch |err| {
2018-07-10 10:22:44 +08:00
std.debug.panic("unable to handle connection: {}\n", err);
};
}
2019-05-25 10:52:07 +08:00
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
2018-07-10 10:22:44 +08:00
const stream = &socket.outStream().stream;
2018-07-10 10:22:44 +08:00
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) };
2018-07-10 10:22:44 +08:00
defer server.tcp_server.deinit();
try server.tcp_server.listen(&addr, MyServer.handler);
2018-07-10 10:22:44 +08:00
2019-08-12 07:53:10 +08:00
_ = async doAsyncTest(&loop, &server.tcp_server.listen_address, &server.tcp_server);
2018-07-10 10:22:44 +08:00
loop.run();
}
async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Server) void {
errdefer @panic("test failure");
2019-08-12 07:53:10 +08:00
var socket_file = try connect(loop, address);
2018-07-10 10:22:44 +08:00
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"));
2018-07-10 10:22:44 +08:00
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 },
};
}
2019-08-12 07:53:10 +08:00
async fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
const self = @fieldParentPtr(OutStream, "stream", out_stream);
2019-08-12 07:53:10 +08:00
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 },
};
}
2019-08-12 07:53:10 +08:00
async fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
const self = @fieldParentPtr(InStream, "stream", in_stream);
2019-08-12 07:53:10 +08:00
return read(self.loop, self.fd, bytes);
}
};