├── .gitignore ├── LICENSE ├── README.md ├── event_epoll.zig ├── event_iocp.zig ├── event_kqueue.zig ├── example_event.zig ├── example_signal.zig ├── example_tcp_benchmark.zig ├── example_tcp_client.zig ├── example_tcp_server.zig ├── example_udp_benchmark.zig ├── notifier_epoll.zig ├── notifier_iocp.zig ├── notifier_kqueue.zig ├── os ├── posix.zig ├── windows.zig └── windows │ ├── kernel32.zig │ └── ws2_32.zig ├── pike.zig ├── signal_posix.zig ├── signal_windows.zig ├── socket_posix.zig ├── socket_windows.zig ├── waker.zig └── zig.mod /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | zig-cache/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kenta Iwasaki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pike 2 | 3 | A minimal cross-platform high-performance async I/O library written in [Zig](https://ziglang.org). 4 | 5 | ## Features 6 | 7 | - [x] Reactor/proactor-based I/O notification support 8 | - [x] epoll (linux) 9 | - [x] kqueue (darwin) 10 | - [x] i/o completion ports (windows) 11 | - [x] Async POSIX socket support 12 | - [x] `bind`, `listen`, `connect`, `accept` 13 | - [x] `read`, `recv`, `recvFrom` 14 | - [x] `write`, `send`, `sendTo` 15 | - [x] get/set socket options 16 | - [x] Async Windows socket support 17 | - [x] `bind`, `listen`, `connect`, `accept` 18 | - [x] `read`, `recv`, `recvFrom` 19 | - [x] `write`, `send`, `sendTo` 20 | - [x] get/set socket options 21 | - [x] Async signal support 22 | - [x] signalfd for epoll (linux) 23 | - [x] EVFILT_SIGNAL for kqueue (darwin) 24 | - [x] SetConsoleCtrlHandler for i/o completion ports (windows) 25 | - [x] Async event support 26 | - [x] sigaction (posix) 27 | - [x] SetConsoleCtrlHandler (windows) 28 | 29 | ## Design 30 | 31 | ### Notifier 32 | 33 | A `Notifier` notifies of the completion of I/O events, or of the read/write-readiness of registered file descriptors/handles. 34 | 35 | Should a `Notifier` report the completion of I/O events, it is designated to wrap around a proactor-based I/O notification layer in the operating system such as I/O completion ports on Windows. 36 | 37 | Should a `Notifier` report the read/write-readiness of registered file descriptors/handles, it is designated to wrap around a reactor-based I/O notification layer in the operating system such as epoll on Linux, or kqueue on Darwin-based operating systems. 38 | 39 | The `Notifier`'s purpose is to drive the execution of asynchronous I/O syscalls upon the notification of a reactor/proactor-based I/O event by dispatching suspended asynchronous function frames to be resumed by a thread pool/scheduler (e.g. [kprotty/zap](https://github.com/kprotty/zap)). 40 | 41 | ### Handle 42 | 43 | A `Handle`'s implementation is specific to a `Notifier` implementation, though overall wraps around and represents a file descriptor/handle in a program. 44 | 45 | Subject to the `Notifier` implementation a `Handle`'s implementation falls under, state required to drive asynchronous I/O syscalls through a `Handle` is kept inside a `Handle`. 46 | 47 | An example would be an intrusive linked list of suspended asynchronous function frames that are to be resumed upon the recipient of a notification that a file descriptor/handle is ready to be written to/read from. -------------------------------------------------------------------------------- /event_epoll.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const Waker = @import("waker.zig").Waker; 4 | const os = std.os; 5 | const mem = std.mem; 6 | 7 | pub const Event = struct { 8 | const Self = @This(); 9 | 10 | handle: pike.Handle, 11 | readers: Waker = .{}, 12 | writers: Waker = .{}, 13 | 14 | pub fn init() !Self { 15 | return Self{ 16 | .handle = .{ 17 | .inner = try os.eventfd(0, os.linux.EFD.CLOEXEC | os.linux.EFD.NONBLOCK), 18 | .wake_fn = wake, 19 | }, 20 | }; 21 | } 22 | 23 | pub fn deinit(self: *Self) void { 24 | os.close(self.handle.inner); 25 | 26 | if (self.writers.shutdown()) |task| pike.dispatch(task, .{}); 27 | if (self.readers.shutdown()) |task| pike.dispatch(task, .{}); 28 | } 29 | 30 | pub fn registerTo(self: *const Self, notifier: *const pike.Notifier) !void { 31 | try notifier.register(&self.handle, .{ .read = true, .write = true }); 32 | } 33 | 34 | fn wake(handle: *pike.Handle, batch: *pike.Batch, opts: pike.WakeOptions) void { 35 | const self = @fieldParentPtr(Self, "handle", handle); 36 | 37 | if (opts.write_ready) if (self.writers.notify()) |task| batch.push(task); 38 | if (opts.read_ready) if (self.readers.notify()) |task| batch.push(task); 39 | if (opts.shutdown) { 40 | if (self.writers.shutdown()) |task| batch.push(task); 41 | if (self.readers.shutdown()) |task| batch.push(task); 42 | } 43 | } 44 | 45 | fn ErrorUnionOf(comptime func: anytype) std.builtin.TypeInfo.ErrorUnion { 46 | return @typeInfo(@typeInfo(@TypeOf(func)).Fn.return_type.?).ErrorUnion; 47 | } 48 | 49 | fn call(self: *Self, comptime function: anytype, args: anytype, comptime opts: pike.CallOptions) !ErrorUnionOf(function).payload { 50 | while (true) { 51 | const result = @call(.{ .modifier = .always_inline }, function, args) catch |err| switch (err) { 52 | error.WouldBlock => { 53 | if (comptime opts.write) { 54 | try self.writers.wait(.{ .use_lifo = true }); 55 | } else if (comptime opts.read) { 56 | try self.readers.wait(.{}); 57 | } 58 | continue; 59 | }, 60 | else => return err, 61 | }; 62 | 63 | return result; 64 | } 65 | } 66 | 67 | fn write(self: *Self, amount: u64) callconv(.Async) !void { 68 | const num_bytes = try self.call(os.write, .{ 69 | self.handle.inner, 70 | mem.asBytes(&amount), 71 | }, .{ .write = true }); 72 | 73 | if (num_bytes != @sizeOf(@TypeOf(amount))) { 74 | return error.ShortWrite; 75 | } 76 | } 77 | 78 | fn read(self: *Self) callconv(.Async) !void { 79 | var counter: u64 = 0; 80 | 81 | const num_bytes = try self.call(os.read, .{ 82 | self.handle.inner, 83 | mem.asBytes(&counter), 84 | }, .{ .read = true }); 85 | 86 | if (num_bytes != @sizeOf(@TypeOf(counter))) { 87 | return error.ShortRead; 88 | } 89 | } 90 | 91 | pub fn post(self: *Self) callconv(.Async) !void { 92 | var frame = async self.read(); 93 | try self.write(1); 94 | try await frame; 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /event_iocp.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const windows = @import("os/windows.zig"); 4 | 5 | pub const Event = struct { 6 | const Self = @This(); 7 | 8 | port: windows.HANDLE = windows.INVALID_HANDLE_VALUE, 9 | 10 | pub fn init() !Self { 11 | return Self{}; 12 | } 13 | 14 | pub fn deinit(_: *Self) void {} 15 | 16 | pub fn registerTo(self: *Self, notifier: *const pike.Notifier) !void { 17 | self.port = notifier.handle; 18 | } 19 | 20 | pub fn post(self: *const Self) callconv(.Async) !void { 21 | var overlapped = pike.Overlapped.init(pike.Task.init(@frame())); 22 | 23 | var err: ?windows.PostQueuedCompletionStatusError = null; 24 | 25 | suspend { 26 | windows.PostQueuedCompletionStatus(self.port, 0, 0, &overlapped.inner) catch |post_err| { 27 | err = post_err; 28 | pike.dispatch(&overlapped.task, .{ .use_lifo = true }); 29 | }; 30 | } 31 | 32 | if (err) |post_err| return post_err; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /event_kqueue.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const Waker = @import("waker.zig").Waker; 4 | 5 | const os = std.os; 6 | 7 | pub const Event = struct { 8 | const Self = @This(); 9 | 10 | handle: pike.Handle = .{ 11 | .inner = -1, 12 | .wake_fn = wake, 13 | }, 14 | waker: Waker = .{}, 15 | 16 | inner: os.Kevent, 17 | notifier: os.fd_t, 18 | 19 | var count: u32 = 0; 20 | 21 | pub fn init() !Self { 22 | const ident = @atomicRmw(u32, &count, .Add, 1, .SeqCst); 23 | 24 | return Self{ 25 | .inner = .{ 26 | .ident = @intCast(usize, ident), 27 | .filter = os.EVFILT_USER, 28 | .flags = os.EV_ADD | os.EV_DISABLE, 29 | .fflags = 0, 30 | .data = 0, 31 | .udata = 0, 32 | }, 33 | .notifier = -1, 34 | }; 35 | } 36 | 37 | pub fn deinit(self: *Self) void { 38 | _ = @atomicRmw(u32, &count, .Sub, 1, .SeqCst); 39 | 40 | self.inner.flags = os.EV_DELETE; 41 | self.inner.fflags = 0; 42 | 43 | if ((os.kevent(self.notifier, @as(*const [1]os.Kevent, &self.inner), &[0]os.Kevent{}, null) catch unreachable) != 0) { 44 | @panic("pike/event (darwin): unexpectedly registered new events while calling deinit()"); 45 | } 46 | 47 | if (self.waker.shutdown()) |task| pike.dispatch(task, .{}); 48 | } 49 | 50 | pub fn registerTo(self: *Self, notifier: *const pike.Notifier) !void { 51 | self.notifier = notifier.handle; 52 | self.inner.udata = @ptrToInt(self); 53 | 54 | if ((try os.kevent(self.notifier, @as(*const [1]os.Kevent, &self.inner), &[0]os.Kevent{}, null)) != 0) { 55 | return error.Unexpected; 56 | } 57 | 58 | self.inner.flags = os.EV_ENABLE; 59 | self.inner.fflags = os.NOTE_TRIGGER; 60 | } 61 | 62 | inline fn wake(handle: *pike.Handle, batch: *pike.Batch, opts: pike.WakeOptions) void { 63 | const self = @fieldParentPtr(Self, "handle", handle); 64 | 65 | if (opts.write_ready) @panic("pike/event (darwin): kqueue unexpectedly reported write-readiness"); 66 | if (opts.read_ready) @panic("pike/event (darwin): kqueue unexpectedly reported read-readiness"); 67 | if (opts.notify) if (self.waker.notify()) |task| batch.push(task); 68 | if (opts.shutdown) if (self.waker.shutdown()) |task| batch.push(task); 69 | } 70 | 71 | pub fn post(self: *Self) callconv(.Async) !void { 72 | if ((try os.kevent(self.notifier, @as(*const [1]os.Kevent, &self.inner), &[0]os.Kevent{}, null)) != 0) { 73 | return error.Unexpected; 74 | } 75 | 76 | try self.waker.wait(.{}); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /example_event.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | 4 | const log = std.log; 5 | 6 | pub fn main() !void { 7 | try pike.init(); 8 | defer pike.deinit(); 9 | 10 | const notifier = try pike.Notifier.init(); 11 | defer notifier.deinit(); 12 | 13 | var event = try pike.Event.init(); 14 | defer nosuspend event.deinit(); 15 | 16 | try event.registerTo(¬ifier); 17 | 18 | var frame: @Frame(pike.Event.post) = undefined; 19 | 20 | frame = async event.post(); 21 | try notifier.poll(10_000); 22 | try nosuspend await frame; 23 | 24 | log.info("Drove the poller once.", .{}); 25 | 26 | frame = async event.post(); 27 | try notifier.poll(10_000); 28 | try nosuspend await frame; 29 | 30 | log.info("Drove the poller twice!", .{}); 31 | 32 | frame = async event.post(); 33 | try notifier.poll(10_000); 34 | try nosuspend await frame; 35 | 36 | log.info("Drove the poller thrice!", .{}); 37 | 38 | try notifier.poll(100); 39 | 40 | log.info("This time the poller wasn't driven - it timed out after 100ms.", .{}); 41 | } 42 | -------------------------------------------------------------------------------- /example_signal.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | 4 | const os = std.os; 5 | const log = std.log; 6 | 7 | pub fn main() !void { 8 | try pike.init(); 9 | defer pike.deinit(); 10 | 11 | const notifier = try pike.Notifier.init(); 12 | defer notifier.deinit(); 13 | 14 | var stopped = false; 15 | 16 | var frame = async run(¬ifier, &stopped); 17 | 18 | while (!stopped) { 19 | try notifier.poll(10_000); 20 | } 21 | 22 | try nosuspend await frame; 23 | } 24 | 25 | fn run(notifier: *const pike.Notifier, stopped: *bool) !void { 26 | var event = try pike.Event.init(); 27 | defer event.deinit(); 28 | 29 | try event.registerTo(notifier); 30 | 31 | var signal = try pike.Signal.init(.{ .interrupt = true }); 32 | defer signal.deinit(); 33 | 34 | defer { 35 | stopped.* = true; 36 | event.post() catch unreachable; 37 | } 38 | 39 | log.info("Press Ctrl+C.", .{}); 40 | 41 | try signal.wait(); 42 | 43 | log.info("Do it again!", .{}); 44 | 45 | try signal.wait(); 46 | 47 | log.info("I promise; one more time.", .{}); 48 | 49 | try signal.wait(); 50 | } 51 | -------------------------------------------------------------------------------- /example_tcp_benchmark.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | 4 | const os = std.os; 5 | const fmt = std.fmt; 6 | const log = std.log; 7 | const net = std.net; 8 | const mem = std.mem; 9 | const heap = std.heap; 10 | const process = std.process; 11 | 12 | fn exists(args: []const []const u8, flags: anytype) ?usize { 13 | inline for (flags) |flag| { 14 | for (args) |arg, index| { 15 | if (mem.eql(u8, arg, flag)) { 16 | return index; 17 | } 18 | } 19 | } 20 | return null; 21 | } 22 | 23 | pub fn main() !void { 24 | var gpa: heap.GeneralPurposeAllocator(.{}) = .{}; 25 | defer _ = gpa.deinit(); 26 | 27 | const allocator = &gpa.allocator; 28 | 29 | const args = try process.argsAlloc(allocator); 30 | defer process.argsFree(allocator, args); 31 | 32 | const run_server = args.len == 1 or (args.len > 1 and exists(args, .{ "server", "--server", "-server", "-s" }) != null); 33 | const run_client = args.len == 1 or (args.len > 1 and exists(args, .{ "client", "--client", "-client", "-c" }) != null); 34 | 35 | const address: net.Address = blk: { 36 | const default = try net.Address.parseIp("127.0.0.1", 9000); 37 | 38 | const index = exists(args, .{ "address", "--address", "-address", "-a" }) orelse { 39 | break :blk default; 40 | }; 41 | 42 | if (args.len <= index + 1) break :blk default; 43 | 44 | var fields = mem.split(u8, args[index + 1], ":"); 45 | 46 | const addr_host = fields.next().?; 47 | const addr_port = try fmt.parseInt(u16, fields.next().?, 10); 48 | 49 | break :blk try net.Address.parseIp(addr_host, addr_port); 50 | }; 51 | 52 | try pike.init(); 53 | defer pike.deinit(); 54 | 55 | const notifier = try pike.Notifier.init(); 56 | defer notifier.deinit(); 57 | 58 | var stopped = false; 59 | var server_frame: @Frame(runBenchmarkServer) = undefined; 60 | var client_frame: @Frame(runBenchmarkClient) = undefined; 61 | 62 | if (run_server) server_frame = async runBenchmarkServer(¬ifier, address, &stopped); 63 | if (run_client) client_frame = async runBenchmarkClient(¬ifier, address, &stopped); 64 | 65 | defer if (run_server) nosuspend await server_frame catch |err| @panic(@errorName(err)); 66 | defer if (run_client) nosuspend await client_frame catch |err| @panic(@errorName(err)); 67 | 68 | while (!stopped) { 69 | try notifier.poll(10_000); 70 | } 71 | } 72 | 73 | fn runBenchmarkServer(notifier: *const pike.Notifier, address: net.Address, stopped: *bool) !void { 74 | defer stopped.* = true; 75 | 76 | var socket = try pike.Socket.init(os.AF.INET, os.SOCK.STREAM, os.IPPROTO.TCP, 0); 77 | defer socket.deinit(); 78 | 79 | try socket.registerTo(notifier); 80 | 81 | try socket.set(.reuse_address, true); 82 | try socket.bind(address); 83 | try socket.listen(128); 84 | 85 | log.info("Listening for clients on: {}", .{try socket.getBindAddress()}); 86 | 87 | var client = try socket.accept(); 88 | defer client.socket.deinit(); 89 | 90 | log.info("Accepted client: {}", .{client.address}); 91 | 92 | try client.socket.registerTo(notifier); 93 | 94 | var buf: [1024]u8 = undefined; 95 | while (true) { 96 | _ = try client.socket.send(&buf, 0); 97 | } 98 | } 99 | 100 | fn runBenchmarkClient(notifier: *const pike.Notifier, address: net.Address, stopped: *bool) !void { 101 | defer stopped.* = true; 102 | 103 | var socket = try pike.Socket.init(os.AF.INET, os.SOCK.STREAM, os.IPPROTO.TCP, 0); 104 | defer socket.deinit(); 105 | 106 | try socket.registerTo(notifier); 107 | try socket.connect(address); 108 | 109 | log.info("Connected to: {}", .{address}); 110 | 111 | var buf: [1024]u8 = undefined; 112 | while (true) { 113 | const n = try socket.recv(&buf, 0); 114 | if (n == 0) return; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /example_tcp_client.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | 4 | const os = std.os; 5 | const net = std.net; 6 | const log = std.log; 7 | 8 | pub fn main() !void { 9 | try pike.init(); 10 | defer pike.deinit(); 11 | 12 | const notifier = try pike.Notifier.init(); 13 | defer notifier.deinit(); 14 | 15 | var stopped = false; 16 | 17 | var frame = async run(¬ifier, &stopped); 18 | 19 | while (!stopped) { 20 | try notifier.poll(10_000); 21 | } 22 | 23 | try nosuspend await frame; 24 | } 25 | 26 | fn run(notifier: *const pike.Notifier, stopped: *bool) !void { 27 | defer stopped.* = true; 28 | 29 | const address = try net.Address.parseIp("127.0.0.1", 44123); 30 | 31 | var socket = try pike.Socket.init(os.AF.INET, os.SOCK.STREAM, os.IPPROTO.TCP, 0); 32 | defer socket.deinit(); 33 | 34 | try socket.registerTo(notifier); 35 | try socket.connect(address); 36 | 37 | log.info("Connected to: {s}", .{address}); 38 | 39 | var buf: [1024]u8 = undefined; 40 | var n: usize = undefined; 41 | 42 | n = try socket.read(&buf); 43 | if (n == 0) return; 44 | log.info("Got: {s}", .{buf[0..n]}); 45 | 46 | n = try socket.read(&buf); 47 | if (n == 0) return; 48 | log.info("Got: {s}", .{buf[0..n]}); 49 | 50 | _ = try socket.write("Hello world!\n"); 51 | _ = try socket.send("Hello world!\n", 0); 52 | } 53 | -------------------------------------------------------------------------------- /example_tcp_server.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | 4 | const os = std.os; 5 | const mem = std.mem; 6 | const net = std.net; 7 | const log = std.log; 8 | const heap = std.heap; 9 | const atomic = std.atomic; 10 | 11 | pub const ClientQueue = atomic.Queue(*Client); 12 | 13 | pub const Client = struct { 14 | socket: pike.Socket, 15 | address: net.Address, 16 | frame: @Frame(Client.run), 17 | 18 | fn run(self: *Client, server: *Server, notifier: *const pike.Notifier) !void { 19 | var node = ClientQueue.Node{ .data = self }; 20 | 21 | server.clients.put(&node); 22 | 23 | defer if (server.clients.remove(&node)) { 24 | suspend { 25 | self.socket.deinit(); 26 | server.allocator.destroy(self); 27 | } 28 | }; 29 | 30 | try self.socket.registerTo(notifier); 31 | 32 | log.info("New peer {} has connected.", .{self.address}); 33 | defer log.info("Peer {} has disconnected.", .{self.address}); 34 | 35 | var reader = self.socket.reader(); 36 | var writer = self.socket.writer(); 37 | 38 | try writer.writeAll("Hello from the server!\n"); 39 | 40 | var buf: [1024]u8 = undefined; 41 | while (true) { 42 | const num_bytes = try reader.read(&buf); 43 | if (num_bytes == 0) return; 44 | 45 | const message = mem.trim(u8, buf[0..num_bytes], " \t\r\n"); 46 | log.info("Peer {} said: {s}", .{ self.address, message }); 47 | } 48 | } 49 | }; 50 | 51 | pub const Server = struct { 52 | socket: pike.Socket, 53 | clients: ClientQueue, 54 | 55 | allocator: *mem.Allocator, 56 | frame: @Frame(Server.run), 57 | 58 | pub fn init(allocator: *mem.Allocator) !Server { 59 | var socket = try pike.Socket.init(os.AF.INET, os.SOCK.STREAM, os.IPPROTO.TCP, 0); 60 | errdefer socket.deinit(); 61 | 62 | try socket.set(.reuse_address, true); 63 | 64 | return Server{ 65 | .socket = socket, 66 | .clients = ClientQueue.init(), 67 | 68 | .frame = undefined, 69 | .allocator = allocator, 70 | }; 71 | } 72 | 73 | pub fn deinit(self: *Server) void { 74 | self.socket.deinit(); 75 | 76 | await self.frame; 77 | 78 | while (self.clients.get()) |node| { 79 | node.data.socket.writer().writeAll("Server is shutting down! Good bye...\n") catch {}; 80 | node.data.socket.deinit(); 81 | 82 | await node.data.frame catch {}; 83 | self.allocator.destroy(node.data); 84 | } 85 | } 86 | 87 | pub fn start(self: *Server, notifier: *const pike.Notifier, address: net.Address) !void { 88 | try self.socket.bind(address); 89 | try self.socket.listen(128); 90 | try self.socket.registerTo(notifier); 91 | 92 | self.frame = async self.run(notifier); 93 | 94 | log.info("Listening for peers on: {}", .{try self.socket.getBindAddress()}); 95 | } 96 | 97 | fn run(self: *Server, notifier: *const pike.Notifier) callconv(.Async) void { 98 | defer log.debug("TCP server has shut down.", .{}); 99 | 100 | while (true) { 101 | var conn = self.socket.accept() catch |err| switch (err) { 102 | error.SocketNotListening, 103 | error.OperationCancelled, 104 | => return, 105 | else => { 106 | log.err("Server - socket.accept(): {s}", .{@errorName(err)}); 107 | continue; 108 | }, 109 | }; 110 | 111 | const client = self.allocator.create(Client) catch |err| { 112 | log.err("Server - allocator.create(Client): {s}", .{@errorName(err)}); 113 | conn.socket.deinit(); 114 | continue; 115 | }; 116 | 117 | client.socket = conn.socket; 118 | client.address = conn.address; 119 | client.frame = async client.run(self, notifier); 120 | } 121 | } 122 | }; 123 | 124 | pub fn run(notifier: *const pike.Notifier, stopped: *bool) !void { 125 | // Setup allocator. 126 | var gpa: heap.GeneralPurposeAllocator(.{}) = .{}; 127 | defer _ = gpa.deinit(); 128 | 129 | // Setup signal handler. 130 | 131 | var event = try pike.Event.init(); 132 | defer event.deinit(); 133 | 134 | try event.registerTo(notifier); 135 | 136 | var signal = try pike.Signal.init(.{ .interrupt = true }); 137 | defer signal.deinit(); 138 | 139 | defer { 140 | stopped.* = true; 141 | event.post() catch unreachable; 142 | } 143 | 144 | // Setup TCP server. 145 | 146 | var server = try Server.init(&gpa.allocator); 147 | defer server.deinit(); 148 | 149 | // Start the server, and await for an interrupt signal to gracefully shutdown 150 | // the server. 151 | 152 | try server.start(notifier, net.Address.initIp4(.{ 0, 0, 0, 0 }, 0)); 153 | try signal.wait(); 154 | } 155 | 156 | pub fn main() !void { 157 | try pike.init(); 158 | defer pike.deinit(); 159 | 160 | const notifier = try pike.Notifier.init(); 161 | defer notifier.deinit(); 162 | 163 | var stopped = false; 164 | 165 | var frame = async run(¬ifier, &stopped); 166 | 167 | while (!stopped) { 168 | try notifier.poll(1_000_000); 169 | } 170 | 171 | try nosuspend await frame; 172 | 173 | log.debug("Successfully shut down.", .{}); 174 | } 175 | -------------------------------------------------------------------------------- /example_udp_benchmark.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | 4 | const os = std.os; 5 | const net = std.net; 6 | 7 | pub fn main() !void { 8 | try pike.init(); 9 | defer pike.deinit(); 10 | 11 | const notifier = try pike.Notifier.init(); 12 | defer notifier.deinit(); 13 | 14 | var stopped = false; 15 | 16 | var server_frame = async runBenchmarkServer(¬ifier, &stopped); 17 | var client_frame = async runBenchmarkClient(¬ifier, &stopped); 18 | 19 | while (!stopped) { 20 | try notifier.poll(10_000); 21 | } 22 | 23 | try nosuspend await server_frame; 24 | try nosuspend await client_frame; 25 | } 26 | 27 | fn runBenchmarkServer(notifier: *const pike.Notifier, stopped: *bool) !void { 28 | defer stopped.* = true; 29 | 30 | const address = try net.Address.parseIp("127.0.0.1", 9000); 31 | 32 | var socket = try pike.Socket.init(os.AF.INET, os.SOCK.DGRAM, 0, 0); 33 | defer socket.deinit(); 34 | 35 | try socket.registerTo(notifier); 36 | 37 | try socket.set(.reuse_address, true); 38 | try socket.bind(address); 39 | 40 | var buf: [1400]u8 = undefined; 41 | while (true) { 42 | _ = try socket.recvFrom(&buf, 0, null); 43 | } 44 | } 45 | 46 | fn runBenchmarkClient(notifier: *const pike.Notifier, stopped: *bool) !void { 47 | defer stopped.* = true; 48 | 49 | const address = try net.Address.parseIp("127.0.0.1", 9000); 50 | 51 | var socket = try pike.Socket.init(os.AF.INET, os.SOCK.DGRAM, 0, 0); 52 | defer socket.deinit(); 53 | 54 | try socket.registerTo(notifier); 55 | 56 | var buf: [1400]u8 = undefined; 57 | while (true) { 58 | _ = try socket.sendTo(&buf, 0, address); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /notifier_epoll.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const posix = @import("os/posix.zig"); 4 | 5 | const os = std.os; 6 | const net = std.net; 7 | const mem = std.mem; 8 | 9 | pub inline fn init() !void {} 10 | pub inline fn deinit() void {} 11 | 12 | pub const Handle = struct { 13 | inner: os.fd_t, 14 | wake_fn: fn (self: *Handle, batch: *pike.Batch, opts: pike.WakeOptions) void, 15 | 16 | pub inline fn wake(self: *Handle, batch: *pike.Batch, opts: pike.WakeOptions) void { 17 | self.wake_fn(self, batch, opts); 18 | } 19 | }; 20 | 21 | pub const Notifier = struct { 22 | const Self = @This(); 23 | 24 | handle: i32, 25 | 26 | pub fn init() !Self { 27 | const handle = try os.epoll_create1(os.linux.EPOLL.CLOEXEC); 28 | errdefer os.close(handle); 29 | 30 | return Self{ .handle = handle }; 31 | } 32 | 33 | pub fn deinit(self: *const Self) void { 34 | os.close(self.handle); 35 | } 36 | 37 | pub fn register(self: *const Self, handle: *const Handle, comptime opts: pike.PollOptions) !void { 38 | if (handle.inner == -1) return; 39 | 40 | var events: u32 = os.linux.EPOLL.ET | os.linux.EPOLL.ERR | os.linux.EPOLL.RDHUP; 41 | if (opts.read) events |= os.linux.EPOLL.IN; 42 | if (opts.write) events |= os.linux.EPOLL.OUT; 43 | 44 | _ = os.linux.epoll_ctl(self.handle, os.linux.EPOLL.CTL_ADD, handle.inner, &os.linux.epoll_event{ 45 | .events = events, 46 | .data = .{ .ptr = @ptrToInt(handle) }, 47 | }); 48 | } 49 | 50 | pub fn poll(self: *const Self, timeout: i32) !void { 51 | var events: [128]os.linux.epoll_event = undefined; 52 | 53 | var batch: pike.Batch = .{}; 54 | defer pike.dispatch(batch, .{}); 55 | 56 | const num_events = os.epoll_wait(self.handle, &events, timeout); 57 | for (events[0..num_events]) |e| { 58 | if (e.data.ptr == 0) continue; 59 | 60 | const handle = @intToPtr(*Handle, e.data.ptr); 61 | 62 | const shutdown = e.events & (os.linux.EPOLL.ERR | os.linux.EPOLL.RDHUP) != 0; 63 | const read_ready = e.events & os.linux.EPOLL.IN != 0; 64 | const write_ready = e.events & os.linux.EPOLL.OUT != 0; 65 | 66 | handle.wake(&batch, .{ 67 | .shutdown = shutdown, 68 | .read_ready = read_ready, 69 | .write_ready = write_ready, 70 | }); 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /notifier_iocp.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const windows = @import("os/windows.zig"); 4 | const ws2_32 = @import("os/windows/ws2_32.zig"); 5 | 6 | const os = std.os; 7 | const net = std.net; 8 | const math = std.math; 9 | 10 | pub inline fn init() !void { 11 | _ = try windows.WSAStartup(2, 2); 12 | } 13 | 14 | pub inline fn deinit() void { 15 | windows.WSACleanup() catch {}; 16 | } 17 | 18 | pub const Handle = struct { 19 | inner: os.fd_t, 20 | }; 21 | 22 | pub const Overlapped = struct { 23 | inner: windows.OVERLAPPED, 24 | task: pike.Task, 25 | 26 | pub fn init(task: pike.Task) Overlapped { 27 | return .{ 28 | .inner = .{ 29 | .Internal = 0, 30 | .InternalHigh = 0, 31 | .Offset = 0, 32 | .OffsetHigh = 0, 33 | .hEvent = null, 34 | }, 35 | .task = task, 36 | }; 37 | } 38 | }; 39 | 40 | pub const Notifier = struct { 41 | const Self = @This(); 42 | 43 | handle: os.fd_t, 44 | 45 | pub fn init() !Self { 46 | const handle = try windows.CreateIoCompletionPort( 47 | windows.INVALID_HANDLE_VALUE, 48 | null, 49 | undefined, 50 | math.maxInt(windows.DWORD), 51 | ); 52 | errdefer windows.CloseHandle(handle); 53 | 54 | return Self{ .handle = handle }; 55 | } 56 | 57 | pub fn deinit(self: *const Self) void { 58 | windows.CloseHandle(self.handle); 59 | } 60 | 61 | pub fn register(self: *const Self, handle: *const Handle, comptime _: pike.PollOptions) !void { 62 | if (handle.inner == windows.INVALID_HANDLE_VALUE) return; 63 | 64 | _ = try windows.CreateIoCompletionPort(handle.inner, self.handle, 0, 0); 65 | 66 | try windows.SetFileCompletionNotificationModes( 67 | handle.inner, 68 | windows.FILE_SKIP_SET_EVENT_ON_HANDLE | windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS, 69 | ); 70 | } 71 | 72 | pub fn poll(self: *const Self, timeout: i32) !void { 73 | var events: [128]windows.OVERLAPPED_ENTRY = undefined; 74 | 75 | var batch: pike.Batch = .{}; 76 | defer pike.dispatch(batch, .{}); 77 | 78 | const num_events = windows.GetQueuedCompletionStatusEx( 79 | self.handle, 80 | &events, 81 | @intCast(windows.DWORD, timeout), 82 | false, 83 | ) catch |err| switch (err) { 84 | error.Timeout => return, 85 | else => return err, 86 | }; 87 | 88 | for (events[0..num_events]) |event| { 89 | const overlapped = @fieldParentPtr(Overlapped, "inner", event.lpOverlapped orelse continue); 90 | batch.push(&overlapped.task); 91 | } 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /notifier_kqueue.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const posix = @import("os/posix.zig"); 4 | 5 | const os = std.os; 6 | const net = std.net; 7 | const mem = std.mem; 8 | const time = std.time; 9 | 10 | pub inline fn init() !void {} 11 | pub inline fn deinit() void {} 12 | 13 | pub const Handle = struct { 14 | inner: os.fd_t, 15 | wake_fn: fn (self: *Handle, batch: *pike.Batch, opts: pike.WakeOptions) void, 16 | 17 | pub inline fn wake(self: *Handle, batch: *pike.Batch, opts: pike.WakeOptions) void { 18 | self.wake_fn(self, batch, opts); 19 | } 20 | }; 21 | 22 | pub const Notifier = struct { 23 | const Self = @This(); 24 | 25 | handle: os.fd_t, 26 | 27 | pub fn init() !Self { 28 | const handle = try os.kqueue(); 29 | errdefer os.close(handle); 30 | 31 | return Self{ .handle = handle }; 32 | } 33 | 34 | pub fn deinit(self: *const Self) void { 35 | os.close(self.handle); 36 | } 37 | 38 | pub fn register(self: *const Self, handle: *const Handle, comptime opts: pike.PollOptions) !void { 39 | if (handle.inner == -1) return; 40 | 41 | var changelist = [_]os.Kevent{ 42 | .{ 43 | .ident = undefined, 44 | .filter = undefined, 45 | .flags = os.EV_ADD | os.EV_CLEAR, 46 | .fflags = 0, 47 | .data = 0, 48 | .udata = undefined, 49 | }, 50 | } ** 2; 51 | 52 | comptime var changelist_len = 0; 53 | 54 | comptime { 55 | if (opts.read) { 56 | changelist[changelist_len].filter = os.EVFILT_READ; 57 | changelist_len += 1; 58 | } 59 | 60 | if (opts.write) { 61 | changelist[changelist_len].filter = os.EVFILT_WRITE; 62 | changelist_len += 1; 63 | } 64 | } 65 | 66 | for (changelist[0..changelist_len]) |*event| { 67 | event.ident = @intCast(usize, handle.inner); 68 | event.udata = @ptrToInt(handle); 69 | } 70 | 71 | _ = try os.kevent(self.handle, changelist[0..changelist_len], &[0]os.Kevent{}, null); 72 | } 73 | 74 | pub fn poll(self: *const Self, timeout: i32) !void { 75 | var events: [128]os.Kevent = undefined; 76 | 77 | var batch: pike.Batch = .{}; 78 | defer pike.dispatch(batch, .{}); 79 | 80 | const timeout_spec = os.timespec{ 81 | .tv_sec = @divTrunc(timeout, time.ms_per_s), 82 | .tv_nsec = @rem(timeout, time.ms_per_s) * time.ns_per_ms, 83 | }; 84 | 85 | const num_events = try os.kevent(self.handle, &[0]os.Kevent{}, events[0..], &timeout_spec); 86 | 87 | for (events[0..num_events]) |e| { 88 | const handle = @intToPtr(*Handle, e.udata); 89 | 90 | const notify = e.filter == os.EVFILT_USER; 91 | const shutdown = e.flags & (os.EV_ERROR | os.EV_EOF) != 0; 92 | const read_ready = e.filter == os.EVFILT_READ; 93 | const write_ready = e.filter == os.EVFILT_WRITE; 94 | 95 | handle.wake(&batch, .{ 96 | .notify = notify, 97 | .shutdown = shutdown, 98 | .read_ready = read_ready, 99 | .write_ready = write_ready, 100 | }); 101 | } 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /os/posix.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | 4 | const os = std.os; 5 | const math = std.math; 6 | 7 | pub usingnamespace if (!@hasDecl(std.os, "SHUT_RD") and !@hasDecl(std.os, "SHUT_WR") and !@hasDecl(std.os, "SHUT_RDWR")) 8 | struct { 9 | pub const SHUT_RD = 0; 10 | pub const SHUT_WR = 1; 11 | pub const SHUT_RDWR = 2; 12 | } 13 | else 14 | struct {}; 15 | 16 | pub const LINGER = extern struct { 17 | l_onoff: c_int, // Whether or not a socket should remain open to send queued dataa after closesocket() is called. 18 | l_linger: c_int, // Number of seconds on how long a socket should remain open after closesocket() is called. 19 | }; 20 | 21 | const funcs = struct { 22 | pub extern "c" fn shutdown(sock: os.socket_t, how: c_int) c_int; 23 | }; 24 | 25 | pub fn shutdown_(sock: os.socket_t, how: c_int) !void { 26 | const rc = if (builtin.link_libc) funcs.shutdown(sock, how) else os.system.shutdown(sock, @intCast(i32, how)); 27 | return switch (os.errno(rc)) { 28 | .SUCCESS => {}, 29 | .BADF => error.BadFileDescriptor, 30 | .INVAL => error.BadArgument, 31 | .NOTCONN => error.SocketNotConnected, 32 | .NOTSOCK => error.NotASocket, 33 | else => |err| os.unexpectedErrno(err), 34 | }; 35 | } 36 | 37 | pub fn sendto_( 38 | /// The file descriptor of the sending socket. 39 | sockfd: os.socket_t, 40 | /// Message to send. 41 | buf: []const u8, 42 | flags: u32, 43 | dest_addr: ?*const os.sockaddr, 44 | addrlen: os.socklen_t, 45 | ) !usize { 46 | while (true) { 47 | const rc = os.system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen); 48 | switch (os.errno(rc)) { 49 | .SUCCESS => return @intCast(usize, rc), 50 | .ACCES => return error.AccessDenied, 51 | .AGAIN, .PROTOTYPE => return error.WouldBlock, 52 | .ALREADY => return error.FastOpenAlreadyInProgress, 53 | .BADF => unreachable, // always a race condition 54 | .CONNRESET => return error.ConnectionResetByPeer, 55 | .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. 56 | .FAULT => unreachable, // An invalid user space address was specified for an argument. 57 | .INTR => continue, 58 | .INVAL => unreachable, // Invalid argument passed. 59 | .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified 60 | .MSGSIZE => return error.MessageTooBig, 61 | .NOBUFS => return error.SystemResources, 62 | .NOMEM => return error.SystemResources, 63 | .NOTCONN => unreachable, // The socket is not connected, and no target has been given. 64 | .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. 65 | .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. 66 | .PIPE => return error.BrokenPipe, 67 | else => |err| return os.unexpectedErrno(err), 68 | } 69 | } 70 | } 71 | 72 | pub fn read_(fd: os.fd_t, buf: []u8) !usize { 73 | const max_count = switch (builtin.os.tag) { 74 | .linux => 0x7ffff000, 75 | .macos, .ios, .watchos, .tvos => math.maxInt(i32), 76 | else => math.maxInt(isize), 77 | }; 78 | const adjusted_len = math.min(max_count, buf.len); 79 | 80 | while (true) { 81 | const rc = os.system.read(fd, buf.ptr, adjusted_len); 82 | switch (os.errno(rc)) { 83 | .SUCCESS => return @intCast(usize, rc), 84 | .INTR => continue, 85 | .INVAL => unreachable, 86 | .FAULT => unreachable, 87 | .AGAIN => return error.WouldBlock, 88 | .BADF => return error.NotOpenForReading, // Can be a race condition. 89 | .IO => return error.InputOutput, 90 | .ISDIR => return error.IsDir, 91 | .NOBUFS => return error.SystemResources, 92 | .NOMEM => return error.SystemResources, 93 | .NOTCONN => return error.SocketNotConnected, 94 | .CONNRESET => return error.ConnectionResetByPeer, 95 | .TIMEDOUT => return error.ConnectionTimedOut, 96 | else => |err| return os.unexpectedErrno(err), 97 | } 98 | } 99 | return os.index; 100 | } 101 | 102 | pub fn connect_(sock: os.socket_t, sock_addr: *const os.sockaddr, len: os.socklen_t) !void { 103 | while (true) { 104 | return switch (os.errno(os.system.connect(sock, sock_addr, len))) { 105 | .SUCCESS => {}, 106 | .ACCES => error.PermissionDenied, 107 | .PERM => error.PermissionDenied, 108 | .ADDRINUSE => error.AddressInUse, 109 | .ADDRNOTAVAIL => error.AddressNotAvailable, 110 | .AFNOSUPPORT => error.AddressFamilyNotSupported, 111 | .AGAIN, .INPROGRESS => error.WouldBlock, 112 | .ALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed. 113 | .BADF => unreachable, // sockfd is not a valid open file descriptor. 114 | .CONNREFUSED => error.ConnectionRefused, 115 | .FAULT => unreachable, // The socket structure address is outside the user's address space. 116 | .INTR => continue, 117 | .ISCONN => error.AlreadyConnected, // The socket is already connected. 118 | .NETUNREACH => error.NetworkUnreachable, 119 | .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. 120 | .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. 121 | .TIMEDOUT => error.ConnectionTimedOut, 122 | .NOENT => error.FileNotFound, // Returned when socket is AF_UNIX and the given path does not exist. 123 | else => |err| os.unexpectedErrno(err), 124 | }; 125 | } 126 | } 127 | 128 | pub fn accept_(sock: os.socket_t, addr: ?*os.sockaddr, addr_size: ?*os.socklen_t, flags: u32) !os.socket_t { 129 | const have_accept4 = comptime !(builtin.target.isDarwin() or builtin.os.tag == .windows); 130 | 131 | const accepted_sock = while (true) { 132 | const rc = if (have_accept4) 133 | os.system.accept4(sock, addr, addr_size, flags) 134 | else if (builtin.os.tag == .windows) 135 | os.windows.accept(sock, addr, addr_size) 136 | else 137 | os.system.accept(sock, addr, addr_size); 138 | 139 | if (builtin.os.tag == .windows) { 140 | if (rc == os.windows.ws2_32.INVALID_SOCKET) { 141 | switch (os.windows.ws2_32.WSAGetLastError()) { 142 | .WSANOTINITIALISED => unreachable, // not initialized WSA 143 | .WSAECONNRESET => return error.ConnectionResetByPeer, 144 | .WSAEFAULT => unreachable, 145 | .WSAEINVAL => return error.SocketNotListening, 146 | .WSAEMFILE => return error.ProcessFdQuotaExceeded, 147 | .WSAENETDOWN => return error.NetworkSubsystemFailed, 148 | .WSAENOBUFS => return error.FileDescriptorNotASocket, 149 | .WSAEOPNOTSUPP => return error.OperationNotSupported, 150 | .WSAEWOULDBLOCK => return error.WouldBlock, 151 | else => |err| return os.windows.unexpectedWSAError(err), 152 | } 153 | } else { 154 | break rc; 155 | } 156 | } else { 157 | switch (os.errno(rc)) { 158 | .SUCCESS => { 159 | break @intCast(os.socket_t, rc); 160 | }, 161 | .INTR => continue, 162 | .AGAIN => return error.WouldBlock, 163 | .CONNABORTED => return error.ConnectionAborted, 164 | .FAULT => unreachable, 165 | .INVAL, .BADF => return error.SocketNotListening, 166 | .NOTSOCK => return error.NotASocket, 167 | .MFILE => return error.ProcessFdQuotaExceeded, 168 | .NFILE => return error.SystemFdQuotaExceeded, 169 | .NOBUFS => return error.SystemResources, 170 | .NOMEM => return error.SystemResources, 171 | .OPNOTSUPP => unreachable, 172 | .PROTO => return error.ProtocolFailure, 173 | .PERM => return error.BlockedByFirewall, 174 | else => |err| return os.unexpectedErrno(err), 175 | } 176 | } 177 | } else unreachable; 178 | 179 | if (!have_accept4) { 180 | try setSockFlags(accepted_sock, flags); 181 | } 182 | return accepted_sock; 183 | } 184 | 185 | fn setSockFlags(sock: os.socket_t, flags: u32) !void { 186 | if ((flags & os.SOCK.CLOEXEC) != 0) { 187 | if (builtin.os.tag == .windows) { 188 | // TODO: Find out if this is supported for sockets 189 | } else { 190 | var fd_flags = os.fcntl(sock, os.F_GETFD, 0) catch |err| switch (err) { 191 | error.FileBusy => unreachable, 192 | error.Locked => unreachable, 193 | else => |e| return e, 194 | }; 195 | fd_flags |= os.FD_CLOEXEC; 196 | _ = os.fcntl(sock, os.F_SETFD, fd_flags) catch |err| switch (err) { 197 | error.FileBusy => unreachable, 198 | error.Locked => unreachable, 199 | else => |e| return e, 200 | }; 201 | } 202 | } 203 | if ((flags & os.SOCK.NONBLOCK) != 0) { 204 | if (builtin.os.tag == .windows) { 205 | var mode: c_ulong = 1; 206 | if (os.windows.ws2_32.ioctlsocket(sock, os.windows.ws2_32.FIONBIO, &mode) == os.windows.ws2_32.SOCKET_ERROR) { 207 | switch (os.windows.ws2_32.WSAGetLastError()) { 208 | .WSANOTINITIALISED => unreachable, 209 | .WSAENETDOWN => return error.NetworkSubsystemFailed, 210 | .WSAENOTSOCK => return error.FileDescriptorNotASocket, 211 | // TODO: handle more errors 212 | else => |err| return os.windows.unexpectedWSAError(err), 213 | } 214 | } 215 | } else { 216 | var fl_flags = os.fcntl(sock, os.F_GETFL, 0) catch |err| switch (err) { 217 | error.FileBusy => unreachable, 218 | error.Locked => unreachable, 219 | else => |e| return e, 220 | }; 221 | fl_flags |= os.O_NONBLOCK; 222 | _ = os.fcntl(sock, os.F_SETFL, fl_flags) catch |err| switch (err) { 223 | error.FileBusy => unreachable, 224 | error.Locked => unreachable, 225 | else => |e| return e, 226 | }; 227 | } 228 | } 229 | } 230 | 231 | pub fn getsockopt(comptime T: type, handle: os.socket_t, level: u32, opt: u32) !T { 232 | var val: T = undefined; 233 | var val_len: u32 = @sizeOf(T); 234 | 235 | const rc = os.system.getsockopt(handle, level, opt, @ptrCast([*]u8, &val), &val_len); 236 | return switch (std.os.linux.getErrno(rc)) { 237 | .SUCCESS => val, 238 | .BADF => error.BadFileDescriptor, // The argument sockfd is not a valid file descriptor. 239 | .FAULT => error.InvalidParameter, // The address pointed to by optval or optlen is not in a valid part of the process address space. 240 | .NOPROTOOPT => error.UnsupportedOption, // The option is unknown at the level indicated. 241 | .NOTSOCK => error.NotASocket, // The file descriptor sockfd does not refer to a socket. 242 | else => |err| os.unexpectedErrno(err), 243 | }; 244 | } 245 | 246 | pub fn sigprocmask(flags: anytype, noalias set: ?*const os.sigset_t, noalias oldset: ?*os.sigset_t) !void { 247 | const rc = os.system.sigprocmask(flags, set, oldset); 248 | return switch (os.errno(rc)) { 249 | .SUCCESS => {}, 250 | .FAULT => error.InvalidParameter, 251 | .INVAL => error.BadSignalSet, 252 | else => |err| os.unexpectedErrno(err), 253 | }; 254 | } 255 | -------------------------------------------------------------------------------- /os/windows.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const mem = std.mem; 4 | const math = std.math; 5 | 6 | const os = std.os; 7 | const windows = os.windows; 8 | const ws2_32 = windows.ws2_32; 9 | 10 | pub const FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 0x1; 11 | pub const FILE_SKIP_SET_EVENT_ON_HANDLE = 0x2; 12 | 13 | pub const CTRL_C_EVENT: windows.DWORD = 0; 14 | pub const CTRL_BREAK_EVENT: windows.DWORD = 1; 15 | pub const CTRL_CLOSE_EVENT: windows.DWORD = 2; 16 | pub const CTRL_LOGOFF_EVENT: windows.DWORD = 5; 17 | pub const CTRL_SHUTDOWN_EVENT: windows.DWORD = 6; 18 | 19 | pub const HANDLER_ROUTINE = fn (dwCtrlType: windows.DWORD) callconv(.C) windows.BOOL; 20 | 21 | pub const OVERLAPPED_ENTRY = extern struct { 22 | lpCompletionKey: windows.ULONG_PTR, 23 | lpOverlapped: ?windows.LPOVERLAPPED, 24 | Internal: windows.ULONG_PTR, 25 | dwNumberOfBytesTransferred: windows.DWORD, 26 | }; 27 | 28 | pub fn loadWinsockExtensionFunction(comptime T: type, sock: ws2_32.SOCKET, guid: windows.GUID) !T { 29 | var function: T = undefined; 30 | var num_bytes: windows.DWORD = undefined; 31 | 32 | const rc = ws2_32.WSAIoctl( 33 | sock, 34 | @import("windows/ws2_32.zig").SIO_GET_EXTENSION_FUNCTION_POINTER, 35 | @ptrCast(*const c_void, &guid), 36 | @sizeOf(windows.GUID), 37 | &function, 38 | @sizeOf(T), 39 | &num_bytes, 40 | null, 41 | null, 42 | ); 43 | 44 | if (rc == ws2_32.SOCKET_ERROR) { 45 | return switch (ws2_32.WSAGetLastError()) { 46 | .WSAEOPNOTSUPP => error.OperationNotSupported, 47 | .WSAENOTSOCK => error.FileDescriptorNotASocket, 48 | else => |err| windows.unexpectedWSAError(err), 49 | }; 50 | } 51 | 52 | if (num_bytes != @sizeOf(T)) { 53 | return error.ShortRead; 54 | } 55 | 56 | return function; 57 | } 58 | 59 | pub fn SetConsoleCtrlHandler(handler_routine: ?HANDLER_ROUTINE, add: bool) !void { 60 | const success = @import("windows/kernel32.zig").SetConsoleCtrlHandler( 61 | handler_routine, 62 | if (add) windows.TRUE else windows.FALSE, 63 | ); 64 | 65 | if (success == windows.FALSE) { 66 | return switch (windows.kernel32.GetLastError()) { 67 | else => |err| windows.unexpectedError(err), 68 | }; 69 | } 70 | } 71 | 72 | pub fn SetFileCompletionNotificationModes(handle: windows.HANDLE, flags: windows.UCHAR) !void { 73 | const success = @import("windows/kernel32.zig").SetFileCompletionNotificationModes(handle, flags); 74 | 75 | if (success == windows.FALSE) { 76 | return windows.unexpectedError(windows.kernel32.GetLastError()); 77 | } 78 | } 79 | 80 | pub const GetQueuedCompletionStatusError = error{ 81 | Aborted, 82 | Cancelled, 83 | EOF, 84 | Timeout, 85 | } || os.UnexpectedError; 86 | 87 | pub fn GetQueuedCompletionStatusEx( 88 | completion_port: windows.HANDLE, 89 | completion_port_entries: []windows.OVERLAPPED_ENTRY, 90 | timeout_ms: ?windows.DWORD, 91 | alertable: bool, 92 | ) GetQueuedCompletionStatusError!u32 { 93 | var num_entries_removed: u32 = 0; 94 | 95 | const success = @import("windows/kernel32.zig").GetQueuedCompletionStatusEx( 96 | completion_port, 97 | completion_port_entries.ptr, 98 | @intCast(windows.ULONG, completion_port_entries.len), 99 | &num_entries_removed, 100 | timeout_ms orelse windows.INFINITE, 101 | @boolToInt(alertable), 102 | ); 103 | 104 | if (success == windows.FALSE) { 105 | return switch (windows.kernel32.GetLastError()) { 106 | .ABANDONED_WAIT_0 => error.Aborted, 107 | .OPERATION_ABORTED => error.Cancelled, 108 | .HANDLE_EOF => error.EOF, 109 | .IMEOUT => error.Timeout, 110 | else => |err| windows.unexpectedError(err), 111 | }; 112 | } 113 | 114 | return num_entries_removed; 115 | } 116 | 117 | pub fn pollBaseSocket(socket: ws2_32.SOCKET, ioctl_code: windows.DWORD) !ws2_32.SOCKET { 118 | var base_socket: ws2_32.SOCKET = undefined; 119 | var num_bytes: windows.DWORD = 0; 120 | 121 | const rc = ws2_32.WSAIoctl( 122 | socket, 123 | ioctl_code, 124 | null, 125 | 0, 126 | @ptrCast([*]u8, &base_socket), 127 | @sizeOf(ws2_32.SOCKET), 128 | &num_bytes, 129 | null, 130 | null, 131 | ); 132 | 133 | if (rc == ws2_32.SOCKET_ERROR) { 134 | return switch (ws2_32.WSAGetLastError()) { 135 | .WSAEOPNOTSUPP => error.OperationNotSupported, 136 | else => |err| windows.unexpectedWSAError(err), 137 | }; 138 | } 139 | 140 | if (num_bytes != @sizeOf(ws2_32.SOCKET)) { 141 | return error.ShortRead; 142 | } 143 | 144 | return base_socket; 145 | } 146 | 147 | pub fn getBaseSocket(socket: ws2_32.SOCKET) !ws2_32.SOCKET { 148 | const err = if (pollBaseSocket(socket, @import("windows/ws2_32.zig").SIO_BASE_HANDLE)) |base_socket| return base_socket else |err| err; 149 | 150 | inline for (.{ 151 | @import("windows/ws2_32.zig").SIO_BSP_HANDLE_SELECT, 152 | @import("windows/ws2_32.zig").SIO_BSP_HANDLE_POLL, 153 | @import("windows/ws2_32.zig").SIO_BSP_HANDLE, 154 | }) |ioctl_code| { 155 | if (pollBaseSocket(socket, ioctl_code)) |base_socket| return base_socket else |_| {} 156 | } 157 | 158 | return err; 159 | } 160 | 161 | pub fn GetAcceptExSockaddrs(socket: ws2_32.SOCKET, buf: []const u8, local_addr_len: u32, remote_addr_len: u32, local_addr: **ws2_32.sockaddr, remote_addr: **ws2_32.sockaddr) !void { 162 | const func = try loadWinsockExtensionFunction(@import("windows/ws2_32.zig").GetAcceptExSockaddrs, socket, @import("windows/ws2_32.zig").WSAID_GETACCEPTEXSOCKADDRS); 163 | 164 | var local_addr_ptr_len = @as(c_int, @sizeOf(ws2_32.sockaddr)); 165 | var remote_addr_ptr_len = @as(c_int, @sizeOf(ws2_32.sockaddr)); 166 | 167 | func( 168 | buf.ptr, 169 | 0, 170 | local_addr_len, 171 | remote_addr_len, 172 | local_addr, 173 | &local_addr_ptr_len, 174 | remote_addr, 175 | &remote_addr_ptr_len, 176 | ); 177 | } 178 | 179 | pub fn AcceptEx(listening_socket: ws2_32.SOCKET, accepted_socket: ws2_32.SOCKET, buf: []u8, local_addr_len: u32, remote_addr_len: u32, num_bytes: *windows.DWORD, overlapped: *windows.OVERLAPPED) !void { 180 | const func = try loadWinsockExtensionFunction(@import("windows/ws2_32.zig").AcceptEx, listening_socket, @import("windows/ws2_32.zig").WSAID_ACCEPTEX); 181 | 182 | const success = func( 183 | listening_socket, 184 | accepted_socket, 185 | buf.ptr, 186 | 0, 187 | local_addr_len, 188 | remote_addr_len, 189 | num_bytes, 190 | overlapped, 191 | ); 192 | 193 | if (success == windows.FALSE) { 194 | return switch (ws2_32.WSAGetLastError()) { 195 | .WSAECONNRESET => error.ConnectionResetByPeer, 196 | .WSAEFAULT => error.BadAddress, 197 | .WSAEINVAL => error.SocketNotListening, 198 | .WSAEMFILE => error.ProcessFdQuotaExceeded, 199 | .WSAENETDOWN => error.NetworkSubsystemFailed, 200 | .WSAENOBUFS => error.FileDescriptorNotASocket, 201 | .WSAEOPNOTSUPP => error.OperationNotSupported, 202 | .WSA_IO_PENDING, .WSAEWOULDBLOCK => error.WouldBlock, 203 | else => |err| windows.unexpectedWSAError(err), 204 | }; 205 | } 206 | } 207 | 208 | pub fn ConnectEx(sock: ws2_32.SOCKET, sock_addr: *const ws2_32.sockaddr, sock_len: ws2_32.socklen_t, overlapped: *windows.OVERLAPPED) !void { 209 | const func = try loadWinsockExtensionFunction(@import("windows/ws2_32.zig").ConnectEx, sock, @import("windows/ws2_32.zig").WSAID_CONNECTEX); 210 | 211 | const success = func(sock, sock_addr, @intCast(c_int, sock_len), null, 0, null, overlapped); 212 | if (success == windows.FALSE) { 213 | return switch (ws2_32.WSAGetLastError()) { 214 | .WSAEADDRINUSE => error.AddressInUse, 215 | .WSAEADDRNOTAVAIL => error.AddressNotAvailable, 216 | .WSAECONNREFUSED => error.ConnectionRefused, 217 | .WSAETIMEDOUT => error.ConnectionTimedOut, 218 | .WSAEFAULT => error.BadAddress, 219 | .WSAEINVAL => error.NotYetBound, 220 | .WSAEISCONN => error.AlreadyConnected, 221 | .WSAENOTSOCK => error.NotASocket, 222 | .WSAEACCES => error.BroadcastNotEnabled, 223 | .WSAENOBUFS => error.SystemResources, 224 | .WSAEAFNOSUPPORT => error.AddressFamilyNotSupported, 225 | .WSA_IO_PENDING, .WSAEINPROGRESS, .WSAEWOULDBLOCK => error.WouldBlock, 226 | .WSAEHOSTUNREACH, .WSAENETUNREACH => error.NetworkUnreachable, 227 | else => |err| windows.unexpectedWSAError(err), 228 | }; 229 | } 230 | } 231 | 232 | pub fn bind_(sock: ws2_32.SOCKET, sock_addr: *const ws2_32.sockaddr, sock_len: ws2_32.socklen_t) !void { 233 | const rc = ws2_32.bind(sock, sock_addr, @intCast(c_int, sock_len)); 234 | if (rc == ws2_32.SOCKET_ERROR) { 235 | return switch (ws2_32.WSAGetLastError()) { 236 | .WSAENETDOWN => error.NetworkSubsystemFailed, 237 | .WSAEACCES => error.AccessDenied, 238 | .WSAEADDRINUSE => error.AddressInUse, 239 | .WSAEADDRNOTAVAIL => error.AddressNotAvailable, 240 | .WSAEFAULT => error.BadAddress, 241 | .WSAEINPROGRESS => error.WouldBlock, 242 | .WSAEINVAL => error.AlreadyBound, 243 | .WSAENOBUFS => error.NoEphemeralPortsAvailable, 244 | .WSAENOTSOCK => error.NotASocket, 245 | else => |err| windows.unexpectedWSAError(err), 246 | }; 247 | } 248 | } 249 | 250 | pub fn listen_(sock: ws2_32.SOCKET, backlog: usize) !void { 251 | const rc = ws2_32.listen(sock, @intCast(c_int, backlog)); 252 | if (rc == ws2_32.SOCKET_ERROR) { 253 | return switch (ws2_32.WSAGetLastError()) { 254 | .WSAENETDOWN => error.NetworkSubsystemFailed, 255 | .WSAEADDRINUSE => error.AddressInUse, 256 | .WSAEISCONN => error.AlreadyConnected, 257 | .WSAEINVAL => error.SocketNotBound, 258 | .WSAEMFILE, .WSAENOBUFS => error.SystemResources, 259 | .WSAENOTSOCK => error.FileDescriptorNotASocket, 260 | .WSAEOPNOTSUPP => error.OperationNotSupported, 261 | .WSAEINPROGRESS => error.WouldBlock, 262 | else => |err| windows.unexpectedWSAError(err), 263 | }; 264 | } 265 | } 266 | 267 | pub fn connect(sock: ws2_32.SOCKET, sock_addr: *const ws2_32.sockaddr, len: ws2_32.socklen_t) !void { 268 | const rc = ws2_32.connect(sock, sock_addr, @intCast(i32, len)); 269 | if (rc == ws2_32.SOCKET_ERROR) { 270 | return switch (ws2_32.WSAGetLastError()) { 271 | .WSAEADDRINUSE => error.AddressInUse, 272 | .WSAEADDRNOTAVAIL => error.AddressNotAvailable, 273 | .WSAECONNREFUSED => error.ConnectionRefused, 274 | .WSAETIMEDOUT => error.ConnectionTimedOut, 275 | .WSAEFAULT => error.BadAddress, 276 | .WSAEINVAL => error.ListeningSocket, 277 | .WSAEISCONN => error.AlreadyConnected, 278 | .WSAENOTSOCK => error.NotASocket, 279 | .WSAEACCES => error.BroadcastNotEnabled, 280 | .WSAENOBUFS => error.SystemResources, 281 | .WSAEAFNOSUPPORT => error.AddressFamilyNotSupported, 282 | .WSAEINPROGRESS, .WSAEWOULDBLOCK => error.WouldBlock, 283 | .WSAEHOSTUNREACH, .WSAENETUNREACH => error.NetworkUnreachable, 284 | else => |err| windows.unexpectedWSAError(err), 285 | }; 286 | } 287 | } 288 | 289 | pub fn recv(sock: ws2_32.SOCKET, buf: []u8) !usize { 290 | const rc = @import("windows/ws2_32.zig").recv(sock, buf.ptr, @intCast(c_int, buf.len), 0); 291 | if (rc == ws2_32.SOCKET_ERROR) { 292 | return switch (ws2_32.WSAGetLastError()) { 293 | .WSANOTINITIALISED => error.WinsockNotInitialized, 294 | .WSAENETDOWN => error.NetworkSubsystemFailed, 295 | .WSAEFAULT => error.BadBuffer, 296 | .WSAENOTCONN => error.SocketNotConnected, 297 | .WSAEINTR => error.Cancelled, 298 | .WSAEINPROGRESS, .WSAEWOULDBLOCK => error.WouldBlock, 299 | .WSAENETRESET => error.NetworkReset, 300 | .WSAENOTSOCK => error.NotASocket, 301 | .WSAEOPNOTSUPP => error.FlagNotSupported, 302 | .WSAESHUTDOWN => error.EndOfFile, 303 | .WSAEMSGSIZE => error.MessageTooLarge, 304 | .WSAEINVAL => error.SocketNotBound, 305 | .WSAECONNABORTED => error.ConnectionAborted, 306 | .WSAETIMEDOUT => error.Timeout, 307 | .WSAECONNRESET => error.Refused, 308 | else => |err| windows.unexpectedWSAError(err), 309 | }; 310 | } 311 | 312 | return @intCast(usize, rc); 313 | } 314 | 315 | pub fn getsockopt(comptime T: type, handle: ws2_32.SOCKET, level: c_int, opt: c_int) !T { 316 | var val: T = undefined; 317 | var val_len: c_int = @sizeOf(T); 318 | 319 | const result = @import("windows/ws2_32.zig").getsockopt(handle, level, opt, @ptrCast([*]u8, &val), &val_len); 320 | if (result == ws2_32.SOCKET_ERROR) { 321 | return switch (ws2_32.WSAGetLastError()) { 322 | .WSAEFAULT => error.InvalidParameter, 323 | .WSAENOPROTOOPT => error.UnsupportedOption, 324 | .WSAENOTSOCK => error.NotASocket, 325 | else => |err| windows.unexpectedWSAError(err), 326 | }; 327 | } 328 | 329 | return val; 330 | } 331 | 332 | pub fn shutdown(socket: ws2_32.SOCKET, how: c_int) !void { 333 | const result = @import("windows/ws2_32.zig").shutdown(socket, how); 334 | if (result == ws2_32.SOCKET_ERROR) { 335 | return switch (ws2_32.WSAGetLastError()) { 336 | .WSAECONNABORTED => error.ConnectionAborted, 337 | .WSAECONNRESET => error.ConnectionResetByPeer, 338 | .WSAEINPROGRESS => error.WouldBlock, 339 | .WSAEINVAL => error.BadArgument, 340 | .WSAENETDOWN => error.NetworkSubsystemFailed, 341 | .WSAENOTCONN => error.SocketNotConnected, 342 | .WSAENOTSOCK => error.FileDescriptorNotASocket, 343 | else => |err| windows.unexpectedWSAError(err), 344 | }; 345 | } 346 | } 347 | 348 | pub const SetSockOptError = error{ 349 | /// The socket is already connected, and a specified option cannot be set while the socket is connected. 350 | AlreadyConnected, 351 | 352 | /// The option is not supported by the protocol. 353 | InvalidProtocolOption, 354 | 355 | /// The send and receive timeout values are too big to fit into the timeout fields in the socket structure. 356 | TimeoutTooBig, 357 | 358 | /// Insufficient resources are available in the system to complete the call. 359 | SystemResources, 360 | 361 | NetworkSubsystemFailed, 362 | FileDescriptorNotASocket, 363 | SocketNotBound, 364 | SocketNotConnected, 365 | AlreadyShutdown, 366 | } || std.os.UnexpectedError; 367 | 368 | pub fn setsockopt(sock: ws2_32.SOCKET, level: u32, opt: u32, val: ?[]const u8) SetSockOptError!void { 369 | const rc = ws2_32.setsockopt(sock, level, opt, if (val) |v| v.ptr else null, if (val) |v| @intCast(ws2_32.socklen_t, v.len) else 0); 370 | if (rc == ws2_32.SOCKET_ERROR) { 371 | switch (ws2_32.WSAGetLastError()) { 372 | .WSANOTINITIALISED => unreachable, 373 | .WSAENETDOWN => return error.NetworkSubsystemFailed, 374 | .WSAEFAULT => unreachable, 375 | .WSAENOTSOCK => return error.FileDescriptorNotASocket, 376 | .WSAEINVAL => return error.SocketNotBound, 377 | .WSAENOTCONN => return error.SocketNotConnected, 378 | .WSAESHUTDOWN => return error.AlreadyShutdown, 379 | else => |err| return windows.unexpectedWSAError(err), 380 | } 381 | } 382 | } 383 | 384 | pub fn WSASendTo(sock: ws2_32.SOCKET, buf: []const u8, flags: windows.DWORD, addr: ?*const ws2_32.sockaddr, addr_len: ws2_32.socklen_t, overlapped: *windows.OVERLAPPED) !void { 385 | var wsa_buf = ws2_32.WSABUF{ 386 | .len = @truncate(u32, buf.len), 387 | .buf = @intToPtr([*]u8, @ptrToInt(buf.ptr)), 388 | }; 389 | 390 | const rc = ws2_32.WSASendTo(sock, @intToPtr([*]ws2_32.WSABUF, @ptrToInt(&wsa_buf)), 1, null, flags, addr, @intCast(c_int, addr_len), @ptrCast(*ws2_32.WSAOVERLAPPED, overlapped), null); 391 | 392 | if (rc == ws2_32.SOCKET_ERROR) { 393 | return switch (ws2_32.WSAGetLastError()) { 394 | .WSAECONNABORTED => error.ConnectionAborted, 395 | .WSAECONNRESET => error.ConnectionResetByPeer, 396 | .WSAEFAULT => error.BadBuffer, 397 | .WSAEINPROGRESS, .WSAEWOULDBLOCK, .WSA_IO_PENDING => error.WouldBlock, 398 | .WSAEINTR => error.Cancelled, 399 | .WSAEINVAL => error.SocketNotBound, 400 | .WSAEMSGSIZE => error.MessageTooLarge, 401 | .WSAENETDOWN => error.NetworkSubsystemFailed, 402 | .WSAENETRESET => error.NetworkReset, 403 | .WSAENOBUFS => error.BufferDeadlock, 404 | .WSAENOTCONN => error.SocketNotConnected, 405 | .WSAENOTSOCK => error.FileDescriptorNotASocket, 406 | .WSAEOPNOTSUPP => error.OperationNotSupported, 407 | .WSAESHUTDOWN => error.AlreadyShutdown, 408 | .WSAETIMEDOUT => error.Timeout, 409 | .WSA_OPERATION_ABORTED => error.OperationAborted, 410 | else => |err| windows.unexpectedWSAError(err), 411 | }; 412 | } 413 | } 414 | 415 | pub fn WSASend(sock: ws2_32.SOCKET, buf: []const u8, flags: windows.DWORD, overlapped: *windows.OVERLAPPED) !void { 416 | var wsa_buf = ws2_32.WSABUF{ 417 | .len = @truncate(u32, buf.len), 418 | .buf = @intToPtr([*]u8, @ptrToInt(buf.ptr)), 419 | }; 420 | 421 | const rc = ws2_32.WSASend(sock, @intToPtr([*]ws2_32.WSABUF, @ptrToInt(&wsa_buf)), 1, null, flags, @ptrCast(*ws2_32.WSAOVERLAPPED, overlapped), null); 422 | 423 | if (rc == ws2_32.SOCKET_ERROR) { 424 | return switch (ws2_32.WSAGetLastError()) { 425 | .WSAECONNABORTED => error.ConnectionAborted, 426 | .WSAECONNRESET => error.ConnectionResetByPeer, 427 | .WSAEFAULT => error.BadBuffer, 428 | .WSAEINPROGRESS, .WSAEWOULDBLOCK, .WSA_IO_PENDING => error.WouldBlock, 429 | .WSAEINTR => error.Cancelled, 430 | .WSAEINVAL => error.SocketNotBound, 431 | .WSAEMSGSIZE => error.MessageTooLarge, 432 | .WSAENETDOWN => error.NetworkSubsystemFailed, 433 | .WSAENETRESET => error.NetworkReset, 434 | .WSAENOBUFS => error.BufferDeadlock, 435 | .WSAENOTCONN => error.SocketNotConnected, 436 | .WSAENOTSOCK => error.FileDescriptorNotASocket, 437 | .WSAEOPNOTSUPP => error.OperationNotSupported, 438 | .WSAESHUTDOWN => error.AlreadyShutdown, 439 | .WSAETIMEDOUT => error.Timeout, 440 | .WSA_OPERATION_ABORTED => error.OperationAborted, 441 | else => |err| windows.unexpectedWSAError(err), 442 | }; 443 | } 444 | } 445 | 446 | pub fn WSARecv(sock: ws2_32.SOCKET, buf: []u8, flags: windows.DWORD, overlapped: *windows.OVERLAPPED) !void { 447 | var wsa_flags: windows.DWORD = flags; 448 | var wsa_buf = ws2_32.WSABUF{ 449 | .len = @truncate(u32, buf.len), 450 | .buf = buf.ptr, 451 | }; 452 | 453 | const rc = ws2_32.WSARecv(sock, @intToPtr([*]const ws2_32.WSABUF, @ptrToInt(&wsa_buf)), 1, null, &wsa_flags, @ptrCast(*ws2_32.WSAOVERLAPPED, overlapped), null); 454 | 455 | if (rc == ws2_32.SOCKET_ERROR) { 456 | return switch (ws2_32.WSAGetLastError()) { 457 | .WSAECONNABORTED => error.ConnectionAborted, 458 | .WSAECONNRESET => error.ConnectionResetByPeer, 459 | .WSAEDISCON => error.ConnectionClosedByPeer, 460 | .WSAEFAULT => error.BadBuffer, 461 | .WSAEINPROGRESS, .WSAEWOULDBLOCK, .WSA_IO_PENDING => error.WouldBlock, 462 | .WSAEINTR => error.Cancelled, 463 | .WSAEINVAL => error.SocketNotBound, 464 | .WSAEMSGSIZE => error.MessageTooLarge, 465 | .WSAENETDOWN => error.NetworkSubsystemFailed, 466 | .WSAENETRESET => error.NetworkReset, 467 | .WSAENOTCONN => error.SocketNotConnected, 468 | .WSAENOTSOCK => error.FileDescriptorNotASocket, 469 | .WSAEOPNOTSUPP => error.OperationNotSupported, 470 | .WSAESHUTDOWN => error.AlreadyShutdown, 471 | .WSAETIMEDOUT => error.Timeout, 472 | .WSA_OPERATION_ABORTED => error.OperationAborted, 473 | else => |err| windows.unexpectedWSAError(err), 474 | }; 475 | } 476 | } 477 | 478 | pub fn WSARecvFrom(sock: ws2_32.SOCKET, buf: []u8, flags: windows.DWORD, addr: ?*ws2_32.sockaddr, addr_len: ?*ws2_32.socklen_t, overlapped: *windows.OVERLAPPED) !void { 479 | var wsa_flags: windows.DWORD = flags; 480 | var wsa_buf = ws2_32.WSABUF{ 481 | .len = @truncate(u32, buf.len), 482 | .buf = buf.ptr, 483 | }; 484 | 485 | const rc = ws2_32.WSARecvFrom(sock, @intToPtr([*]const ws2_32.WSABUF, @ptrToInt(&wsa_buf)), 1, null, &wsa_flags, addr, addr_len, @ptrCast(*ws2_32.WSAOVERLAPPED, overlapped), null); 486 | 487 | if (rc == ws2_32.SOCKET_ERROR) { 488 | return switch (ws2_32.WSAGetLastError()) { 489 | .WSAECONNABORTED => error.ConnectionAborted, 490 | .WSAECONNRESET => error.ConnectionResetByPeer, 491 | .WSAEDISCON => error.ConnectionClosedByPeer, 492 | .WSAEFAULT => error.BadBuffer, 493 | .WSAEINPROGRESS, .WSAEWOULDBLOCK, .WSA_IO_PENDING => error.WouldBlock, 494 | .WSAEINTR => error.Cancelled, 495 | .WSAEINVAL => error.SocketNotBound, 496 | .WSAEMSGSIZE => error.MessageTooLarge, 497 | .WSAENETDOWN => error.NetworkSubsystemFailed, 498 | .WSAENETRESET => error.NetworkReset, 499 | .WSAENOTCONN => error.SocketNotConnected, 500 | .WSAENOTSOCK => error.FileDescriptorNotASocket, 501 | .WSAEOPNOTSUPP => error.OperationNotSupported, 502 | .WSAESHUTDOWN => error.AlreadyShutdown, 503 | .WSAETIMEDOUT => error.Timeout, 504 | .WSA_OPERATION_ABORTED => error.OperationAborted, 505 | else => |err| windows.unexpectedWSAError(err), 506 | }; 507 | } 508 | } 509 | 510 | pub fn ReadFile_(handle: windows.HANDLE, buf: []u8, overlapped: *windows.OVERLAPPED) !void { 511 | const len = math.cast(windows.DWORD, buf.len) catch math.maxInt(windows.DWORD); 512 | 513 | const success = windows.kernel32.ReadFile(handle, buf.ptr, len, null, overlapped); 514 | if (success == windows.FALSE) { 515 | return switch (windows.kernel32.GetLastError()) { 516 | .IO_PENDING => error.WouldBlock, 517 | .OPERATION_ABORTED => error.OperationAborted, 518 | .BROKEN_PIPE => error.BrokenPipe, 519 | .HANDLE_EOF, .NETNAME_DELETED => error.EndOfFile, 520 | else => |err| windows.unexpectedError(err), 521 | }; 522 | } 523 | } 524 | 525 | pub fn WriteFile_(handle: windows.HANDLE, buf: []const u8, overlapped: *windows.OVERLAPPED) !void { 526 | const len = math.cast(windows.DWORD, buf.len) catch math.maxInt(windows.DWORD); 527 | 528 | const success = windows.kernel32.WriteFile(handle, buf.ptr, len, null, overlapped); 529 | if (success == windows.FALSE) { 530 | return switch (windows.kernel32.GetLastError()) { 531 | .IO_PENDING => error.WouldBlock, 532 | .OPERATION_ABORTED => error.OperationAborted, 533 | .BROKEN_PIPE => error.BrokenPipe, 534 | .HANDLE_EOF, .NETNAME_DELETED => error.EndOfFile, 535 | else => |err| windows.unexpectedError(err), 536 | }; 537 | } 538 | } 539 | 540 | pub fn CancelIoEx(handle: windows.HANDLE, overlapped: *windows.OVERLAPPED) !void { 541 | const success = windows.kernel32.CancelIoEx(handle, overlapped); 542 | if (success == windows.FALSE) { 543 | return switch (windows.kernel32.GetLastError()) { 544 | .NOT_FOUND => error.RequestNotFound, 545 | else => |err| windows.unexpectedError(err), 546 | }; 547 | } 548 | } 549 | 550 | pub fn GetOverlappedResult_(h: windows.HANDLE, overlapped: *windows.OVERLAPPED, wait: bool) !windows.DWORD { 551 | var bytes: windows.DWORD = undefined; 552 | if (windows.kernel32.GetOverlappedResult(h, overlapped, &bytes, @boolToInt(wait)) == 0) { 553 | return switch (windows.kernel32.GetLastError()) { 554 | .IO_INCOMPLETE => if (!wait) error.WouldBlock else unreachable, 555 | .OPERATION_ABORTED => error.OperationAborted, 556 | else => |err| windows.unexpectedError(err), 557 | }; 558 | } 559 | return bytes; 560 | } 561 | -------------------------------------------------------------------------------- /os/windows/kernel32.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const windows = @import("../windows.zig"); 4 | const kernel32 = windows.kernel32; 5 | 6 | pub usingnamespace kernel32; 7 | 8 | pub extern "kernel32" fn SetConsoleCtrlHandler( 9 | HandlerRoutine: ?windows.HANDLER_ROUTINE, 10 | Add: windows.BOOL, 11 | ) callconv(.Stdcall) windows.BOOL; 12 | 13 | pub extern "kernel32" fn SetFileCompletionNotificationModes( 14 | FileHandle: windows.HANDLE, 15 | Flags: windows.UCHAR, 16 | ) callconv(.Stdcall) windows.BOOL; 17 | 18 | pub extern "kernel32" fn GetQueuedCompletionStatusEx( 19 | CompletionPort: windows.HANDLE, 20 | lpCompletionPortEntries: [*]windows.OVERLAPPED_ENTRY, 21 | ulCount: windows.ULONG, 22 | ulNumEntriesRemoved: *windows.ULONG, 23 | dwMilliseconds: windows.DWORD, 24 | fAlertable: windows.BOOL, 25 | ) callconv(.Stdcall) windows.BOOL; 26 | -------------------------------------------------------------------------------- /os/windows/ws2_32.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const windows = @import("../windows.zig"); 4 | const ws2_32 = windows.ws2_32; 5 | 6 | pub usingnamespace ws2_32; 7 | 8 | const IOC_VOID = 0x80000000; 9 | const IOC_OUT = 0x40000000; 10 | const IOC_IN = 0x80000000; 11 | const IOC_WS2 = 0x08000000; 12 | 13 | pub const SIO_BSP_HANDLE = IOC_OUT | IOC_WS2 | 27; 14 | pub const SIO_BSP_HANDLE_SELECT = IOC_OUT | IOC_WS2 | 28; 15 | pub const SIO_BSP_HANDLE_POLL = IOC_OUT | IOC_WS2 | 29; 16 | 17 | pub const SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_OUT | IOC_IN | IOC_WS2 | 6; 18 | 19 | pub const SO_UPDATE_CONNECT_CONTEXT = 0x7010; 20 | pub const SO_UPDATE_ACCEPT_CONTEXT = 0x700B; 21 | 22 | pub const SD_RECEIVE = 0; 23 | pub const SD_SEND = 1; 24 | pub const SD_BOTH = 2; 25 | 26 | pub const LINGER = extern struct { 27 | l_onoff: windows.USHORT, // Whether or not a socket should remain open to send queued dataa after closesocket() is called. 28 | l_linger: windows.USHORT, // Number of seconds on how long a socket should remain open after closesocket() is called. 29 | }; 30 | 31 | pub const WSAID_CONNECTEX = windows.GUID{ 32 | .Data1 = 0x25a207b9, 33 | .Data2 = 0xddf3, 34 | .Data3 = 0x4660, 35 | .Data4 = [8]u8{ 0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e }, 36 | }; 37 | 38 | pub const WSAID_ACCEPTEX = windows.GUID{ 39 | .Data1 = 0xb5367df1, 40 | .Data2 = 0xcbac, 41 | .Data3 = 0x11cf, 42 | .Data4 = [8]u8{ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 }, 43 | }; 44 | 45 | pub const WSAID_GETACCEPTEXSOCKADDRS = windows.GUID{ 46 | .Data1 = 0xb5367df2, 47 | .Data2 = 0xcbac, 48 | .Data3 = 0x11cf, 49 | .Data4 = [8]u8{ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 }, 50 | }; 51 | 52 | pub const sockaddr_storage = extern struct { 53 | family: ws2_32.ADDRESS_FAMILY, 54 | __ss_padding: [128 - @sizeOf(windows.ULONGLONG) - @sizeOf(ws2_32.ADDRESS_FAMILY)]u8, 55 | __ss_align: windows.ULONGLONG, 56 | }; 57 | 58 | pub extern "ws2_32" fn getsockopt( 59 | s: ws2_32.SOCKET, 60 | level: c_int, 61 | optname: c_int, 62 | optval: [*]u8, 63 | optlen: *c_int, 64 | ) callconv(.Stdcall) c_int; 65 | 66 | pub extern "ws2_32" fn shutdown( 67 | s: ws2_32.SOCKET, 68 | how: c_int, 69 | ) callconv(.Stdcall) c_int; 70 | 71 | pub extern "ws2_32" fn recv( 72 | s: ws2_32.SOCKET, 73 | buf: [*]u8, 74 | len: c_int, 75 | flags: c_int, 76 | ) callconv(.Stdcall) c_int; 77 | 78 | pub const ConnectEx = fn ( 79 | s: ws2_32.SOCKET, 80 | name: *const ws2_32.sockaddr, 81 | namelen: c_int, 82 | lpSendBuffer: ?*c_void, 83 | dwSendDataLength: windows.DWORD, 84 | lpdwBytesSent: ?*windows.DWORD, 85 | lpOverlapped: *windows.OVERLAPPED, 86 | ) callconv(.Stdcall) windows.BOOL; 87 | 88 | pub const AcceptEx = fn ( 89 | sListenSocket: ws2_32.SOCKET, 90 | sAcceptSocket: ws2_32.SOCKET, 91 | lpOutputBuffer: [*]u8, 92 | dwReceiveDataLength: windows.DWORD, 93 | dwLocalAddressLength: ws2_32.socklen_t, 94 | dwRemoteAddressLength: ws2_32.socklen_t, 95 | lpdwBytesReceived: ?*windows.DWORD, 96 | lpOverlapped: *windows.OVERLAPPED, 97 | ) callconv(.Stdcall) windows.BOOL; 98 | 99 | pub const GetAcceptExSockaddrs = fn ( 100 | lpOutputBuffer: [*]const u8, 101 | dwReceiveDataLength: windows.DWORD, 102 | dwLocalAddressLength: ws2_32.socklen_t, 103 | dwRemoteAddressLength: ws2_32.socklen_t, 104 | LocalSockaddr: **ws2_32.sockaddr, 105 | LocalSockaddrLength: *windows.INT, 106 | RemoteSockaddr: **ws2_32.sockaddr, 107 | RemoteSockaddrLength: *windows.INT, 108 | ) callconv(.Stdcall) void; 109 | -------------------------------------------------------------------------------- /pike.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const root = @import("root"); 3 | 4 | const os = std.os; 5 | const builtin = @import("builtin"); 6 | 7 | pub const PollOptions = packed struct { 8 | read: bool = false, 9 | write: bool = false, 10 | }; 11 | 12 | pub const CallOptions = packed struct { 13 | read: bool = false, 14 | write: bool = false, 15 | }; 16 | 17 | pub const WakeOptions = packed struct { 18 | notify: bool = false, 19 | shutdown: bool = false, 20 | read_ready: bool = false, 21 | write_ready: bool = false, 22 | }; 23 | 24 | const has_epoll = @hasDecl(os.linux, "epoll_create1") and @hasDecl(os.linux, "epoll_ctl") and @hasDecl(os.linux, "epoll_event"); 25 | const has_kqueue = @hasDecl(os.system, "kqueue") and @hasDecl(os.system, "kevent") and @hasDecl(os, "Kevent"); 26 | 27 | // Export asynchronous frame dispatcher and user scope (Task). 28 | 29 | pub const Task = if (@hasDecl(root, "pike_task")) 30 | root.pike_task 31 | else 32 | struct { 33 | next: ?*Task = null, 34 | frame: anyframe, 35 | 36 | pub inline fn init(frame: anyframe) Task { 37 | return .{ .frame = frame }; 38 | } 39 | }; 40 | 41 | pub const Batch = if (@hasDecl(root, "pike_batch")) 42 | root.pike_batch 43 | else 44 | struct { 45 | head: ?*Task = null, 46 | tail: *Task = undefined, 47 | 48 | pub fn from(batchable: anytype) Batch { 49 | if (@TypeOf(batchable) == Batch) 50 | return batchable; 51 | 52 | if (@TypeOf(batchable) == *Task) { 53 | batchable.next = null; 54 | return Batch{ 55 | .head = batchable, 56 | .tail = batchable, 57 | }; 58 | } 59 | 60 | if (@TypeOf(batchable) == ?*Task) { 61 | const task: *Task = batchable orelse return Batch{}; 62 | return Batch.from(task); 63 | } 64 | 65 | @compileError(@typeName(@TypeOf(batchable)) ++ " cannot be converted into a " ++ @typeName(Batch)); 66 | } 67 | 68 | pub fn push(self: *Batch, entity: anytype) void { 69 | const other = Batch.from(entity); 70 | if (self.head == null) { 71 | self.* = other; 72 | } else { 73 | self.tail.next = other.head; 74 | self.tail = other.tail; 75 | } 76 | } 77 | 78 | pub fn pop(self: *Batch) ?*Task { 79 | const task = self.head orelse return null; 80 | self.head = task.next; 81 | return task; 82 | } 83 | }; 84 | 85 | pub const dispatch: fn (anytype, anytype) void = if (@hasDecl(root, "pike_dispatch")) 86 | root.pike_dispatch 87 | else 88 | struct { 89 | fn default(batchable: anytype, args: anytype) void { 90 | var batch = Batch.from(batchable); 91 | _ = args; 92 | while (batch.pop()) |task| { 93 | resume task.frame; 94 | } 95 | } 96 | }.default; 97 | 98 | // Export 'Notifier' and 'Handle'. 99 | 100 | pub usingnamespace if (@hasDecl(root, "notifier")) 101 | root.notifier 102 | else if (has_epoll) 103 | @import("notifier_epoll.zig") 104 | else if (has_kqueue) 105 | @import("notifier_kqueue.zig") 106 | else if (builtin.os.tag == .windows) 107 | @import("notifier_iocp.zig") 108 | else 109 | @compileError("pike: unable to figure out a 'Notifier'/'Handle' implementation to use for the build target"); 110 | 111 | // Export 'SocketOptionType', 'SocketOption', and 'Socket'. 112 | 113 | pub usingnamespace if (builtin.os.tag == .windows) 114 | @import("socket_windows.zig") 115 | else 116 | @import("socket_posix.zig"); 117 | 118 | // Export 'SignalType', and 'Signal'. 119 | 120 | pub usingnamespace if (has_epoll or has_kqueue) 121 | @import("signal_posix.zig") 122 | else if (builtin.os.tag == .windows) 123 | @import("signal_windows.zig") 124 | else 125 | @compileError("pike: unable to figure out a 'Signal' implementation to use for the build target"); 126 | 127 | // Export 'Event'. 128 | 129 | pub usingnamespace if (has_epoll) 130 | @import("event_epoll.zig") 131 | else if (has_kqueue) 132 | @import("event_kqueue.zig") 133 | else if (builtin.os.tag == .windows) 134 | @import("event_iocp.zig") 135 | else 136 | @compileError("pike: unable to figure out a 'Event' implementation to use for the build target"); 137 | 138 | // Export 'Waker' and 'PackedWaker'. 139 | 140 | pub usingnamespace @import("waker.zig"); 141 | -------------------------------------------------------------------------------- /signal_posix.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const posix = @import("os/posix.zig"); 4 | const PackedWaker = @import("waker.zig").PackedWaker; 5 | const os = std.os; 6 | const system = os.system; 7 | 8 | const mem = std.mem; 9 | const meta = std.meta; 10 | const builtin = @import("builtin"); 11 | 12 | pub const SignalType = packed struct { 13 | terminate: bool = false, 14 | interrupt: bool = false, 15 | quit: bool = false, 16 | hup: bool = false, 17 | 18 | fn toSet(self: SignalType) os.sigset_t { 19 | const sigaddset = if (comptime builtin.target.isDarwin()) system.sigaddset else os.linux.sigaddset; 20 | 21 | var set = mem.zeroes(os.sigset_t); 22 | if (self.terminate) sigaddset(&set, os.SIG.TERM); 23 | if (self.interrupt) sigaddset(&set, os.SIG.INT); 24 | if (self.quit) sigaddset(&set, os.SIG.QUIT); 25 | if (self.hup) sigaddset(&set, os.SIG.HUP); 26 | 27 | return set; 28 | } 29 | }; 30 | 31 | pub const Signal = struct { 32 | const EMPTY_SIGACTION = os.Sigaction{ 33 | .handler = .{ .handler = null }, 34 | .mask = mem.zeroes(os.sigset_t), 35 | .flags = 0, 36 | }; 37 | 38 | const MaskInt = meta.Int(.unsigned, @bitSizeOf(SignalType)); 39 | const Self = @This(); 40 | 41 | var lock: std.Thread.Mutex = .{}; 42 | var mask: SignalType = .{}; 43 | var waker: PackedWaker(pike.Task, SignalType) = .{}; 44 | 45 | current: SignalType, 46 | previous: [@bitSizeOf(SignalType)]os.Sigaction, 47 | 48 | fn handler(signal: c_int) callconv(.C) void { 49 | const current_held = lock.acquire(); 50 | const current_mask = mask; 51 | current_held.release(); 52 | 53 | switch (signal) { 54 | os.SIG.TERM => { 55 | if (!current_mask.terminate) return; 56 | 57 | const held = lock.acquire(); 58 | const next_node = waker.wake(.{ .terminate = true }); 59 | held.release(); 60 | 61 | if (next_node) |node| pike.dispatch(&node.data, .{}); 62 | }, 63 | os.SIG.INT => { 64 | if (!current_mask.interrupt) return; 65 | 66 | const held = lock.acquire(); 67 | const next_node = waker.wake(.{ .interrupt = true }); 68 | held.release(); 69 | 70 | if (next_node) |node| pike.dispatch(&node.data, .{}); 71 | }, 72 | os.SIG.QUIT => { 73 | if (!current_mask.quit) return; 74 | 75 | const held = lock.acquire(); 76 | const next_node = waker.wake(.{ .quit = true }); 77 | held.release(); 78 | 79 | if (next_node) |node| pike.dispatch(&node.data, .{}); 80 | }, 81 | os.SIG.HUP => { 82 | if (!current_mask.hup) return; 83 | 84 | const held = lock.acquire(); 85 | const next_node = waker.wake(.{ .hup = true }); 86 | held.release(); 87 | 88 | if (next_node) |node| pike.dispatch(&node.data, .{}); 89 | }, 90 | else => {}, 91 | } 92 | } 93 | 94 | pub fn init(current: SignalType) !Self { 95 | const held = lock.acquire(); 96 | defer held.release(); 97 | 98 | const new_mask = @bitCast(SignalType, @bitCast(MaskInt, current) | @bitCast(MaskInt, mask)); 99 | 100 | const sigaction = os.Sigaction{ 101 | .handler = .{ .handler = handler }, 102 | .mask = new_mask.toSet(), 103 | .flags = 0, 104 | }; 105 | 106 | var previous = [_]os.Sigaction{EMPTY_SIGACTION} ** @bitSizeOf(SignalType); 107 | 108 | os.sigaction(os.SIG.TERM, &sigaction, &previous[std.meta.fieldIndex(SignalType, "terminate").?]); 109 | os.sigaction(os.SIG.INT, &sigaction, &previous[std.meta.fieldIndex(SignalType, "interrupt").?]); 110 | os.sigaction(os.SIG.QUIT, &sigaction, &previous[std.meta.fieldIndex(SignalType, "quit").?]); 111 | os.sigaction(os.SIG.HUP, &sigaction, &previous[std.meta.fieldIndex(SignalType, "hup").?]); 112 | 113 | mask = new_mask; 114 | 115 | return Self{ 116 | .current = current, 117 | .previous = previous, 118 | }; 119 | } 120 | 121 | pub fn deinit(self: *Self) void { 122 | for (self.previous) |sigaction, i| { 123 | os.sigaction( 124 | switch (i) { 125 | 0 => os.SIG.TERM, 126 | 1 => os.SIG.INT, 127 | 2 => os.SIG.QUIT, 128 | 3 => os.SIG.HUP, 129 | else => unreachable, 130 | }, 131 | &sigaction, 132 | null, 133 | ); 134 | } 135 | } 136 | 137 | pub fn wait(self: *Self) callconv(.Async) !void { 138 | const held = lock.acquire(); 139 | if (waker.wait(self.current)) { 140 | held.release(); 141 | } else { 142 | suspend { 143 | var node = @TypeOf(waker).FrameNode{ .data = pike.Task.init(@frame()) }; 144 | @TypeOf(waker).FrameList.append(&waker.heads, self.current, &node); 145 | held.release(); 146 | } 147 | 148 | const next_held = lock.acquire(); 149 | const next_node = waker.next(self.current); 150 | next_held.release(); 151 | 152 | if (next_node) |node| { 153 | pike.dispatch(&node.data, .{}); 154 | } 155 | } 156 | } 157 | }; 158 | -------------------------------------------------------------------------------- /signal_windows.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const windows = @import("os/windows.zig"); 4 | const PackedWaker = @import("waker.zig").PackedWaker; 5 | const math = std.math; 6 | const meta = std.meta; 7 | 8 | pub const SignalType = packed struct { 9 | terminate: bool = false, 10 | interrupt: bool = false, 11 | quit: bool = false, 12 | hup: bool = false, 13 | }; 14 | 15 | pub const Signal = struct { 16 | const MaskInt = meta.Int(.unsigned, @bitSizeOf(SignalType)); 17 | 18 | const Self = @This(); 19 | 20 | var refs: u64 = 0; 21 | var mask: u64 = 0; 22 | 23 | var lock: std.Thread.Mutex = .{}; 24 | var waker: PackedWaker(pike.Task, SignalType) = .{}; 25 | 26 | current_signal: SignalType, 27 | previous_signal: u64, 28 | 29 | fn handler(signal: windows.DWORD) callconv(.C) windows.BOOL { 30 | const current = @bitCast(SignalType, @truncate(MaskInt, @atomicLoad(u64, &mask, .SeqCst))); 31 | 32 | return blk: { 33 | switch (signal) { 34 | windows.CTRL_C_EVENT, windows.CTRL_BREAK_EVENT => { 35 | if (!current.interrupt and !current.terminate) break :blk windows.FALSE; 36 | 37 | const held = lock.acquire(); 38 | const next_node = waker.wake(.{ .interrupt = true, .terminate = true }); 39 | held.release(); 40 | 41 | if (next_node) |node| pike.dispatch(&node.data, .{}); 42 | 43 | break :blk windows.TRUE; 44 | }, 45 | windows.CTRL_CLOSE_EVENT => { 46 | if (!current.hup) break :blk windows.FALSE; 47 | 48 | const held = lock.acquire(); 49 | const next_node = waker.wake(.{ .hup = true }); 50 | held.release(); 51 | 52 | if (next_node) |node| pike.dispatch(&node.data, .{}); 53 | 54 | break :blk windows.TRUE; 55 | }, 56 | windows.CTRL_LOGOFF_EVENT, windows.CTRL_SHUTDOWN_EVENT => { 57 | if (!current.quit) break :blk windows.FALSE; 58 | 59 | const held = lock.acquire(); 60 | const next_node = waker.wake(.{ .quit = true }); 61 | held.release(); 62 | 63 | if (next_node) |node| pike.dispatch(&node.data, .{}); 64 | 65 | break :blk windows.TRUE; 66 | }, 67 | else => break :blk windows.FALSE, 68 | } 69 | }; 70 | } 71 | 72 | pub fn init(current_signal: SignalType) !Self { 73 | errdefer _ = @atomicRmw(u64, &refs, .Sub, 1, .SeqCst); 74 | 75 | if (@atomicRmw(u64, &refs, .Add, 1, .SeqCst) == 0) { 76 | try windows.SetConsoleCtrlHandler(handler, true); 77 | } 78 | 79 | const previous_signal = @atomicRmw(u64, &mask, .Or, @intCast(u64, @bitCast(MaskInt, current_signal)), .SeqCst); 80 | 81 | return Self{ 82 | .current_signal = current_signal, 83 | .previous_signal = previous_signal, 84 | }; 85 | } 86 | 87 | pub fn deinit(self: *const Self) void { 88 | @atomicStore(u64, &mask, self.previous_signal, .SeqCst); 89 | if (@atomicRmw(u64, &refs, .Sub, 1, .SeqCst) == 1) { 90 | windows.SetConsoleCtrlHandler(handler, false) catch unreachable; 91 | 92 | const held = lock.acquire(); 93 | while (waker.wake(@bitCast(SignalType, @as(MaskInt, math.maxInt(MaskInt))))) |node| { 94 | pike.dispatch(&node.data, .{}); 95 | } 96 | held.release(); 97 | } 98 | } 99 | 100 | pub fn wait(self: *const Self) callconv(.Async) !void { 101 | const held = lock.acquire(); 102 | if (waker.wait(self.current_signal)) { 103 | held.release(); 104 | } else { 105 | suspend { 106 | var node = @TypeOf(waker).FrameNode{ .data = pike.Task.init(@frame()) }; 107 | @TypeOf(waker).FrameList.append(&waker.heads, self.current_signal, &node); 108 | held.release(); 109 | } 110 | 111 | const next_held = lock.acquire(); 112 | const next_node = waker.next(self.current_signal); 113 | next_held.release(); 114 | 115 | if (next_node) |node| { 116 | pike.dispatch(&node.data, .{}); 117 | } 118 | } 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /socket_posix.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const posix = @import("os/posix.zig"); 4 | const Waker = @import("waker.zig").Waker; 5 | const io = std.io; 6 | const os = std.os; 7 | const net = std.net; 8 | const mem = std.mem; 9 | const meta = std.meta; 10 | 11 | pub const SocketOptionType = enum(u32) { 12 | debug = os.SO.DEBUG, 13 | listen = os.SO.ACCEPTCONN, 14 | reuse_address = os.SO.REUSEADDR, 15 | keep_alive = os.SO.KEEPALIVE, 16 | dont_route = os.SO.DONTROUTE, 17 | broadcast = os.SO.BROADCAST, 18 | linger = os.SO.LINGER, 19 | oob_inline = os.SO.OOBINLINE, 20 | 21 | send_buffer_max_size = os.SO.SNDBUF, 22 | recv_buffer_max_size = os.SO.RCVBUF, 23 | 24 | send_buffer_min_size = os.SO.SNDLOWAT, 25 | recv_buffer_min_size = os.SO.RCVLOWAT, 26 | 27 | send_timeout = os.SO.SNDTIMEO, 28 | recv_timeout = os.SO.RCVTIMEO, 29 | 30 | socket_error = os.SO.ERROR, 31 | socket_type = os.SO.TYPE, 32 | }; 33 | 34 | pub const SocketOption = union(SocketOptionType) { 35 | debug: bool, 36 | listen: bool, 37 | reuse_address: bool, 38 | keep_alive: bool, 39 | dont_route: bool, 40 | broadcast: bool, 41 | linger: posix.LINGER, 42 | oob_inline: bool, 43 | 44 | send_buffer_max_size: u32, 45 | recv_buffer_max_size: u32, 46 | 47 | send_buffer_min_size: u32, 48 | recv_buffer_min_size: u32, 49 | 50 | send_timeout: u32, // Timeout specified in milliseconds. 51 | recv_timeout: u32, // Timeout specified in milliseconds. 52 | 53 | socket_error: void, 54 | socket_type: u32, 55 | }; 56 | 57 | pub const Connection = struct { 58 | socket: Socket, 59 | address: net.Address, 60 | }; 61 | 62 | pub const Socket = struct { 63 | pub const Reader = io.Reader(*Self, anyerror, read); 64 | pub const Writer = io.Writer(*Self, anyerror, write); 65 | 66 | const Self = @This(); 67 | 68 | handle: pike.Handle, 69 | readers: Waker = .{}, 70 | writers: Waker = .{}, 71 | 72 | pub fn init(domain: u32, socket_type: u32, protocol: u32, flags: u32) !Self { 73 | return Self{ 74 | .handle = .{ 75 | .inner = try os.socket( 76 | domain, 77 | socket_type | flags | os.SOCK.CLOEXEC | os.SOCK.NONBLOCK, 78 | protocol, 79 | ), 80 | .wake_fn = wake, 81 | }, 82 | }; 83 | } 84 | 85 | pub fn deinit(self: *Self) void { 86 | self.shutdown(posix.SHUT_RDWR) catch {}; 87 | 88 | if (self.writers.shutdown()) |task| pike.dispatch(task, .{}); 89 | if (self.readers.shutdown()) |task| pike.dispatch(task, .{}); 90 | 91 | os.close(self.handle.inner); 92 | } 93 | 94 | pub fn registerTo(self: *const Self, notifier: *const pike.Notifier) !void { 95 | try notifier.register(&self.handle, .{ .read = true, .write = true }); 96 | } 97 | 98 | fn wake(handle: *pike.Handle, batch: *pike.Batch, opts: pike.WakeOptions) void { 99 | const self = @fieldParentPtr(Self, "handle", handle); 100 | 101 | if (opts.write_ready) if (self.writers.notify()) |task| batch.push(task); 102 | if (opts.read_ready) if (self.readers.notify()) |task| batch.push(task); 103 | if (opts.shutdown) { 104 | if (self.writers.shutdown()) |task| batch.push(task); 105 | if (self.readers.shutdown()) |task| batch.push(task); 106 | } 107 | } 108 | 109 | fn ErrorUnionOf(comptime func: anytype) std.builtin.TypeInfo.ErrorUnion { 110 | return @typeInfo(@typeInfo(@TypeOf(func)).Fn.return_type.?).ErrorUnion; 111 | } 112 | 113 | fn call(self: *Self, comptime function: anytype, args: anytype, comptime opts: pike.CallOptions) !ErrorUnionOf(function).payload { 114 | while (true) { 115 | const result = @call(.{ .modifier = .always_inline }, function, args) catch |err| switch (err) { 116 | error.WouldBlock => { 117 | if (comptime opts.write) { 118 | try self.writers.wait(.{ .use_lifo = true }); 119 | } else if (comptime opts.read) { 120 | try self.readers.wait(.{}); 121 | } 122 | continue; 123 | }, 124 | else => return err, 125 | }; 126 | 127 | return result; 128 | } 129 | } 130 | 131 | pub fn shutdown(self: *const Self, how: c_int) !void { 132 | try posix.shutdown_(self.handle.inner, how); 133 | } 134 | 135 | pub fn get(self: *const Self, comptime opt: SocketOptionType) !meta.TagPayload(SocketOption, opt) { 136 | if (opt == .socket_error) { 137 | const errno = try posix.getsockopt(u32, self.handle.inner, os.SOL.SOCKET, @enumToInt(opt)); 138 | return switch (errno) { 139 | @enumToInt(os.E.SUCCESS) => {}, 140 | @enumToInt(os.E.ACCES) => error.PermissionDenied, 141 | @enumToInt(os.E.PERM) => error.PermissionDenied, 142 | @enumToInt(os.E.ADDRINUSE) => error.AddressInUse, 143 | @enumToInt(os.E.ADDRNOTAVAIL) => error.AddressNotAvailable, 144 | @enumToInt(os.E.AFNOSUPPORT) => error.AddressFamilyNotSupported, 145 | @enumToInt(os.E.AGAIN) => error.SystemResources, 146 | @enumToInt(os.E.ALREADY) => error.AlreadyConnecting, 147 | @enumToInt(os.E.BADF) => error.BadFileDescriptor, 148 | @enumToInt(os.E.CONNREFUSED) => error.ConnectionRefused, 149 | @enumToInt(os.E.FAULT) => error.InvalidParameter, 150 | @enumToInt(os.E.ISCONN) => error.AlreadyConnected, 151 | @enumToInt(os.E.NETUNREACH) => error.NetworkUnreachable, 152 | @enumToInt(os.E.NOTSOCK) => error.NotASocket, 153 | @enumToInt(os.E.PROTOTYPE) => error.UnsupportedProtocol, 154 | @enumToInt(os.E.TIMEDOUT) => error.ConnectionTimedOut, 155 | else => |err| os.unexpectedErrno(@intToEnum(os.E, err)), 156 | }; 157 | } 158 | 159 | return posix.getsockopt( 160 | meta.TagPayload(SocketOption, opt), 161 | self.handle.inner, 162 | os.SOL.SOCKET, 163 | @enumToInt(opt), 164 | ); 165 | } 166 | 167 | pub fn set(self: *const Self, comptime opt: SocketOptionType, value: meta.TagPayload(SocketOption, opt)) !void { 168 | const val = switch (@TypeOf(value)) { 169 | bool => @intCast(c_int, @boolToInt(value)), 170 | else => value, 171 | }; 172 | 173 | try os.setsockopt( 174 | self.handle.inner, 175 | os.SOL.SOCKET, 176 | @enumToInt(opt), 177 | blk: { 178 | if (comptime @typeInfo(@TypeOf(val)) == .Optional) { 179 | break :blk if (val) |v| @as([]const u8, mem.asBytes(&v)[0..@sizeOf(@TypeOf(val))]) else &[0]u8{}; 180 | } else { 181 | break :blk @as([]const u8, mem.asBytes(&val)[0..@sizeOf(@TypeOf(val))]); 182 | } 183 | }, 184 | ); 185 | } 186 | 187 | pub fn getBindAddress(self: *const Self) !net.Address { 188 | var addr: os.sockaddr = undefined; 189 | var addr_len: os.socklen_t = @sizeOf(@TypeOf(addr)); 190 | try os.getsockname(self.handle.inner, &addr, &addr_len); 191 | return net.Address.initPosix(@alignCast(4, &addr)); 192 | } 193 | 194 | pub fn bind(self: *Self, address: net.Address) !void { 195 | try os.bind(self.handle.inner, &address.any, address.getOsSockLen()); 196 | } 197 | 198 | pub fn listen(self: *Self, backlog: usize) !void { 199 | try os.listen(self.handle.inner, @truncate(u31, backlog)); 200 | } 201 | 202 | pub fn accept(self: *Self) callconv(.Async) !Connection { 203 | var addr: os.sockaddr = undefined; 204 | var addr_len: os.socklen_t = @sizeOf(@TypeOf(addr)); 205 | 206 | const handle = try self.call(posix.accept_, .{ 207 | self.handle.inner, 208 | &addr, 209 | &addr_len, 210 | os.SOCK.NONBLOCK | os.SOCK.CLOEXEC, 211 | }, .{ .read = true }); 212 | 213 | return Connection{ 214 | .socket = Socket{ .handle = .{ .inner = handle, .wake_fn = wake } }, 215 | .address = net.Address.initPosix(@alignCast(4, &addr)), 216 | }; 217 | } 218 | 219 | pub fn connect(self: *Self, address: net.Address) callconv(.Async) !void { 220 | posix.connect_(self.handle.inner, &address.any, address.getOsSockLen()) catch |err| switch (err) { 221 | error.WouldBlock => try self.writers.wait(.{ .use_lifo = true }), 222 | else => return err, 223 | }; 224 | 225 | try self.get(.socket_error); 226 | } 227 | 228 | pub inline fn reader(self: *Self) Reader { 229 | return Reader{ .context = self }; 230 | } 231 | 232 | pub inline fn writer(self: *Self) Writer { 233 | return Writer{ .context = self }; 234 | } 235 | 236 | pub fn read(self: *Self, buf: []u8) !usize { 237 | const num_bytes = self.call(posix.read_, .{ self.handle.inner, buf }, .{ .read = true }) catch |err| switch (err) { 238 | error.NotOpenForReading, 239 | error.ConnectionResetByPeer, 240 | error.OperationCancelled, 241 | => return 0, 242 | else => return err, 243 | }; 244 | 245 | return num_bytes; 246 | } 247 | 248 | pub fn recv(self: *Self, buf: []u8, flags: u32) callconv(.Async) !usize { 249 | return self.recvFrom(buf, flags, null); 250 | } 251 | 252 | pub fn recvFrom(self: *Self, buf: []u8, flags: u32, address: ?*net.Address) callconv(.Async) !usize { 253 | var src_addr: os.sockaddr = undefined; 254 | var src_addr_len: os.socklen_t = undefined; 255 | 256 | const num_bytes = try self.call(os.recvfrom, .{ 257 | self.handle.inner, 258 | buf, 259 | flags, 260 | @as(?*os.sockaddr, if (address != null) &src_addr else null), 261 | @as(?*os.socklen_t, if (address != null) &src_addr_len else null), 262 | }, .{ .read = true }); 263 | 264 | if (address) |a| { 265 | a.* = net.Address{ .any = src_addr }; 266 | } 267 | 268 | return num_bytes; 269 | } 270 | 271 | pub fn write(self: *Self, buf: []const u8) !usize { 272 | return self.call(os.write, .{ self.handle.inner, buf }, .{ .write = true }); 273 | } 274 | 275 | pub fn send(self: *Self, buf: []const u8, flags: u32) callconv(.Async) !usize { 276 | return self.sendTo(buf, flags, null); 277 | } 278 | 279 | pub fn sendTo(self: *Self, buf: []const u8, flags: u32, address: ?net.Address) callconv(.Async) !usize { 280 | return self.call(posix.sendto_, .{ 281 | self.handle.inner, 282 | buf, 283 | flags, 284 | @as(?*const os.sockaddr, if (address) |a| &a.any else null), 285 | if (address) |a| a.getOsSockLen() else 0, 286 | }, .{ .write = true }); 287 | } 288 | }; 289 | -------------------------------------------------------------------------------- /socket_windows.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | const windows = @import("os/windows.zig"); 4 | const ws2_32 = @import("os/windows/ws2_32.zig"); 5 | 6 | const io = std.io; 7 | const os = std.os; 8 | const net = std.net; 9 | const mem = std.mem; 10 | const meta = std.meta; 11 | 12 | var OVERLAPPED = windows.OVERLAPPED{ .Internal = 0, .InternalHigh = 0, .Offset = 0, .OffsetHigh = 0, .hEvent = null }; 13 | var OVERLAPPED_PARAM = &OVERLAPPED; 14 | 15 | pub const SocketOptionType = enum(u32) { 16 | debug = os.SO_DEBUG, 17 | listen = os.SO_ACCEPTCONN, 18 | reuse_address = os.SO_REUSEADDR, 19 | keep_alive = os.SO_KEEPALIVE, 20 | dont_route = os.SO_DONTROUTE, 21 | broadcast = os.SO_BROADCAST, 22 | linger = os.SO_LINGER, 23 | oob_inline = os.SO_OOBINLINE, 24 | 25 | send_buffer_max_size = os.SO_SNDBUF, 26 | recv_buffer_max_size = os.SO_RCVBUF, 27 | 28 | send_buffer_min_size = os.SO_SNDLOWAT, 29 | recv_buffer_min_size = os.SO_RCVLOWAT, 30 | 31 | send_timeout = os.SO_SNDTIMEO, 32 | recv_timeout = os.SO_RCVTIMEO, 33 | 34 | socket_error = os.SO_ERROR, 35 | socket_type = os.SO_TYPE, 36 | 37 | protocol_info_a = ws2_32.SO_PROTOCOL_INFOA, 38 | protocol_info_w = ws2_32.SO_PROTOCOL_INFOW, 39 | 40 | update_connect_context = ws2_32.SO_UPDATE_CONNECT_CONTEXT, 41 | update_accept_context = ws2_32.SO_UPDATE_ACCEPT_CONTEXT, 42 | }; 43 | 44 | pub const SocketOption = union(SocketOptionType) { 45 | debug: bool, 46 | listen: bool, 47 | reuse_address: bool, 48 | keep_alive: bool, 49 | dont_route: bool, 50 | broadcast: bool, 51 | linger: ws2_32.LINGER, 52 | oob_inline: bool, 53 | 54 | send_buffer_max_size: u32, 55 | recv_buffer_max_size: u32, 56 | 57 | send_buffer_min_size: u32, 58 | recv_buffer_min_size: u32, 59 | 60 | send_timeout: u32, // Timeout specified in milliseconds. 61 | recv_timeout: u32, // Timeout specified in milliseconds. 62 | 63 | socket_error: void, 64 | socket_type: u32, 65 | 66 | protocol_info_a: ws2_32.WSAPROTOCOL_INFOA, 67 | protocol_info_w: ws2_32.WSAPROTOCOL_INFOW, 68 | 69 | update_connect_context: ?ws2_32.SOCKET, 70 | update_accept_context: ?ws2_32.SOCKET, 71 | }; 72 | 73 | pub const Connection = struct { 74 | socket: Socket, 75 | address: net.Address, 76 | }; 77 | 78 | pub const Socket = struct { 79 | pub const Reader = io.Reader(*Self, anyerror, read); 80 | pub const Writer = io.Writer(*Self, anyerror, write); 81 | 82 | const Self = @This(); 83 | 84 | handle: pike.Handle, 85 | 86 | pub fn init(domain: i32, socket_type: i32, protocol: i32, flags: windows.DWORD) !Self { 87 | return Self{ 88 | .handle = .{ 89 | .inner = try windows.WSASocketW( 90 | domain, 91 | socket_type, 92 | protocol, 93 | null, 94 | 0, 95 | flags | ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT, 96 | ), 97 | }, 98 | }; 99 | } 100 | 101 | pub fn deinit(self: *const Self) void { 102 | self.shutdown(ws2_32.SD_BOTH) catch {}; 103 | windows.closesocket(@ptrCast(ws2_32.SOCKET, self.handle.inner)) catch {}; 104 | } 105 | 106 | pub fn registerTo(self: *const Self, notifier: *const pike.Notifier) !void { 107 | try notifier.register(&self.handle, .{ .read = true, .write = true }); 108 | } 109 | 110 | fn ErrorUnionOf(comptime func: anytype) std.builtin.TypeInfo.ErrorUnion { 111 | return @typeInfo(@typeInfo(@TypeOf(func)).Fn.return_type.?).ErrorUnion; 112 | } 113 | 114 | fn call(_: *Self, comptime function: anytype, raw_args: anytype, comptime _: pike.CallOptions) callconv(.Async) (ErrorUnionOf(function).error_set || error{OperationCancelled})!pike.Overlapped { 115 | var overlapped = pike.Overlapped.init(pike.Task.init(@frame())); 116 | var args = raw_args; 117 | 118 | comptime var i = 0; 119 | inline while (i < args.len) : (i += 1) { 120 | if (comptime @TypeOf(args[i]) == *windows.OVERLAPPED) { 121 | args[i] = &overlapped.inner; 122 | } 123 | } 124 | 125 | var err: ?ErrorUnionOf(function).error_set = null; 126 | 127 | suspend { 128 | var would_block = false; 129 | 130 | if (@call(.{ .modifier = .always_inline }, function, args)) |_| {} else |call_err| switch (call_err) { 131 | error.WouldBlock => would_block = true, 132 | else => err = call_err, 133 | } 134 | 135 | if (!would_block) pike.dispatch(&overlapped.task, .{ .use_lifo = true }); 136 | } 137 | 138 | if (err) |call_err| return call_err; 139 | 140 | return overlapped; 141 | } 142 | 143 | pub fn shutdown(self: *const Self, how: c_int) !void { 144 | try windows.shutdown(@ptrCast(ws2_32.SOCKET, self.handle.inner), how); 145 | } 146 | 147 | pub fn get(self: *const Self, comptime opt: SocketOptionType) !meta.TagPayload(SocketOption, opt) { 148 | if (opt == .socket_error) { 149 | const errno = try windows.getsockopt(u32, @ptrCast(ws2_32.SOCKET, self.handle.inner), os.SOL.SOCKET, @enumToInt(opt)); 150 | if (errno != 0) { 151 | return switch (@intToEnum(ws2_32.WinsockError, @truncate(u16, errno))) { 152 | .WSAEACCES => error.PermissionDenied, 153 | .WSAEADDRINUSE => error.AddressInUse, 154 | .WSAEADDRNOTAVAIL => error.AddressNotAvailable, 155 | .WSAEAFNOSUPPORT => error.AddressFamilyNotSupported, 156 | .WSAEALREADY => error.AlreadyConnecting, 157 | .WSAEBADF => error.BadFileDescriptor, 158 | .WSAECONNREFUSED => error.ConnectionRefused, 159 | .WSAEFAULT => error.InvalidParameter, 160 | .WSAEISCONN => error.AlreadyConnected, 161 | .WSAENETUNREACH => error.NetworkUnreachable, 162 | .WSAENOTSOCK => error.NotASocket, 163 | .WSAEPROTOTYPE => error.UnsupportedProtocol, 164 | .WSAETIMEDOUT => error.ConnectionTimedOut, 165 | .WSAESHUTDOWN => error.AlreadyShutdown, 166 | else => |err| windows.unexpectedWSAError(err), 167 | }; 168 | } 169 | } else { 170 | return windows.getsockopt( 171 | meta.TagPayload(SocketOption, opt), 172 | @ptrCast(ws2_32.SOCKET, self.handle.inner), 173 | os.SOL.SOCKET, 174 | @enumToInt(opt), 175 | ); 176 | } 177 | } 178 | 179 | pub fn set(self: *const Self, comptime opt: SocketOptionType, val: meta.TagPayload(SocketOption, opt)) !void { 180 | try windows.setsockopt( 181 | @ptrCast(ws2_32.SOCKET, self.handle.inner), 182 | os.SOL.SOCKET, 183 | @enumToInt(opt), 184 | blk: { 185 | if (comptime @typeInfo(@TypeOf(val)) == .Optional) { 186 | break :blk if (val) |v| @as([]const u8, std.mem.asBytes(&v)[0..@sizeOf(@TypeOf(val))]) else null; 187 | } else { 188 | break :blk @as([]const u8, std.mem.asBytes(&val)[0..@sizeOf(@TypeOf(val))]); 189 | } 190 | }, 191 | ); 192 | } 193 | 194 | pub fn getBindAddress(self: *const Self) !net.Address { 195 | var addr: os.sockaddr = undefined; 196 | var addr_len: os.socklen_t = @sizeOf(@TypeOf(addr)); 197 | try os.getsockname(@ptrCast(ws2_32.SOCKET, self.handle.inner), &addr, &addr_len); 198 | return net.Address.initPosix(@alignCast(4, &addr)); 199 | } 200 | 201 | pub fn bind(self: *const Self, address: net.Address) !void { 202 | try windows.bind_(@ptrCast(ws2_32.SOCKET, self.handle.inner), &address.any, address.getOsSockLen()); 203 | } 204 | 205 | pub fn listen(self: *const Self, backlog: usize) !void { 206 | try windows.listen_(@ptrCast(ws2_32.SOCKET, self.handle.inner), backlog); 207 | } 208 | 209 | pub fn accept(self: *Self) callconv(.Async) !Connection { 210 | const info = try self.get(.protocol_info_w); 211 | 212 | var incoming = try Self.init( 213 | info.iAddressFamily, 214 | info.iSocketType, 215 | info.iProtocol, 216 | 0, 217 | ); 218 | errdefer incoming.deinit(); 219 | 220 | var buf: [2 * @sizeOf(ws2_32.sockaddr_storage) + 32]u8 = undefined; 221 | var num_bytes: windows.DWORD = undefined; 222 | 223 | _ = try self.call(windows.AcceptEx, .{ 224 | @ptrCast(ws2_32.SOCKET, self.handle.inner), 225 | @ptrCast(ws2_32.SOCKET, incoming.handle.inner), 226 | &buf, 227 | @sizeOf(ws2_32.sockaddr_storage), 228 | @sizeOf(ws2_32.sockaddr_storage), 229 | &num_bytes, 230 | OVERLAPPED_PARAM, 231 | }, .{}); 232 | 233 | var local_addr: *ws2_32.sockaddr = undefined; 234 | var remote_addr: *ws2_32.sockaddr = undefined; 235 | 236 | windows.GetAcceptExSockaddrs( 237 | @ptrCast(ws2_32.SOCKET, self.handle.inner), 238 | &buf, 239 | @as(c_int, @sizeOf(ws2_32.sockaddr_storage)), 240 | @as(c_int, @sizeOf(ws2_32.sockaddr_storage)), 241 | &local_addr, 242 | &remote_addr, 243 | ) catch |err| switch (err) { 244 | error.FileDescriptorNotASocket => return error.SocketNotListening, 245 | else => return err, 246 | }; 247 | 248 | try incoming.set(.update_accept_context, @ptrCast(ws2_32.SOCKET, self.handle.inner)); 249 | 250 | return Connection{ 251 | .socket = incoming, 252 | .address = net.Address.initPosix(@alignCast(4, remote_addr)), 253 | }; 254 | } 255 | 256 | pub fn connect(self: *Self, address: net.Address) callconv(.Async) !void { 257 | try self.bind(net.Address.initIp4(.{ 0, 0, 0, 0 }, 0)); 258 | 259 | _ = try self.call(windows.ConnectEx, .{ 260 | @ptrCast(ws2_32.SOCKET, self.handle.inner), 261 | &address.any, 262 | address.getOsSockLen(), 263 | OVERLAPPED_PARAM, 264 | }, .{}); 265 | 266 | try self.get(.socket_error); 267 | try self.set(.update_connect_context, null); 268 | } 269 | 270 | pub inline fn reader(self: *Self) Reader { 271 | return Reader{ .context = self }; 272 | } 273 | 274 | pub inline fn writer(self: *Self) Writer { 275 | return Writer{ .context = self }; 276 | } 277 | 278 | pub fn read(self: *Self, buf: []u8) !usize { 279 | const overlapped = self.call(windows.ReadFile_, .{ 280 | self.handle.inner, buf, OVERLAPPED_PARAM, 281 | }, .{}) catch |err| switch (err) { 282 | error.EndOfFile => return 0, 283 | else => return err, 284 | }; 285 | 286 | return overlapped.inner.InternalHigh; 287 | } 288 | 289 | pub fn recv(self: *Self, buf: []u8, flags: u32) callconv(.Async) !usize { 290 | const overlapped = self.call(windows.WSARecv, .{ 291 | @ptrCast(ws2_32.SOCKET, self.handle.inner), buf, flags, OVERLAPPED_PARAM, 292 | }, .{}) catch |err| switch (err) { 293 | error.ConnectionAborted, 294 | error.ConnectionResetByPeer, 295 | error.ConnectionClosedByPeer, 296 | error.NetworkSubsystemFailed, 297 | error.NetworkReset, 298 | => return 0, 299 | else => return err, 300 | }; 301 | 302 | return overlapped.inner.InternalHigh; 303 | } 304 | 305 | pub fn recvFrom(self: *Self, buf: []u8, flags: u32, address: ?*net.Address) callconv(.Async) !usize { 306 | var src_addr: ws2_32.sockaddr = undefined; 307 | var src_addr_len: ws2_32.socklen_t = undefined; 308 | 309 | const overlapped = self.call(windows.WSARecvFrom, .{ 310 | @ptrCast(ws2_32.SOCKET, self.handle.inner), 311 | buf, 312 | flags, 313 | @as(?*ws2_32.sockaddr, if (address != null) &src_addr else null), 314 | @as(?*ws2_32.socklen_t, if (address != null) &src_addr_len else null), 315 | OVERLAPPED_PARAM, 316 | }, .{}) catch |err| switch (err) { 317 | error.ConnectionAborted, 318 | error.ConnectionResetByPeer, 319 | error.ConnectionClosedByPeer, 320 | error.NetworkSubsystemFailed, 321 | error.NetworkReset, 322 | => return 0, 323 | else => return err, 324 | }; 325 | 326 | if (address) |a| { 327 | a.* = net.Address{ .any = src_addr }; 328 | } 329 | 330 | return overlapped.inner.InternalHigh; 331 | } 332 | 333 | pub fn write(self: *Self, buf: []const u8) !usize { 334 | const overlapped = self.call(windows.WriteFile_, .{ 335 | self.handle.inner, buf, OVERLAPPED_PARAM, 336 | }, .{}) catch |err| switch (err) { 337 | error.EndOfFile => return 0, 338 | else => return err, 339 | }; 340 | 341 | return overlapped.inner.InternalHigh; 342 | } 343 | 344 | pub fn send(self: *Self, buf: []const u8, flags: u32) callconv(.Async) !usize { 345 | const overlapped = self.call(windows.WSASend, .{ 346 | @ptrCast(ws2_32.SOCKET, self.handle.inner), buf, flags, OVERLAPPED_PARAM, 347 | }, .{}) catch |err| switch (err) { 348 | error.ConnectionAborted, 349 | error.ConnectionResetByPeer, 350 | error.NetworkSubsystemFailed, 351 | error.NetworkReset, 352 | => return 0, 353 | else => return err, 354 | }; 355 | 356 | return overlapped.inner.InternalHigh; 357 | } 358 | 359 | pub fn sendTo(self: *Self, buf: []const u8, flags: u32, address: ?net.Address) callconv(.Async) !usize { 360 | const overlapped = self.call(windows.WSASendTo, .{ 361 | @ptrCast(ws2_32.SOCKET, self.handle.inner), 362 | buf, 363 | flags, 364 | @as(?*const ws2_32.sockaddr, if (address) |a| &a.any else null), 365 | if (address) |a| a.getOsSockLen() else 0, 366 | OVERLAPPED_PARAM, 367 | }, .{}) catch |err| switch (err) { 368 | error.ConnectionAborted, 369 | error.ConnectionResetByPeer, 370 | error.ConnectionClosedByPeer, 371 | error.NetworkSubsystemFailed, 372 | error.NetworkReset, 373 | => return 0, 374 | else => return err, 375 | }; 376 | 377 | return overlapped.inner.InternalHigh; 378 | } 379 | }; 380 | -------------------------------------------------------------------------------- /waker.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pike = @import("pike.zig"); 3 | 4 | const mem = std.mem; 5 | const meta = std.meta; 6 | const math = std.math; 7 | const testing = std.testing; 8 | 9 | const assert = std.debug.assert; 10 | 11 | pub const Waker = struct { 12 | const EMPTY = 0; 13 | const NOTIFIED: usize = 1; 14 | const SHUTDOWN: usize = 2; 15 | 16 | state: usize = EMPTY, 17 | 18 | pub const Node = struct { 19 | dead: bool = false, 20 | task: pike.Task, 21 | }; 22 | 23 | pub fn wait(self: *Waker, args: anytype) !void { 24 | var node: Node = .{ .task = pike.Task.init(@frame()) }; 25 | 26 | suspend { 27 | var state = @atomicLoad(usize, &self.state, .Monotonic); 28 | 29 | while (true) { 30 | const new_state = switch (state) { 31 | EMPTY => @ptrToInt(&node), 32 | NOTIFIED => EMPTY, 33 | SHUTDOWN => { 34 | node.dead = true; 35 | pike.dispatch(&node.task, .{ .use_lifo = true }); 36 | break; 37 | }, 38 | else => unreachable, 39 | }; 40 | 41 | state = @cmpxchgWeak( 42 | usize, 43 | &self.state, 44 | state, 45 | new_state, 46 | .Release, 47 | .Monotonic, 48 | ) orelse { 49 | if (new_state == EMPTY) pike.dispatch(&node.task, args); 50 | break; 51 | }; 52 | } 53 | } 54 | 55 | if (node.dead) return error.OperationCancelled; 56 | } 57 | 58 | pub fn notify(self: *Waker) ?*pike.Task { 59 | var state = @atomicLoad(usize, &self.state, .Monotonic); 60 | 61 | while (true) { 62 | const new_state = switch (state) { 63 | EMPTY => NOTIFIED, 64 | NOTIFIED, SHUTDOWN => return null, 65 | else => EMPTY, 66 | }; 67 | 68 | state = @cmpxchgWeak(usize, &self.state, state, new_state, .Acquire, .Monotonic) orelse { 69 | if (new_state == NOTIFIED) return null; 70 | const node = @intToPtr(*Node, state); 71 | return &node.task; 72 | }; 73 | } 74 | } 75 | 76 | pub fn shutdown(self: *Waker) ?*pike.Task { 77 | return switch (@atomicRmw(usize, &self.state, .Xchg, SHUTDOWN, .AcqRel)) { 78 | EMPTY, NOTIFIED, SHUTDOWN => null, 79 | else => |state| { 80 | const node = @intToPtr(*Node, state); 81 | node.dead = true; 82 | return &node.task; 83 | }, 84 | }; 85 | } 86 | }; 87 | 88 | test "Waker.wait() / Waker.notify() / Waker.shutdown()" { 89 | var waker: Waker = .{}; 90 | 91 | { 92 | var frame = async waker.wait(.{}); 93 | pike.dispatch(waker.notify().?, .{}); 94 | try nosuspend await frame; 95 | } 96 | 97 | { 98 | testing.expect(waker.shutdown() == null); 99 | testing.expectError(error.OperationCancelled, nosuspend waker.wait(.{})); 100 | } 101 | } 102 | 103 | pub fn PackedWaker(comptime Frame: type, comptime Set: type) type { 104 | const set_fields = meta.fields(Set); 105 | const set_count = set_fields.len; 106 | 107 | return struct { 108 | pub const FrameList = PackedList(Frame, Set); 109 | pub const FrameNode = FrameList.Node; 110 | const Self = @This(); 111 | 112 | ready: [set_count]bool = [_]bool{false} ** set_count, 113 | heads: [set_count]?*FrameNode = [_]?*FrameNode{null} ** set_count, 114 | 115 | pub fn wait(self: *Self, set: Set) bool { 116 | var any_ready = false; 117 | inline for (set_fields) |field, field_index| { 118 | if (@field(set, field.name) and self.ready[field_index]) { 119 | if (self.ready[field_index]) { 120 | self.ready[field_index] = false; 121 | any_ready = true; 122 | } 123 | } 124 | } 125 | 126 | return any_ready; 127 | } 128 | 129 | pub fn wake(self: *Self, set: Set) ?*FrameList.Node { 130 | return FrameList.pop(&self.heads, set) orelse blk: { 131 | inline for (set_fields) |field, field_index| { 132 | if (@field(set, field.name) and self.heads[field_index] == null) { 133 | self.ready[field_index] = true; 134 | } 135 | } 136 | 137 | break :blk null; 138 | }; 139 | } 140 | 141 | pub fn next(self: *Self, set: Set) ?*FrameList.Node { 142 | inline for (set_fields) |field, field_index| { 143 | if (@field(set, field.name) and self.heads[field_index] == null) { 144 | return null; 145 | } 146 | } 147 | 148 | return FrameList.pop(&self.heads, set); 149 | } 150 | }; 151 | } 152 | 153 | test "PackedWaker.wake() / PackedWaker.wait()" { 154 | const Set = struct { 155 | a: bool = false, 156 | b: bool = false, 157 | c: bool = false, 158 | d: bool = false, 159 | }; 160 | 161 | const Scope = struct { 162 | inner: anyframe, 163 | }; 164 | 165 | const Test = struct { 166 | fn do(waker: *PackedWaker(*Scope, Set), set: Set, completed: *bool) callconv(.Async) void { 167 | defer completed.* = true; 168 | 169 | if (waker.wait(set)) return; 170 | 171 | suspend { 172 | var scope = Scope{ .inner = @frame() }; 173 | var node = meta.Child(@TypeOf(waker)).FrameNode{ .data = &scope }; 174 | meta.Child(@TypeOf(waker)).FrameList.append(&waker.heads, set, &node); 175 | } 176 | } 177 | }; 178 | 179 | var waker: PackedWaker(*Scope, Set) = .{}; 180 | 181 | testing.expect(waker.wake(.{ .a = true, .b = true, .c = true, .d = true }) == null); 182 | testing.expect(mem.allEqual(bool, &waker.ready, true)); 183 | 184 | testing.expect(waker.wait(.{ .a = true, .b = true, .c = true, .d = true })); 185 | testing.expect(mem.allEqual(bool, &waker.ready, false)); 186 | 187 | var A_done = false; 188 | var B_done = false; 189 | var C_done = false; 190 | var D_done = false; 191 | 192 | var A = async Test.do(&waker, .{ .a = true, .c = true }, &A_done); 193 | var B = async Test.do(&waker, .{ .a = true, .b = true, .c = true }, &B_done); 194 | var C = async Test.do(&waker, .{ .a = true, .b = true, .d = true }, &C_done); 195 | var D = async Test.do(&waker, .{ .d = true }, &D_done); 196 | 197 | resume waker.wake(.{ .b = true }).?.data.inner; 198 | nosuspend await B; 199 | testing.expect(B_done); 200 | 201 | resume waker.wake(.{ .b = true }).?.data.inner; 202 | nosuspend await C; 203 | testing.expect(C_done); 204 | 205 | resume waker.wake(.{ .a = true }).?.data.inner; 206 | nosuspend await A; 207 | testing.expect(A_done); 208 | 209 | resume waker.wake(.{ .d = true }).?.data.inner; 210 | nosuspend await D; 211 | testing.expect(D_done); 212 | } 213 | 214 | fn PackedList(comptime T: type, comptime U: type) type { 215 | const set_fields = meta.fields(U); 216 | const set_count = set_fields.len; 217 | 218 | return struct { 219 | const Self = @This(); 220 | 221 | pub const Node = struct { 222 | data: T, 223 | next: [set_count]?*Self.Node = [_]?*Node{null} ** set_count, 224 | prev: [set_count]?*Self.Node = [_]?*Node{null} ** set_count, 225 | }; 226 | 227 | pub fn append(heads: *[set_count]?*Self.Node, set: U, node: *Self.Node) void { 228 | assert(mem.allEqual(?*Self.Node, &node.prev, null)); 229 | assert(mem.allEqual(?*Self.Node, &node.next, null)); 230 | 231 | inline for (set_fields) |field, i| { 232 | if (@field(set, field.name)) { 233 | if (heads[i]) |head| { 234 | const tail = head.prev[i] orelse unreachable; 235 | 236 | node.prev[i] = tail; 237 | tail.next[i] = node; 238 | 239 | head.prev[i] = node; 240 | } else { 241 | node.prev[i] = node; 242 | heads[i] = node; 243 | } 244 | } 245 | } 246 | } 247 | 248 | pub fn prepend(heads: *[set_count]?*Self.Node, set: U, node: *Self.Node) void { 249 | assert(mem.allEqual(?*Self.Node, &node.prev, null)); 250 | assert(mem.allEqual(?*Self.Node, &node.next, null)); 251 | 252 | inline for (set_fields) |field, i| { 253 | if (@field(set, field.name)) { 254 | if (heads[i]) |head| { 255 | node.prev[i] = head; 256 | node.next[i] = head; 257 | 258 | head.prev[i] = node; 259 | heads[i] = node; 260 | } else { 261 | node.prev[i] = node; 262 | heads[i] = node; 263 | } 264 | } 265 | } 266 | } 267 | 268 | pub fn pop(heads: *[set_count]?*Self.Node, set: U) ?*Self.Node { 269 | inline for (set_fields) |field, field_index| { 270 | if (@field(set, field.name) and heads[field_index] != null) { 271 | const head = heads[field_index] orelse unreachable; 272 | 273 | comptime var j = 0; 274 | inline while (j < set_count) : (j += 1) { 275 | if (head.prev[j]) |prev| prev.next[j] = if (prev != head.next[j]) head.next[j] else null; 276 | if (head.next[j]) |next| next.prev[j] = head.prev[j]; 277 | if (heads[j] == head) heads[j] = head.next[j]; 278 | } 279 | 280 | return head; 281 | } 282 | } 283 | 284 | return null; 285 | } 286 | }; 287 | } 288 | 289 | test "PackedList.append() / PackedList.prepend() / PackedList.pop()" { 290 | const Set = struct { 291 | a: bool = false, 292 | b: bool = false, 293 | c: bool = false, 294 | d: bool = false, 295 | }; 296 | 297 | const U8List = PackedList(u8, Set); 298 | const Node = U8List.Node; 299 | 300 | var heads: [@sizeOf(Set)]?*Node = [_]?*Node{null} ** @sizeOf(Set); 301 | 302 | var A = Node{ .data = 'A' }; 303 | var B = Node{ .data = 'B' }; 304 | var C = Node{ .data = 'C' }; 305 | var D = Node{ .data = 'D' }; 306 | 307 | U8List.append(&heads, .{ .a = true, .c = true }, &A); 308 | U8List.append(&heads, .{ .a = true, .b = true, .c = true }, &B); 309 | U8List.prepend(&heads, .{ .a = true, .b = true, .d = true }, &C); 310 | U8List.append(&heads, .{ .d = true }, &D); 311 | 312 | testing.expect(U8List.pop(&heads, .{ .b = true }).?.data == C.data); 313 | testing.expect(U8List.pop(&heads, .{ .b = true }).?.data == B.data); 314 | testing.expect(U8List.pop(&heads, .{ .a = true }).?.data == A.data); 315 | testing.expect(U8List.pop(&heads, .{ .d = true }).?.data == D.data); 316 | 317 | testing.expect(mem.allEqual(?*Node, &heads, null)); 318 | } 319 | 320 | test "PackedList.append() / PackedList.prepend() / PackedList.pop()" { 321 | const Set = struct { 322 | a: bool = false, 323 | b: bool = false, 324 | c: bool = false, 325 | d: bool = false, 326 | }; 327 | 328 | const U8List = PackedList(u8, Set); 329 | const Node = U8List.Node; 330 | 331 | var heads: [@sizeOf(Set)]?*Node = [_]?*Node{null} ** @sizeOf(Set); 332 | 333 | var A = Node{ .data = 'A' }; 334 | var B = Node{ .data = 'B' }; 335 | 336 | U8List.append(&heads, .{ .a = true, .b = true }, &A); 337 | testing.expect(heads[0] == &A and heads[0].?.prev[0] == &A and heads[0].?.next[0] == null); 338 | testing.expect(heads[1] == &A and heads[1].?.prev[1] == &A and heads[1].?.next[1] == null); 339 | testing.expect(heads[2] == null); 340 | testing.expect(heads[3] == null); 341 | 342 | U8List.prepend(&heads, .{ .a = true, .c = true }, &B); 343 | testing.expect(heads[0] == &B and heads[0].?.prev[0] == &A and heads[0].?.prev[0].?.next[0] == null); 344 | testing.expect(heads[1] == &A and heads[1].?.prev[1] == &A and heads[1].?.prev[1].?.next[1] == null); 345 | testing.expect(heads[2] == &B and heads[2].?.prev[2] == &B and heads[2].?.prev[2].?.next[2] == null); 346 | testing.expect(heads[3] == null); 347 | 348 | testing.expect(U8List.pop(&heads, .{ .a = true }).?.data == B.data); 349 | testing.expect(heads[0] == &A); 350 | testing.expect(heads[0].?.prev[0] == &A); 351 | testing.expect(mem.allEqual(?*Node, &heads[0].?.next, null)); 352 | 353 | testing.expect(U8List.pop(&heads, .{ .a = true }).?.data == A.data); 354 | testing.expect(mem.allEqual(?*Node, &heads, null)); 355 | } 356 | -------------------------------------------------------------------------------- /zig.mod: -------------------------------------------------------------------------------- 1 | id: ecqhfxxtbno2n70o29kui7dcxv53c242kl2nxam3kcroi5cl 2 | name: pike 3 | main: pike.zig --------------------------------------------------------------------------------