| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- const std = @import("std");
- const builtin = @import("builtin");
- const posix = std.posix;
- const socket = @import("socket.zig");
- const Engine = @import("engine.zig").Engine;
- const pizzaria = @import("command.zig");
- const resp = @import("redis.zig");
- const pkbfi = @import("pkbfi.zig");
- const migration = @import("migration.zig");
- const initial_buffer = 64 * 1024;
- const max_connection_bytes = 512 * 1024 * 1024;
- const max_connections = 256;
- var should_exit = std.atomic.Value(bool).init(false);
- var active_connections = std.atomic.Value(u32).init(0);
- var memory_mutex: std.Thread.Mutex = .{};
- var allocated_connection_bytes: usize = 0;
- fn signalHandler(_: c_int) callconv(.c) void {
- should_exit.store(true, .release);
- }
- fn reserveMemory(engine: *Engine, amount: usize) !void {
- memory_mutex.lock();
- defer memory_mutex.unlock();
- if (amount > max_connection_bytes - allocated_connection_bytes) return error.Backpressure;
- allocated_connection_bytes += amount;
- engine.addConnectionBytes(amount);
- }
- fn releaseMemory(engine: *Engine, amount: usize) void {
- memory_mutex.lock();
- std.debug.assert(amount <= allocated_connection_bytes);
- allocated_connection_bytes -= amount;
- memory_mutex.unlock();
- engine.removeConnectionBytes(amount);
- }
- fn sendAll(connection: posix.socket_t, bytes: []const u8) !void {
- var position: usize = 0;
- while (position < bytes.len) {
- const written = posix.send(connection, bytes[position..], posix.MSG.NOSIGNAL) catch |err| switch (err) {
- error.WouldBlock => return error.WriteTimedOut,
- else => return err,
- };
- if (written == 0) return error.ConnectionClosed;
- position += written;
- }
- }
- fn handleResp(engine: *Engine, connection: posix.socket_t, command: resp.Command) !void {
- if (command.command_type == .get) return handleRespGet(engine, connection, command.key);
- var response = try resp.execute(engine, std.heap.smp_allocator, command);
- defer response.deinit(std.heap.smp_allocator);
- if (response == .bulk) {
- try reserveMemory(engine, response.bulk.bytes.len);
- defer releaseMemory(engine, response.bulk.bytes.len);
- }
- var prefix: [64]u8 = undefined;
- const encoded = try resp.encodePrefix(response, &prefix);
- try sendAll(connection, encoded);
- if (response == .bulk) {
- try sendAll(connection, response.bulk.bytes);
- try sendAll(connection, "\r\n");
- }
- }
- fn handleRespGet(engine: *Engine, connection: posix.socket_t, key: []const u8) !void {
- const record = try engine.getRef(key) orelse {
- try sendAll(connection, "$-1\r\n");
- return;
- };
- var header: [32]u8 = undefined;
- try sendAll(connection, try std.fmt.bufPrint(&header, "${d}\r\n", .{record.value_len}));
- const buffer_size = @min(@as(usize, 64 * 1024), record.value_len);
- try reserveMemory(engine, buffer_size);
- defer releaseMemory(engine, buffer_size);
- const buffer = try std.heap.smp_allocator.alloc(u8, buffer_size);
- defer std.heap.smp_allocator.free(buffer);
- var position: u32 = 0;
- while (position < record.value_len) {
- const amount = try engine.readValue(record, buffer, position);
- try sendAll(connection, buffer[0..amount]);
- position += @intCast(amount);
- }
- try sendAll(connection, "\r\n");
- }
- fn handleRespGets(engine: *Engine, connection: posix.socket_t, keys: []const []const u8) !void {
- const capacity = 1024 * 1024;
- try reserveMemory(engine, capacity);
- defer releaseMemory(engine, capacity);
- const output = try std.heap.smp_allocator.alloc(u8, capacity);
- defer std.heap.smp_allocator.free(output);
- var position: usize = 0;
- for (keys) |key| {
- const record = try engine.getRef(key) orelse {
- if (position + 5 > output.len) {
- try sendAll(connection, output[0..position]);
- position = 0;
- }
- @memcpy(output[position .. position + 5], "$-1\r\n");
- position += 5;
- continue;
- };
- var header: [32]u8 = undefined;
- const encoded_header = try std.fmt.bufPrint(&header, "${d}\r\n", .{record.value_len});
- const needed = encoded_header.len + record.value_len + 2;
- if (needed > output.len) {
- if (position != 0) {
- try sendAll(connection, output[0..position]);
- position = 0;
- }
- try handleRespGet(engine, connection, key);
- continue;
- }
- if (position + needed > output.len) {
- try sendAll(connection, output[0..position]);
- position = 0;
- }
- @memcpy(output[position .. position + encoded_header.len], encoded_header);
- position += encoded_header.len;
- const amount = try engine.readValue(record, output[position .. position + record.value_len], 0);
- position += amount;
- @memcpy(output[position .. position + 2], "\r\n");
- position += 2;
- }
- if (position != 0) try sendAll(connection, output[0..position]);
- }
- fn handleConnection(engine: *Engine, connection: posix.socket_t) void {
- defer _ = active_connections.fetchSub(1, .monotonic);
- defer posix.close(connection);
- reserveMemory(engine, initial_buffer) catch return;
- var buffer = std.heap.smp_allocator.alloc(u8, initial_buffer) catch {
- releaseMemory(engine, initial_buffer);
- return;
- };
- defer {
- std.heap.smp_allocator.free(buffer);
- releaseMemory(engine, buffer.len);
- }
- var session = pkbfi.Session.init(std.heap.smp_allocator);
- defer session.deinit();
- var buffered: usize = 0;
- while (!should_exit.load(.acquire)) {
- if (buffered == buffer.len) {
- const maximum: usize = if (buffered >= 4 and std.mem.eql(u8, buffer[0..4], "PKBF")) pkbfi.max_frame_size + pkbfi.header_size else if (buffered > 0 and buffer[0] == '*') pkbfi.max_frame_size + 1024 else 1024 * 1024;
- if (buffer.len >= maximum) return;
- const next = @min(maximum, buffer.len * 2);
- reserveMemory(engine, next - buffer.len) catch return;
- buffer = std.heap.smp_allocator.realloc(buffer, next) catch {
- releaseMemory(engine, next - buffer.len);
- return;
- };
- }
- const amount = posix.read(connection, buffer[buffered..]) catch |err| switch (err) {
- error.WouldBlock => return,
- error.ConnectionResetByPeer => return,
- else => return,
- };
- if (amount == 0) return;
- buffered += amount;
- var consumed: usize = 0;
- while (consumed < buffered) {
- const input = buffer[consumed..buffered];
- if (input.len >= 4 and std.mem.eql(u8, input[0..4], "PKBF")) {
- const frame = pkbfi.parse(input) catch |err| switch (err) {
- error.Incomplete => break,
- else => return,
- };
- if (frame.opcode == .put) {
- var operations: [256]@import("engine.zig").Operation = undefined;
- var request_ids: [256]u64 = undefined;
- var lsns: [256]u64 = undefined;
- var count: usize = 0;
- var pipeline_consumed: usize = 0;
- while (count < operations.len and pipeline_consumed < input.len) {
- const next = pkbfi.parse(input[pipeline_consumed..]) catch |err| switch (err) {
- error.Incomplete => break,
- else => return,
- };
- if (next.opcode != .put) break;
- operations[count] = pkbfi.putOperation(next) catch return;
- request_ids[count] = next.request_id;
- count += 1;
- pipeline_consumed += next.consumed;
- }
- engine.beginRequest();
- engine.putMany(operations[0..count], lsns[0..count]) catch {
- engine.endRequest();
- return;
- };
- engine.endRequest();
- var responses = std.ArrayListUnmanaged(u8){};
- defer responses.deinit(std.heap.smp_allocator);
- for (0..count) |index| {
- var body: [10]u8 = [_]u8{0} ** 10;
- std.mem.writeInt(u64, body[2..10], lsns[index], .little);
- const response = pkbfi.encode(std.heap.smp_allocator, @intFromEnum(pkbfi.Opcode.put) | 0x8000, 1, request_ids[index], &body) catch return;
- defer std.heap.smp_allocator.free(response);
- responses.appendSlice(std.heap.smp_allocator, response) catch return;
- }
- reserveMemory(engine, responses.items.len) catch return;
- sendAll(connection, responses.items) catch {
- releaseMemory(engine, responses.items.len);
- return;
- };
- releaseMemory(engine, responses.items.len);
- consumed += pipeline_consumed;
- } else {
- engine.beginRequest();
- const response = session.execute(engine, frame) catch {
- engine.endRequest();
- return;
- };
- engine.endRequest();
- reserveMemory(engine, response.len) catch {
- std.heap.smp_allocator.free(response);
- return;
- };
- sendAll(connection, response) catch {
- std.heap.smp_allocator.free(response);
- releaseMemory(engine, response.len);
- return;
- };
- std.heap.smp_allocator.free(response);
- releaseMemory(engine, response.len);
- consumed += frame.consumed;
- }
- } else if (input[0] == '*') {
- const parsed = resp.parse(input) catch |err| switch (err) {
- error.Incomplete => break,
- else => return,
- };
- if (parsed.command.command_type == .set) {
- var operations: [256]@import("engine.zig").Operation = undefined;
- var lsns: [256]u64 = undefined;
- var count: usize = 0;
- var pipeline_consumed: usize = 0;
- while (count < operations.len and pipeline_consumed < input.len) {
- const next = resp.parse(input[pipeline_consumed..]) catch |err| switch (err) {
- error.Incomplete => break,
- else => return,
- };
- if (next.command.command_type != .set) break;
- operations[count] = .{ .opcode = .put, .key = next.command.key, .value = next.command.value };
- count += 1;
- pipeline_consumed += next.consumed;
- }
- engine.beginRequest();
- engine.putMany(operations[0..count], lsns[0..count]) catch {
- engine.endRequest();
- return;
- };
- engine.endRequest();
- var responses: [256 * 5]u8 = undefined;
- for (0..count) |index| @memcpy(responses[index * 5 ..][0..5], "+OK\r\n");
- sendAll(connection, responses[0 .. count * 5]) catch return;
- consumed += pipeline_consumed;
- } else if (parsed.command.command_type == .get) {
- var keys: [256][]const u8 = undefined;
- var count: usize = 0;
- var pipeline_consumed: usize = 0;
- while (count < keys.len and pipeline_consumed < input.len) {
- const next = resp.parse(input[pipeline_consumed..]) catch |err| switch (err) {
- error.Incomplete => break,
- else => return,
- };
- if (next.command.command_type != .get) break;
- keys[count] = next.command.key;
- count += 1;
- pipeline_consumed += next.consumed;
- }
- engine.beginRequest();
- handleRespGets(engine, connection, keys[0..count]) catch {
- engine.endRequest();
- return;
- };
- engine.endRequest();
- consumed += pipeline_consumed;
- } else {
- engine.beginRequest();
- handleResp(engine, connection, parsed.command) catch {
- engine.endRequest();
- return;
- };
- engine.endRequest();
- consumed += parsed.consumed;
- }
- } else {
- const end = std.mem.indexOfScalar(u8, input, '\r') orelse break;
- engine.beginRequest();
- const response = pizzaria.execute(engine, std.heap.smp_allocator, input[0..end]) catch {
- engine.endRequest();
- return;
- };
- engine.endRequest();
- reserveMemory(engine, response.len) catch {
- std.heap.smp_allocator.free(response);
- return;
- };
- sendAll(connection, response) catch {
- std.heap.smp_allocator.free(response);
- releaseMemory(engine, response.len);
- return;
- };
- sendAll(connection, "\r") catch {
- std.heap.smp_allocator.free(response);
- releaseMemory(engine, response.len);
- return;
- };
- std.heap.smp_allocator.free(response);
- releaseMemory(engine, response.len);
- consumed += end + 1 + @intFromBool(input.len > end + 1 and input[end + 1] == '\n');
- }
- }
- if (consumed != 0) {
- const remaining = buffered - consumed;
- std.mem.copyForwards(u8, buffer[0..remaining], buffer[consumed..buffered]);
- buffered = remaining;
- }
- if (buffer.len > initial_buffer and buffered <= initial_buffer) {
- var replacement = std.heap.smp_allocator.alloc(u8, initial_buffer) catch return;
- @memcpy(replacement[0..buffered], buffer[0..buffered]);
- const released = buffer.len - initial_buffer;
- std.heap.smp_allocator.free(buffer);
- buffer = replacement;
- releaseMemory(engine, released);
- }
- }
- }
- pub fn main() !void {
- var host: []const u8 = "127.0.0.1";
- var port: u16 = 8085;
- var path: []const u8 = ".pkvdb";
- var unix_path: ?[]const u8 = null;
- var migration_source: ?[]const u8 = null;
- var args = try std.process.argsWithAllocator(std.heap.page_allocator);
- defer args.deinit();
- _ = args.skip();
- while (args.next()) |argument| {
- if (std.mem.startsWith(u8, argument, "-host=")) host = argument[6..] else if (std.mem.startsWith(u8, argument, "-port=")) port = try std.fmt.parseInt(u16, argument[6..], 10) else if (std.mem.startsWith(u8, argument, "-path=")) path = argument[6..] else if (std.mem.startsWith(u8, argument, "-migrate=")) migration_source = argument[9..] else if (std.mem.eql(u8, argument, "-unix")) unix_path = ".pizzakv.sock" else if (std.mem.startsWith(u8, argument, "-unix=")) unix_path = argument[6..] else if (std.mem.eql(u8, argument, "-redis") or std.mem.eql(u8, argument, "-pkbfi") or std.mem.eql(u8, argument, "-iwal")) {} else return error.InvalidArgument;
- }
- if (migration_source) |source| {
- const result = try migration.migrate(std.heap.smp_allocator, source, path);
- std.debug.print("Migrated {d} keys from {d} records checksum={x}\n", .{ result.keys, result.records, result.checksum });
- return;
- }
- const action = posix.Sigaction{ .handler = .{ .handler = signalHandler }, .mask = std.mem.zeroes(posix.sigset_t), .flags = 0 };
- _ = posix.sigaction(posix.SIG.TERM, &action, null);
- _ = posix.sigaction(posix.SIG.INT, &action, null);
- var engine = try Engine.open(std.heap.smp_allocator, path);
- defer engine.close();
- const listener = if (unix_path) |name| try socket.initUnix(name) else try socket.init(host, port);
- defer posix.close(listener);
- defer if (unix_path) |name| posix.unlink(name) catch {};
- std.debug.print("PizzaKV {s} Pizzaria/RESP/PKBFI\n", .{path});
- while (!should_exit.load(.acquire)) {
- var descriptors = [_]posix.pollfd{.{ .fd = listener, .events = posix.POLL.IN, .revents = 0 }};
- if ((posix.poll(&descriptors, 100) catch continue) == 0) continue;
- const connection = posix.accept(listener, null, null, 0) catch continue;
- if (active_connections.fetchAdd(1, .monotonic) >= max_connections) {
- _ = active_connections.fetchSub(1, .monotonic);
- posix.close(connection);
- continue;
- }
- if (unix_path == null and (builtin.target.os.tag == .linux or builtin.target.os.tag == .macos)) posix.setsockopt(connection, posix.IPPROTO.TCP, posix.TCP.NODELAY, &std.mem.toBytes(@as(c_int, 1))) catch {};
- socket.setReadTimeout(connection, 30) catch {};
- socket.setWriteTimeout(connection, 30) catch {};
- const thread = std.Thread.spawn(.{}, handleConnection, .{ &engine, connection }) catch {
- _ = active_connections.fetchSub(1, .monotonic);
- posix.close(connection);
- continue;
- };
- thread.detach();
- }
- while (active_connections.load(.monotonic) != 0) std.Thread.sleep(10 * std.time.ns_per_ms);
- }
|