feat: use epoll event loop
This commit is contained in:
parent
d54c3149d3
commit
7e054722db
3 changed files with 183 additions and 59 deletions
27
src/State.zig
Normal file
27
src/State.zig
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Mapping = struct {
|
||||||
|
mapped: []const u8,
|
||||||
|
doc: ?[]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MapData = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
mappings: std.StringHashMap(Mapping),
|
||||||
|
renames: std.StringHashMap([]const u8),
|
||||||
|
};
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
lock: std.Thread.RwLock,
|
||||||
|
mdata: ?MapData = null,
|
||||||
|
|
||||||
|
const State = @This();
|
||||||
|
|
||||||
|
pub fn deinit(self: State) void {
|
||||||
|
var selfv = self;
|
||||||
|
if (selfv.mdata) |*m| {
|
||||||
|
m.arena.deinit();
|
||||||
|
m.mappings.deinit();
|
||||||
|
m.renames.deinit();
|
||||||
|
}
|
||||||
|
}
|
181
src/main.zig
181
src/main.zig
|
@ -1,17 +1,13 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const csv_reader = @import("csv_reader.zig");
|
const csv_reader = @import("csv_reader.zig");
|
||||||
|
|
||||||
|
const State = @import("State.zig");
|
||||||
const StringPacket = @import("StringPacket.zig");
|
const StringPacket = @import("StringPacket.zig");
|
||||||
|
|
||||||
pub const std_options = std.Options{
|
pub const std_options = std.Options{
|
||||||
.log_level = .debug,
|
.log_level = .debug,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Mapping = struct {
|
|
||||||
mapped: []const u8,
|
|
||||||
doc: ?[]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn getAddr(alloc: std.mem.Allocator) !std.net.Address {
|
fn getAddr(alloc: std.mem.Allocator) !std.net.Address {
|
||||||
const sockpath = try std.fs.path.join(alloc, &.{
|
const sockpath = try std.fs.path.join(alloc, &.{
|
||||||
std.posix.getenv("XDG_RUNTIME_DIR") orelse return error.MissingRuntimeDir,
|
std.posix.getenv("XDG_RUNTIME_DIR") orelse return error.MissingRuntimeDir,
|
||||||
|
@ -46,9 +42,6 @@ fn map() !void {
|
||||||
|
|
||||||
const stdin = std.io.getStdIn().reader();
|
const stdin = std.io.getStdIn().reader();
|
||||||
|
|
||||||
var buf: [128]u8 = undefined;
|
|
||||||
const request = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse return error.NoInput;
|
|
||||||
|
|
||||||
const addr = try getAddr(alloc);
|
const addr = try getAddr(alloc);
|
||||||
|
|
||||||
const sockfd = try std.posix.socket(
|
const sockfd = try std.posix.socket(
|
||||||
|
@ -62,6 +55,9 @@ fn map() !void {
|
||||||
|
|
||||||
const stream = std.net.Stream{ .handle = sockfd };
|
const stream = std.net.Stream{ .handle = sockfd };
|
||||||
|
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
const request = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse return error.NoInput;
|
||||||
|
|
||||||
const pkt = StringPacket{ .str = std.mem.trim(u8, request, &std.ascii.whitespace) };
|
const pkt = StringPacket{ .str = std.mem.trim(u8, request, &std.ascii.whitespace) };
|
||||||
try pkt.write(stream.writer());
|
try pkt.write(stream.writer());
|
||||||
|
|
||||||
|
@ -71,41 +67,38 @@ fn map() !void {
|
||||||
try std.io.getStdOut().writer().print("{s}\n", .{res.str});
|
try std.io.getStdOut().writer().print("{s}\n", .{res.str});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runServer() !void {
|
fn loadData(state: *State) !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var data = State.MapData{
|
||||||
defer _ = gpa.deinit();
|
.arena = std.heap.ArenaAllocator.init(state.alloc),
|
||||||
|
.mappings = std.StringHashMap(State.Mapping).init(state.alloc),
|
||||||
const alloc = gpa.allocator();
|
.renames = std.StringHashMap([]const u8).init(state.alloc),
|
||||||
|
};
|
||||||
var mappings = std.StringHashMap(Mapping).init(alloc);
|
errdefer {
|
||||||
defer mappings.deinit();
|
data.arena.deinit();
|
||||||
|
data.mappings.deinit();
|
||||||
var data_arena = std.heap.ArenaAllocator.init(alloc);
|
data.renames.deinit();
|
||||||
defer data_arena.deinit();
|
}
|
||||||
|
|
||||||
var renames = std.StringHashMap([]const u8).init(alloc);
|
|
||||||
defer renames.deinit();
|
|
||||||
|
|
||||||
if (std.fs.cwd().openFile("renames.csv", .{})) |renames_file| {
|
if (std.fs.cwd().openFile("renames.csv", .{})) |renames_file| {
|
||||||
defer renames_file.close();
|
defer renames_file.close();
|
||||||
|
|
||||||
var reader = csv_reader.csvReader(std.io.bufferedReader(renames_file.reader()));
|
var reader = csv_reader.csvReader(std.io.bufferedReader(renames_file.reader()));
|
||||||
|
|
||||||
while (try reader.next(data_arena.allocator())) |rec| {
|
while (try reader.next(data.arena.allocator())) |rec| {
|
||||||
if (rec.cols.len != 2) {
|
if (rec.cols.len != 2) {
|
||||||
std.log.warn("found rename with invalid record length, skipping", .{});
|
std.log.warn("found rename with invalid record length, skipping", .{});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renames.contains(rec.cols[0])) {
|
if (data.renames.contains(rec.cols[0])) {
|
||||||
std.log.warn("duplicate rename '{s}'", .{rec.cols[0]});
|
std.log.warn("duplicate rename '{s}'", .{rec.cols[0]});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try renames.put(rec.cols[0], rec.cols[1]);
|
try data.renames.put(rec.cols[0], rec.cols[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
std.log.info("loaded {} renames", .{renames.count()});
|
std.log.info("loaded {} renames", .{data.renames.count()});
|
||||||
} else |err| {
|
} else |err| {
|
||||||
std.log.warn("couldn't open renames file: {}, skipping", .{err});
|
std.log.warn("couldn't open renames file: {}, skipping", .{err});
|
||||||
}
|
}
|
||||||
|
@ -116,48 +109,53 @@ fn runServer() !void {
|
||||||
var mappings_iter = mappings_dir.iterate();
|
var mappings_iter = mappings_dir.iterate();
|
||||||
|
|
||||||
while (try mappings_iter.next()) |entry| {
|
while (try mappings_iter.next()) |entry| {
|
||||||
const fpath = try std.fs.path.join(alloc, &.{ "mappings", entry.name });
|
const fpath = try std.fs.path.join(state.alloc, &.{ "mappings", entry.name });
|
||||||
defer alloc.free(fpath);
|
defer state.alloc.free(fpath);
|
||||||
|
|
||||||
var mapfile = try std.fs.cwd().openFile(fpath, .{});
|
var mapfile = try std.fs.cwd().openFile(fpath, .{});
|
||||||
defer mapfile.close();
|
defer mapfile.close();
|
||||||
|
|
||||||
var reader = csv_reader.csvReader(std.io.bufferedReader(mapfile.reader()));
|
var reader = csv_reader.csvReader(std.io.bufferedReader(mapfile.reader()));
|
||||||
|
|
||||||
while (try reader.next(data_arena.allocator())) |rec| {
|
while (try reader.next(data.arena.allocator())) |rec| {
|
||||||
if (rec.cols.len < 2) {
|
if (rec.cols.len < 2) {
|
||||||
std.log.warn("found mapping with invalid length, skipping", .{});
|
std.log.warn("found mapping with invalid length, skipping", .{});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mappings.contains(rec.cols[0])) {
|
if (data.mappings.contains(rec.cols[0])) {
|
||||||
std.log.warn("duplicate mapping '{s}'", .{rec.cols[0]});
|
std.log.warn("duplicate mapping '{s}'", .{rec.cols[0]});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapping = Mapping{
|
const mapping = State.Mapping{
|
||||||
.mapped = rec.cols[1],
|
.mapped = rec.cols[1],
|
||||||
.doc = if (rec.cols.len >= 4) rec.cols[3] else null,
|
.doc = if (rec.cols.len >= 4) rec.cols[3] else null,
|
||||||
};
|
};
|
||||||
|
|
||||||
try mappings.put(rec.cols[0], mapping);
|
try data.mappings.put(rec.cols[0], mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std.log.info("loaded {} mappings", .{mappings.count()});
|
state.lock.lock();
|
||||||
|
defer state.lock.unlock();
|
||||||
|
|
||||||
const addr = try getAddr(alloc);
|
state.mdata = data;
|
||||||
var server = try addr.listen(.{});
|
|
||||||
defer server.deinit();
|
|
||||||
std.log.info("listening on {}", .{addr});
|
|
||||||
|
|
||||||
|
std.log.info("loaded {} mappings", .{data.mappings.count()});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleConnection(state: *State, con: std.net.Server.Connection) !void {
|
||||||
while (true) {
|
while (true) {
|
||||||
const con = try server.accept();
|
const req = try StringPacket.read(con.stream.reader(), state.alloc);
|
||||||
const req = try StringPacket.read(con.stream.reader(), alloc);
|
defer state.alloc.free(req.str);
|
||||||
defer alloc.free(req.str);
|
|
||||||
|
|
||||||
if (mappings.get(req.str)) |mapping| {
|
state.lock.lockShared();
|
||||||
const renamed = renames.get(mapping.mapped);
|
defer state.lock.unlockShared();
|
||||||
|
|
||||||
|
if (state.mdata) |m| {
|
||||||
|
if (m.mappings.get(req.str)) |mapping| {
|
||||||
|
const renamed = m.renames.get(mapping.mapped);
|
||||||
const res = StringPacket{ .str = renamed orelse mapping.mapped };
|
const res = StringPacket{ .str = renamed orelse mapping.mapped };
|
||||||
try res.write(con.stream.writer());
|
try res.write(con.stream.writer());
|
||||||
|
|
||||||
|
@ -178,5 +176,104 @@ fn runServer() !void {
|
||||||
const res = StringPacket{ .str = "<unknown>" };
|
const res = StringPacket{ .str = "<unknown>" };
|
||||||
try res.write(con.stream.writer());
|
try res.write(con.stream.writer());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const res = StringPacket{ .str = "<not loaded>" };
|
||||||
|
try res.write(con.stream.writer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runServer() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
|
var state = State{
|
||||||
|
.alloc = alloc,
|
||||||
|
.lock = .{},
|
||||||
|
};
|
||||||
|
defer state.deinit();
|
||||||
|
|
||||||
|
const sigs = comptime sigset: {
|
||||||
|
var sigset = std.os.linux.empty_sigset;
|
||||||
|
std.os.linux.sigaddset(&sigset, std.posix.SIG.TERM);
|
||||||
|
std.os.linux.sigaddset(&sigset, std.posix.SIG.INT);
|
||||||
|
break :sigset sigset;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.posix.sigprocmask(std.posix.SIG.BLOCK, &sigs, null);
|
||||||
|
|
||||||
|
var pool: std.Thread.Pool = undefined;
|
||||||
|
try std.Thread.Pool.init(&pool, .{
|
||||||
|
.allocator = alloc,
|
||||||
|
.n_jobs = 4, // plenty
|
||||||
|
});
|
||||||
|
defer pool.deinit(); // joins threads
|
||||||
|
|
||||||
|
try pool.spawn((struct {
|
||||||
|
fn f(s: *State) void {
|
||||||
|
loadData(s) catch |e| {
|
||||||
|
std.log.err("failed to load data: {}", .{e});
|
||||||
|
s.mdata = .{
|
||||||
|
.arena = std.heap.ArenaAllocator.init(s.alloc),
|
||||||
|
.mappings = std.StringHashMap(State.Mapping).init(s.alloc),
|
||||||
|
.renames = std.StringHashMap([]const u8).init(s.alloc),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}).f, .{&state});
|
||||||
|
|
||||||
|
const addr = try getAddr(alloc);
|
||||||
|
var server = try addr.listen(.{});
|
||||||
|
defer {
|
||||||
|
server.deinit();
|
||||||
|
const path = std.mem.sliceTo(&addr.un.path, 0);
|
||||||
|
std.fs.cwd().deleteFile(path) catch |e|
|
||||||
|
std.log.warn("failed to delete socket: {}", .{e});
|
||||||
|
}
|
||||||
|
std.log.info("listening on {}", .{addr});
|
||||||
|
|
||||||
|
const sigfd = try std.posix.signalfd(-1, &sigs, 0);
|
||||||
|
defer std.posix.close(sigfd);
|
||||||
|
|
||||||
|
const epfd = try std.posix.epoll_create1(0);
|
||||||
|
defer std.posix.close(epfd);
|
||||||
|
|
||||||
|
for ([_]std.posix.fd_t{ server.stream.handle, sigfd }) |fd| {
|
||||||
|
const ev = std.os.linux.epoll_event{
|
||||||
|
.data = .{ .fd = fd },
|
||||||
|
.events = std.os.linux.EPOLL.IN,
|
||||||
|
};
|
||||||
|
try std.posix.epoll_ctl(epfd, std.os.linux.EPOLL.CTL_ADD, fd, @constCast(&ev));
|
||||||
|
}
|
||||||
|
|
||||||
|
var ev_buf: [32]std.os.linux.epoll_event = undefined;
|
||||||
|
outer: while (true) {
|
||||||
|
const evs = ev_buf[0..std.os.linux.epoll_wait(epfd, &ev_buf, ev_buf.len, -1)];
|
||||||
|
for (evs) |ev| {
|
||||||
|
if (ev.data.fd == server.stream.handle) {
|
||||||
|
const con = try server.accept();
|
||||||
|
errdefer con.stream.close();
|
||||||
|
try pool.spawn((struct {
|
||||||
|
fn f(s: *State, conn: std.net.Server.Connection) void {
|
||||||
|
defer conn.stream.close();
|
||||||
|
handleConnection(s, conn) catch |e| switch (e) {
|
||||||
|
error.EndOfStream => {},
|
||||||
|
else => std.log.warn("handing connection: {}", .{e}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}).f, .{ &state, con });
|
||||||
|
} else if (ev.data.fd == sigfd) {
|
||||||
|
var siginfo: std.posix.siginfo_t = undefined;
|
||||||
|
std.debug.assert(try std.posix.read(
|
||||||
|
sigfd,
|
||||||
|
std.mem.asBytes(&siginfo),
|
||||||
|
) == @sizeOf(std.posix.siginfo_t));
|
||||||
|
|
||||||
|
std.log.info("got signal {}, bye!", .{siginfo.signo});
|
||||||
|
break :outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
vim.lua
2
vim.lua
|
@ -12,7 +12,7 @@ vim.keymap.set("n", "m", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
vim.keymap.set("n", "M", function()
|
vim.keymap.set("n", "M", function()
|
||||||
local nlines = 99
|
local nlines = vim.api.nvim_buf_line_count(0)
|
||||||
for i = 1, nlines do
|
for i = 1, nlines do
|
||||||
print(i .. "/" .. nlines)
|
print(i .. "/" .. nlines)
|
||||||
local line = vim.fn.getline(i)
|
local line = vim.fn.getline(i)
|
||||||
|
|
Loading…
Reference in a new issue