Hi,
This is my first time using io_uring, and I’ve been learning Zig for past few days. I’m trying to create a TCP server as a little pet project (and to get more familiar with Zig + Systems Programming). However, I’ve been stumped by io_uring.
const std = @import("std");
const posix = std.posix;
const request = @import("request.zig");
const linux = std.os.linux;
const Response = @import("response.zig").Response;
const Address = std.net.Address;
const EventType = enum(u8) { ACCEPT, READ, WRITE };
const Client = struct {
allocator: *std.mem.Allocator,
client_socket: posix.socket_t,
buffer: []u8,
fn init(allocator: *std.mem.Allocator, socket: posix.socket_t) !Client {
const buffer = try allocator.alloc(u8, 4096);
@memset(buffer, 0); //done to make buffer readable
return .{
.allocator = allocator,
.client_socket = socket,
.buffer = buffer,
};
}
fn deinit(self: *Client, allocator: *std.mem.Allocator) void {
allocator.free(self.buffer);
allocator.destroy(self);
}
};
const Event = struct {
ptr: ?*Client,
event_type: EventType,
fn init(ptr: ?*Client, event_type: EventType) Event {
return .{ .ptr = ptr, .event_type = event_type };
}
};
fn addAcceptRequest(ring: *linux.IoUring, listener: posix.socket_t, allocator: *std.mem.Allocator) !void {
const event = try allocator.create(Event);
event.* = Event.init(null, .ACCEPT);
var address: posix.sockaddr = undefined;
var address_len: posix.socklen_t = @sizeOf(posix.sockaddr);
const user_data: usize = @intFromPtr(event);
std.debug.print("ACCEPT - user_data: {any}\n", .{user_data});
_ = try ring.accept(user_data, listener, &address, &address_len, 0);
const num_submitted = try ring.submit();
std.debug.print("ACCEPT - num_submitted: {any}\n", .{num_submitted});
}
fn addReadRequest(ring: *linux.IoUring, client_socket: posix.socket_t, allocator: *std.mem.Allocator) !void {
const client = try allocator.create(Client);
client.* = try Client.init(allocator, client_socket);
const event = try allocator.create(Event);
event.* = Event.init(client, .READ);
const read_buffer = linux.IoUring.ReadBuffer{ .buffer = client.buffer[0..] };
const user_data: usize = @intFromPtr(event);
_ = try ring.read(user_data, client.client_socket, read_buffer, 0);
const num_submitted = try ring.submit();
std.debug.print("READ - num_submitted: {any}\n", .{num_submitted});
}
fn handleRequest(client: *Client) void {
_ = request.parse_request(client.buffer) catch |err| {
std.log.err("Invalid request when parsing: {any}", .{err});
return;
};
std.debug.print("\nClient Request\n{s}\n\n", .{client.buffer}); //view contents of buffer after reading/parsing is done
}
pub fn main() !void {
const host = [4]u8{ 127, 0, 0, 1 };
const port = 3000;
const addr = Address.initIp4(host, port);
{
var buffer: [64]u8 = undefined;
const stderr = std.debug.lockStderrWriter(&buffer);
defer std.debug.unlockStderrWriter();
try stderr.print("Listening at http://", .{});
try addr.in.format(stderr);
try stderr.print("\n", .{});
try stderr.flush();
}
const sock_fd = try posix.socket(addr.any.family, posix.SOCK.STREAM | posix.SOCK.NONBLOCK, posix.IPPROTO.TCP);
try posix.setsockopt(sock_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
try posix.bind(sock_fd, &addr.any, addr.getOsSockLen());
try posix.listen(sock_fd, 1024);
var ring = try linux.IoUring.init(256, 0);
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var allocator = gpa.allocator();
try addAcceptRequest(&ring, sock_fd, &allocator);
while (true) {
//TODO: add threading here
var cqe = try ring.copy_cqe();
std.debug.print("cqe: {any}\n", .{cqe});
std.debug.print("user_data: {any}\n", .{cqe.user_data});
const event: *Event = @ptrFromInt(cqe.user_data);
std.debug.print("event: {any}\n", .{event.*});
switch (event.event_type) {
.ACCEPT => {
try addAcceptRequest(&ring, sock_fd, &allocator);
try addReadRequest(&ring, cqe.res, &allocator);
},
.READ => {
std.debug.print("HELLO!!!", .{});
handleRequest(event.ptr.?);
},
.WRITE => {},
}
ring.cqe_seen(&cqe);
}
}
Here’s my current code, in its entirety. When running it, I run into the following output:
Listening at http://127.0.0.1:3000
ACCEPT - user_data: 133904333537280
ACCEPT - num_submitted: 1
cqe: .{ .user_data = 133904333537280, .res = 5, .flags = 0 }
user_data: 133904333537280
event: .{ .ptr = null, .event_type = .ACCEPT }
ACCEPT - user_data: 133904333537296
ACCEPT - num_submitted: 1
READ - num_submitted: 1
cqe: .{ .user_data = 133904333537296, .res = 6, .flags = 0 }
user_data: 133904333537296
event: .{ .ptr = null, .event_type = .ACCEPT }
ACCEPT - user_data: 133904333537328
ACCEPT - num_submitted: 1
READ - num_submitted: 1
cqe: .{ .user_data = 0, .res = 0, .flags = 0 }
user_data: 0
thread 195698 panic: cast causes pointer to be null
/home/w/projects/tcp-server/src/main.zig:162:31: 0x11449b3 in main (main.zig)
const event: *Event = @ptrFromInt(cqe.user_data);
I’ve been heavily following along with this code of a similar implementation, but in C (I learned C in university, but am not a massive fan).
Any help, advice, or resources would be greatly appreciated!