瀏覽代碼

Re-commiting whole course squashed into one commit

The history is non-sensical and messy (including every dumb typo)
so I squashed it all to avoid the rabbit hole of thinking how to split
it in a meaningful way.
Alois Mahdal 5 天之前
父節點
當前提交
cba351f6f7
共有 19 個檔案被更改,包括 9762 行新增23 行删除
  1. 1
    1
      .codecrafters/run.sh
  2. 3
    0
      .gitignore
  3. 1
    1
      codecrafters.yml
  4. 2579
    0
      src/glob.zig
  5. 300
    20
      src/main.zig
  6. 4
    0
      src/redis.zig
  7. 844
    0
      src/redis/command.zig
  8. 490
    0
      src/redis/core.zig
  9. 167
    0
      src/redis/logger.zig
  10. 1074
    0
      src/redis/rdb.zig
  11. 279
    0
      src/redis/replication.zig
  12. 851
    0
      src/redis/resp.zig
  13. 251
    0
      src/redis/server.zig
  14. 118
    0
      src/redis/store.zig
  15. 345
    0
      src/redis/task.zig
  16. 188
    0
      src/redis/worker.zig
  17. 2192
    0
      src/util.zig
  18. 74
    0
      watch_logs.sh
  19. 1
    1
      your_program.sh

+ 1
- 1
.codecrafters/run.sh 查看文件

@@ -8,4 +8,4 @@
8 8
 
9 9
 set -e # Exit on failure
10 10
 
11
-exec zig-out/bin/main "$@"
11
+exec zig-out/bin/main --debug --verbose --max-replicas 4 --max-threads 8 "$@"

+ 3
- 0
.gitignore 查看文件

@@ -16,3 +16,6 @@ bin/
16 16
 **/.DS_Store
17 17
 *.swp
18 18
 *~
19
+
20
+# server logs
21
+logs/

+ 1
- 1
codecrafters.yml 查看文件

@@ -2,7 +2,7 @@
2 2
 #
3 3
 # These can be VERY verbose, so we suggest turning them off
4 4
 # unless you really need them.
5
-debug: false
5
+debug: true
6 6
 
7 7
 # Use this to change the Zig version used to run your code
8 8
 # on Codecrafters.

+ 2579
- 0
src/glob.zig
文件差異過大導致無法顯示
查看文件


+ 300
- 20
src/main.zig 查看文件

@@ -1,25 +1,305 @@
1 1
 const std = @import("std");
2 2
 const net = std.net;
3 3
 
4
+const util = @import("util.zig");
5
+const redis = @import("redis.zig");
6
+
7
+const _KB = 1024;
8
+const _MB = 1024 * _KB;
9
+
10
+const HELP_TEXT =
11
+    \\usage: codecrafters-redis [options]
12
+    \\
13
+    \\options:
14
+    \\  --address ADDR     Bind to IP address ADDR.
15
+    \\  --port PORT        Bind to port PORT.
16
+    \\  --command-timeout SECONDS
17
+    \\                     Drop command connection if a peer does not respond under
18
+    \\                     SECONDS.   SECONDS must be decimal with up to millisecond
19
+    \\                     precision. Default: 10 seconds.
20
+    \\  --max-threads THREAD_COUNT
21
+    \\                     Don't create more than THREAD_COUNT threads.  Shared for
22
+    \\                     command threads as well as replication threads.
23
+    \\                     Default: use as many threads as available on CPU.
24
+    \\  --max-replicas REPLICA_COUNT
25
+    \\                     Don't accept more than REPLICA_COUNT replicas.  This must
26
+    \\                     at least one lower than --max-threads, otherwise
27
+    \\                     no commands can be processed.  Default: half of --max-threads,
28
+    \\                     rounded up to integer value.
29
+    \\                     This option is ignored in replica mode.
30
+    \\  --replica-timeout SECONDS
31
+    \\                     Drop replica connection if it does not respond under
32
+    \\                     SECONDS.   Applies to replicas for master and vice
33
+    \\                     versa.  SECONDS must be decimal with up to millisecond
34
+    \\                     precision. Default: 10 seconds.
35
+    \\  --replicaof CONN   Run as replica of CONN; CONN must be in form
36
+    \\                     of 'ADDR PORT'; that is, IP address or hostname
37
+    \\                     followed by space and port number.
38
+    \\  --dir DIRPATH      Store RDB files in DIRPATH.
39
+    \\  --dbfilename NAME  RDB file name.
40
+    \\  --debug            Enable debugging output.
41
+    \\  --logdir DIRPATH   Store logs in directory DIRPATH.  This will create separate
42
+    \\                     files for server and each individual task (connection).
43
+    \\                     (Default: logging to files is disabled).
44
+    \\  --verbose          Enable verbose output.
45
+    \\
46
+    \\
47
+;
48
+
49
+const ArgParsingResult = union(enum) {
50
+    options: AppOptions,
51
+    usage_error: []const u8,
52
+
53
+    fn unknown_arg(arg: []const u8) ArgParsingResult {
54
+        var arr: [100]u8 = undefined;
55
+        const msg = std.fmt.bufPrint(@as([]u8, &arr), "unknown argument: {s}", .{arg}) catch "unknown argument";
56
+        return ArgParsingResult{ .usage_error = msg };
57
+    }
58
+};
59
+
60
+const AppOptions = struct {
61
+    address: []const u8 = "127.0.0.1",
62
+    port: u16 = 6379,
63
+    debug: bool = false,
64
+    verbose: bool = false,
65
+    logdir: ?[]const u8 = null,
66
+    thread_buff_size: usize = 3 * _MB,
67
+    server_buff_size: usize = 10 * _MB,
68
+    replica_buff_size: usize = 640 * _KB,
69
+    replica_timeout_ms: u64 = 10_000,
70
+    command_timeout_ms: u64 = 10_000,
71
+    max_dump_bytes: usize = 10 * _MB,
72
+    max_threads: ?usize = null,
73
+    max_replicas: ?usize = null,
74
+    dir: ?[]const u8 = null,
75
+    dbfilename: []const u8 = "dump.rdb",
76
+    redis_replicaof_host: ?[]const u8 = null,
77
+    redis_replicaof_port: u16 = 6379,
78
+
79
+    const ConnInfo = struct {
80
+        addr: []const u8,
81
+        port: u16,
82
+        fn parse(text: []const u8) !ConnInfo {
83
+            if (std.mem.count(u8, text, " ") != 1) return error.InvalidConnStr;
84
+            const space_idx = std.mem.indexOf(u8, text, " ") orelse unreachable;
85
+            return ConnInfo{
86
+                .addr = text[0..space_idx],
87
+                .port = std.fmt.parseInt(u16, text[space_idx + 1 ..], 10) catch return error.InvalidConnStr,
88
+            };
89
+        }
90
+    };
91
+
92
+    fn init() AppOptions {
93
+        const parsing_result = AppOptions.parse_args();
94
+        switch (parsing_result) {
95
+            .options => return parsing_result.options,
96
+            .usage_error => {
97
+                _ = std.io.getStdErr().write(HELP_TEXT) catch {};
98
+                _ = std.io.getStdErr().write(parsing_result.usage_error) catch {};
99
+                _ = std.io.getStdErr().write("\n") catch {};
100
+                std.process.exit(2);
101
+            },
102
+        }
103
+    }
104
+
105
+    fn parse_args() ArgParsingResult {
106
+        var options = AppOptions{};
107
+        var this: []const u8 = undefined;
108
+        var it = std.process.args();
109
+        var param: ?[]const u8 = null;
110
+        _ = it.next(); // drop program name
111
+        while (true) {
112
+            this = @as(?[]const u8, it.next()) orelse break;
113
+            if (std.mem.eql(u8, this, "--address")) {
114
+                param = @as(?[]const u8, it.next());
115
+                options.address = param orelse return ArgParsingResult{ .usage_error = "no ADDRESS?" };
116
+            } else if (std.mem.eql(u8, this, "--port")) {
117
+                param = @as(?[]const u8, it.next());
118
+                options.port = std.fmt.parseInt(u16, param orelse return ArgParsingResult{ .usage_error = "no PORT?" }, 10) catch {
119
+                    return ArgParsingResult{ .usage_error = "PORT must be integer" };
120
+                };
121
+            } else if (std.mem.eql(u8, this, "--replicaof")) {
122
+                param = @as(?[]const u8, it.next());
123
+                const conn_info = ConnInfo.parse(param orelse return ArgParsingResult{ .usage_error = "no CONN?" }) catch {
124
+                    return ArgParsingResult{ .usage_error = "invalid CONN (must be '<ADDR> <PORT>')" };
125
+                };
126
+                options.redis_replicaof_host = conn_info.addr;
127
+                options.redis_replicaof_port = conn_info.port;
128
+            } else if (std.mem.eql(u8, this, "--thread-buff-size")) {
129
+                param = @as(?[]const u8, it.next());
130
+                options.thread_buff_size = std.fmt.parseInt(usize, param orelse return ArgParsingResult{ .usage_error = "no BYTES?" }, 10) catch {
131
+                    return ArgParsingResult{ .usage_error = "BYTES must be integer" };
132
+                };
133
+            } else if (std.mem.eql(u8, this, "--server-buff-size")) {
134
+                param = @as(?[]const u8, it.next());
135
+                options.server_buff_size = std.fmt.parseInt(usize, param orelse return ArgParsingResult{ .usage_error = "no BYTES?" }, 10) catch {
136
+                    return ArgParsingResult{ .usage_error = "BYTES must be integer" };
137
+                };
138
+            } else if (std.mem.eql(u8, this, "--max-dump-bytes")) {
139
+                param = @as(?[]const u8, it.next());
140
+                options.max_dump_bytes = std.fmt.parseInt(usize, param orelse return ArgParsingResult{ .usage_error = "no BYTES?" }, 10) catch {
141
+                    return ArgParsingResult{ .usage_error = "BYTES must be integer" };
142
+                };
143
+            } else if (std.mem.eql(u8, this, "--max-threads")) {
144
+                param = @as(?[]const u8, it.next());
145
+                options.max_threads = std.fmt.parseInt(usize, param orelse return ArgParsingResult{ .usage_error = "no THREAD_COUNT?" }, 10) catch {
146
+                    return ArgParsingResult{ .usage_error = "THREAD_COUNT must be integer" };
147
+                };
148
+                if (options.max_threads == 0) {
149
+                    return ArgParsingResult{ .usage_error = "THREAD_COUNT must be greater than 0" };
150
+                }
151
+            } else if (std.mem.eql(u8, this, "--max-replicas")) {
152
+                param = @as(?[]const u8, it.next());
153
+                options.max_replicas = std.fmt.parseInt(usize, param orelse return ArgParsingResult{ .usage_error = "no REPLICA_COUNT?" }, 10) catch {
154
+                    return ArgParsingResult{ .usage_error = "REPLICA_COUNT must be integer" };
155
+                };
156
+            } else if (std.mem.eql(u8, this, "--replica-timeout")) {
157
+                param = @as(?[]const u8, it.next());
158
+                const replica_timeout_s = std.fmt.parseFloat(f32, param orelse return ArgParsingResult{ .usage_error = "no SECONDS?" }) catch {
159
+                    return ArgParsingResult{ .usage_error = "SECONDS must be decimal" };
160
+                };
161
+                options.replica_timeout_ms = @intFromFloat(replica_timeout_s * std.time.ms_per_s);
162
+            } else if (std.mem.eql(u8, this, "--command-timeout")) {
163
+                param = @as(?[]const u8, it.next());
164
+                const command_timeout_s = std.fmt.parseFloat(f32, param orelse return ArgParsingResult{ .usage_error = "no SECONDS?" }) catch {
165
+                    return ArgParsingResult{ .usage_error = "SECONDS must be decimal" };
166
+                };
167
+                options.command_timeout_ms = @intFromFloat(command_timeout_s * std.time.ms_per_s);
168
+            } else if (std.mem.eql(u8, this, "--dir")) {
169
+                param = @as(?[]const u8, it.next());
170
+                options.dir = param orelse return ArgParsingResult{ .usage_error = "no DIRPATH?" };
171
+            } else if (std.mem.eql(u8, this, "--dbfilename")) {
172
+                param = @as(?[]const u8, it.next());
173
+                options.dbfilename = param orelse return ArgParsingResult{ .usage_error = "no NAME?" };
174
+            } else if (std.mem.eql(u8, this, "--debug")) {
175
+                options.debug = true;
176
+            } else if (std.mem.eql(u8, this, "--verbose")) {
177
+                options.verbose = true;
178
+            } else if (std.mem.eql(u8, this, "--logdir")) {
179
+                param = @as(?[]const u8, it.next());
180
+                options.logdir = param orelse return ArgParsingResult{ .usage_error = "no DIRPATH?" };
181
+            } else {
182
+                return ArgParsingResult.unknown_arg(this);
183
+            }
184
+        }
185
+        return ArgParsingResult{ .options = options };
186
+    }
187
+};
188
+
189
+const Memory = struct {
190
+    allocator: std.mem.Allocator,
191
+    server_buff: []u8,
192
+    rdbload_buff: []u8,
193
+    threads_buff: []u8,
194
+    replicas_buff: []u8,
195
+    server_fba: std.heap.FixedBufferAllocator,
196
+    rdbload_fba: std.heap.FixedBufferAllocator,
197
+    threads_fba: std.heap.FixedBufferAllocator,
198
+    replicas_fba: std.heap.FixedBufferAllocator,
199
+
200
+    fn create(options: AppOptions, thread_count: usize, replica_count: usize, allocator: std.mem.Allocator) !Memory {
201
+        const server_buff = try allocator.alloc(u8, options.server_buff_size);
202
+        const threads_buff_size = try std.math.mul(usize, options.thread_buff_size, thread_count);
203
+        const threads_buff = try allocator.alloc(u8, threads_buff_size);
204
+        const replicas_buff_size = try std.math.mul(usize, options.replica_buff_size, replica_count);
205
+        const replicas_buff = try allocator.alloc(u8, replicas_buff_size);
206
+        const rdbload_buff_size = try std.math.mul(usize, options.max_dump_bytes, 2);
207
+        const rdbload_buff = try allocator.alloc(u8, rdbload_buff_size);
208
+        return Memory{
209
+            .allocator = allocator,
210
+            .server_buff = server_buff,
211
+            .server_fba = std.heap.FixedBufferAllocator.init(server_buff),
212
+            .threads_buff = threads_buff,
213
+            .threads_fba = std.heap.FixedBufferAllocator.init(threads_buff),
214
+            .replicas_buff = replicas_buff,
215
+            .replicas_fba = std.heap.FixedBufferAllocator.init(replicas_buff),
216
+            .rdbload_buff = rdbload_buff,
217
+            .rdbload_fba = std.heap.FixedBufferAllocator.init(rdbload_buff),
218
+        };
219
+    }
220
+
221
+    fn destroy(self: *Memory) void {
222
+        self.server_fba.reset();
223
+        self.allocator.free(self.server_buff);
224
+        self.threads_fba.reset();
225
+        self.replicas_fba.reset();
226
+        self.allocator.free(self.threads_buff);
227
+        self.rdbload_fba.reset();
228
+        self.allocator.free(self.rdbload_buff);
229
+    }
230
+};
231
+
4 232
 pub fn main() !void {
5
-    const stdout = std.io.getStdOut().writer();
6
-
7
-    // You can use print statements as follows for debugging, they'll be visible when running tests.
8
-    try stdout.print("Logs from your program will appear here!", .{});
9
-
10
-    // Uncomment this block to pass the first stage
11
-    //
12
-    // const address = try net.Address.resolveIp("127.0.0.1", 6379);
13
-    //
14
-    // var listener = try address.listen(.{
15
-    //     .reuse_address = true,
16
-    // });
17
-    // defer listener.deinit();
18
-    //
19
-    // while (true) {
20
-    //     const connection = try listener.accept();
21
-    //
22
-    //     try stdout.print("accepted new connection", .{});
23
-    //     connection.stream.close();
24
-    // }
233
+    const app_options = AppOptions.init();
234
+    if (app_options.verbose) {
235
+        std.log.info("PID: {}", .{std.os.linux.getpid()});
236
+        std.log.info("getCpuCount: {}", .{try std.Thread.getCpuCount()});
237
+        std.log.info("app_options.address: {s}", .{app_options.address});
238
+        std.log.info("app_options.port: {d}", .{app_options.port});
239
+        std.log.info("app_options.debug: {}", .{app_options.debug});
240
+        std.log.info("app_options.verbose: {}", .{app_options.verbose});
241
+        std.log.info("app_options.logdir: {?s}", .{app_options.logdir});
242
+        std.log.info("app_options.thread_buff_size: {d}", .{app_options.thread_buff_size});
243
+        std.log.info("app_options.server_buff_size: {d}", .{app_options.server_buff_size});
244
+        std.log.info("app_options.replica_buff_size: {d}", .{app_options.replica_buff_size});
245
+        std.log.info("app_options.replica_timeout_ms: {d}", .{app_options.replica_timeout_ms});
246
+        std.log.info("app_options.command_timeout_s: {d}", .{app_options.command_timeout_ms});
247
+        std.log.info("app_options.max_dump_bytes: {d}", .{app_options.max_dump_bytes});
248
+        std.log.info("app_options.max_threads: {?d}", .{app_options.max_threads});
249
+        std.log.info("app_options.max_replicas: {?d}", .{app_options.max_replicas});
250
+        std.log.info("app_options.dir: {?s}", .{app_options.dir});
251
+        std.log.info("app_options.dbfilename: {s}", .{app_options.dbfilename});
252
+        std.log.info("app_options.redis_replicaof_host: {?s}", .{app_options.redis_replicaof_host});
253
+        std.log.info("app_options.redis_replicaof_port: {d}", .{app_options.redis_replicaof_port});
254
+    }
255
+
256
+    const thread_count = app_options.max_threads orelse @as(usize, @max(1, try std.Thread.getCpuCount()));
257
+    const replica_count = counting: {
258
+        if (app_options.redis_replicaof_host != null) break :counting 1;
259
+        if (app_options.max_replicas != null) break :counting app_options.max_replicas.?;
260
+        if (app_options.redis_replicaof_host != null and app_options.max_replicas != null) {
261
+            std.log.warn("ignoring --max-replicas in replica mode", .{});
262
+        }
263
+        break :counting (thread_count + 1) / 2;
264
+    };
265
+
266
+    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
267
+    var memory = try Memory.create(app_options, thread_count, replica_count, gpa.allocator());
268
+    defer memory.destroy();
269
+
270
+    const address = try net.Address.resolveIp(app_options.address, app_options.port);
271
+    var listener = try address.listen(.{
272
+        .reuse_address = true,
273
+    });
274
+    defer listener.deinit();
275
+
276
+    var server = try redis.server.Server.create(
277
+        redis.server.Config{
278
+            .dir = app_options.dir,
279
+            .dbfilename = app_options.dbfilename,
280
+            .max_dump_bytes = app_options.max_dump_bytes,
281
+            .replication_host = app_options.redis_replicaof_host,
282
+            .replication_port = app_options.redis_replicaof_port,
283
+            .replica_timeout_ms = app_options.replica_timeout_ms,
284
+            .command_timeout_ms = app_options.command_timeout_ms,
285
+            .thread_count = thread_count,
286
+            .replica_count = replica_count,
287
+            .replica_buff_size = app_options.replica_buff_size,
288
+            .port = app_options.port,
289
+            .debug = app_options.debug,
290
+            .verbose = app_options.verbose,
291
+            .logdir = app_options.logdir,
292
+        },
293
+        memory.threads_buff,
294
+        memory.replicas_buff,
295
+        memory.server_fba.allocator(),
296
+    );
297
+    defer server.destroy();
298
+
299
+    try server.maybe_load_dump(memory.rdbload_fba.allocator());
300
+    try server.serve(&listener);
301
+}
302
+
303
+test {
304
+    std.testing.refAllDecls(@This());
25 305
 }

+ 4
- 0
src/redis.zig 查看文件

@@ -0,0 +1,4 @@
1
+pub const resp = @import("redis/resp.zig");
2
+pub const server = @import("redis/server.zig");
3
+pub const command = @import("redis/command.zig");
4
+pub const task = @import("redis/task.zig");

+ 844
- 0
src/redis/command.zig 查看文件

@@ -0,0 +1,844 @@
1
+const std = @import("std");
2
+
3
+const glob = @import("../glob.zig");
4
+const util = @import("../util.zig");
5
+
6
+const core = @import("core.zig");
7
+const resp = @import("resp.zig");
8
+const replication = @import("replication.zig");
9
+const store = @import("store.zig");
10
+
11
+const Elem = core.Elem;
12
+const Logger = @import("logger.zig").Logger;
13
+const Server = @import("server.zig").Server;
14
+const Task = @import("task.zig").Task;
15
+
16
+const ELEM_MAX_DEPTH: usize = 128;
17
+
18
+const SyntaxError = error{
19
+    InvalidArgument,
20
+    InvalidCommand,
21
+    InvalidKey,
22
+    InvalidValue,
23
+    NoCommand,
24
+    TooFewArguments,
25
+    TooManyArguments,
26
+    UnknownArgument,
27
+    UnknownCommand,
28
+    UnknownValue,
29
+};
30
+
31
+const RunError = error{
32
+    InternalError,
33
+    NotAMaster,
34
+    NotAReplica,
35
+};
36
+
37
+const ParseCtx = struct {
38
+    args_stack: *ElemStack,
39
+    logger: Logger,
40
+    allocator: std.mem.Allocator,
41
+};
42
+
43
+pub const Result = struct {
44
+    elem: Elem,
45
+    ok: bool = true,
46
+
47
+    const NBS = Result{ .elem = Elem{ .nbs = null } };
48
+
49
+    pub fn from_str(str: []const u8) Result {
50
+        return Result{ .elem = Elem{ .str = str } };
51
+    }
52
+
53
+    pub fn from_err(err: anyerror) Result {
54
+        return Result{ .elem = Elem{ .err = @errorName(err) }, .ok = false };
55
+    }
56
+
57
+    pub fn from_int(int: i64) Result {
58
+        return Result{ .elem = Elem{ .int = int }, .ok = false };
59
+    }
60
+
61
+    pub fn from_arr(arr: []const Elem) Result {
62
+        return Result{ .elem = Elem{ .arr = arr } };
63
+    }
64
+
65
+    pub fn format(value: Result, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
66
+        _ = fmt;
67
+        _ = options;
68
+        const w: std.io.AnyWriter = writer;
69
+        try w.print("Result{{.elem={any}}}", .{&value.elem});
70
+    }
71
+};
72
+
73
+const _TestCommand = struct {
74
+    subcommand: Subcmd,
75
+
76
+    const Subcmd = union(enum) {
77
+        queuing,
78
+        slowset: SetCommand,
79
+        slowset_p: SetCommand,
80
+    };
81
+
82
+    fn parse(ctx: ParseCtx) !Command {
83
+        const subcmd = b: {
84
+            if (try ctx.args_stack.pop_literal("SLOWSET")) {
85
+                const command = try SetCommand.parse(ctx);
86
+                break :b Subcmd{ .slowset = command.set };
87
+            } else if (try ctx.args_stack.pop_literal("SLOWSET_P")) {
88
+                const command = try SetCommand.parse(ctx);
89
+                break :b Subcmd{ .slowset = command.set };
90
+            } else if (try ctx.args_stack.pop_literal("QUEUING")) {
91
+                break :b Subcmd{ .queuing = {} };
92
+            } else return SyntaxError.UnknownValue;
93
+        };
94
+        return Command{ ._test = _TestCommand{ .subcommand = subcmd } };
95
+    }
96
+
97
+    fn _run_queuing(self: @This(), task: *Task, server: *Server) !Result {
98
+        _ = self;
99
+        _ = server;
100
+        const timeout_ns = 5_000_000;
101
+        const ms = try task.require_master_state();
102
+        const data = "*3\r\n+SET\r\n+_test\r\n+foo\r\n";
103
+        var fba = std.io.fixedBufferStream(data);
104
+        const et = try resp.parse_et(fba.reader().any(), task.allocator);
105
+        const batch1 = try ms.replicas.bq.queue_batch(.{ .propagate = et }, timeout_ns);
106
+        ms.replicas.bq._dump("_TEST QUEUING batch1 queued");
107
+        const batch2 = try ms.replicas.bq.queue_batch(.{ .propagate = et }, timeout_ns);
108
+        ms.replicas.bq._dump("_TEST QUEUING batch2 queued");
109
+        const batch3 = try ms.replicas.bq.queue_batch(.{ .propagate = et }, timeout_ns);
110
+        ms.replicas.bq._dump("_TEST QUEUING batch3 queued");
111
+        while (true) {
112
+            const item = batch1.next();
113
+            std.log.debug("batch1.next()={any}", .{item});
114
+            if (item == null) break;
115
+        }
116
+        ms.replicas.bq._dump("_TEST QUEUING batch1 exhausted");
117
+        batch1.deinit();
118
+        ms.replicas.bq._dump("_TEST QUEUING batch1 deinited");
119
+        while (true) {
120
+            const item = batch2.next();
121
+            std.log.debug("batch2.next()={any}", .{item});
122
+            if (item == null) break;
123
+        }
124
+        ms.replicas.bq._dump("_TEST QUEUING batch2 exhausted");
125
+        batch2.deinit();
126
+        ms.replicas.bq._dump("_TEST QUEUING batch2 deinited");
127
+        while (true) {
128
+            const item = batch3.next();
129
+            std.log.debug("batch3.next()={any}", .{item});
130
+            if (item == null) break;
131
+        }
132
+        ms.replicas.bq._dump("_TEST QUEUING batch3 exhausted");
133
+        batch3.deinit();
134
+        ms.replicas.bq._dump("_TEST QUEUING batch3 deinited");
135
+        task.logger.info("_TestCommand().run():et={any}", .{et});
136
+        return Result.from_str("najs");
137
+    }
138
+
139
+    fn _run_slowset(self: @This(), task: *Task, server: *Server) !Result {
140
+        task.sleep(5_000);
141
+        return try self.subcommand.slowset.run(task, server);
142
+    }
143
+
144
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
145
+        return switch (self.subcommand) {
146
+            .slowset_p => try self._run_slowset(task, server),
147
+            .slowset => try self._run_slowset(task, server),
148
+            .queuing => try self._run_queuing(task, server),
149
+        };
150
+    }
151
+};
152
+
153
+const ConfigCommand = struct {
154
+    subcommand: Subcmd,
155
+
156
+    const Subcmd = union(enum) {
157
+        get: struct { key: []const u8 },
158
+    };
159
+
160
+    fn parse(ctx: ParseCtx) !Command {
161
+        var subcmd: Subcmd = undefined;
162
+        if (try ctx.args_stack.pop_literal("GET")) {
163
+            const key = try ctx.args_stack.pop_str();
164
+            subcmd = Subcmd{ .get = .{ .key = key } };
165
+        } else return SyntaxError.UnknownValue;
166
+        return Command{ .config = .{ .subcommand = subcmd } };
167
+    }
168
+
169
+    fn _run_get(self: @This(), server: *Server, allocator: std.mem.Allocator) !Result {
170
+        var elems_al = std.ArrayList(Elem).init(allocator);
171
+        const key = self.subcommand.get.key;
172
+        const key_elem = Elem{ .str = key };
173
+        const value_elem = blk: {
174
+            if (std.mem.eql(u8, key, "dir")) break :blk Elem{
175
+                .str = server.config.dir orelse break :blk core.NBS,
176
+            };
177
+            if (std.mem.eql(u8, key, "dbfilename")) break :blk Elem{
178
+                .str = server.config.dbfilename,
179
+            };
180
+            return Result.NBS;
181
+        };
182
+        try elems_al.append(key_elem);
183
+        try elems_al.append(value_elem);
184
+        return Result.from_arr(elems_al.items);
185
+    }
186
+
187
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
188
+        switch (self.subcommand) {
189
+            .get => return try self._run_get(server, task.allocator),
190
+        }
191
+    }
192
+};
193
+
194
+const EchoCommand = struct {
195
+    message: Elem,
196
+
197
+    fn parse(ctx: ParseCtx) !Command {
198
+        const message = try ctx.args_stack.pop_elem();
199
+        return Command{ .echo = EchoCommand{ .message = message } };
200
+    }
201
+
202
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
203
+        _ = task;
204
+        _ = server;
205
+        return Result{ .elem = self.message };
206
+    }
207
+};
208
+
209
+const GetCommand = struct {
210
+    key: []const u8,
211
+
212
+    fn parse(ctx: ParseCtx) !Command {
213
+        const key = try ctx.args_stack.pop_str();
214
+        return Command{ .get = GetCommand{ .key = key } };
215
+    }
216
+
217
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
218
+        _ = task;
219
+        server.store.m.lock();
220
+        defer server.store.m.unlock();
221
+        const maybe_elem = try server.store.get_elem(self.key);
222
+        return Result{ .elem = maybe_elem orelse core.NBS };
223
+    }
224
+};
225
+
226
+const InfoCommand = struct {
227
+    sections: std.AutoHashMap(Section, void),
228
+
229
+    const Section = enum { replication };
230
+    const KnownSection = struct {
231
+        section: Section,
232
+        label: []const u8,
233
+    };
234
+
235
+    const KNOWN_SECTIONS: []const KnownSection = &.{
236
+        KnownSection{ .section = .replication, .label = "Replication" },
237
+    };
238
+
239
+    const Buff = struct {
240
+        al: std.ArrayList(u8),
241
+        fn init(allocator: std.mem.Allocator) Buff {
242
+            return Buff{ .al = std.ArrayList(u8).init(allocator) };
243
+        }
244
+        fn append_cmt(self: *Buff, text: []const u8) !void {
245
+            const w = self.al.writer();
246
+            try std.fmt.format(w, "# {s}\n", .{text});
247
+        }
248
+        fn append_replid(self: *Buff, key: []const u8, value: u160) !void {
249
+            const w = self.al.writer();
250
+            try std.fmt.format(w, "{s}:{x:040}\n", .{ key, value });
251
+        }
252
+        fn append_int(self: *Buff, key: []const u8, value: usize) !void {
253
+            const w = self.al.writer();
254
+            try std.fmt.format(w, "{s}:{d}\n", .{ key, value });
255
+        }
256
+        fn append_str(self: *Buff, key: []const u8, value: []const u8) !void {
257
+            const w = self.al.writer();
258
+            try std.fmt.format(w, "{s}:{s}\n", .{ key, value });
259
+        }
260
+        fn append_crep(self: *Buff, r: replication.Replica, s: *Server) !void {
261
+            const connection = try s.connection_pool.require(r.connection_h);
262
+            const w = self.al.writer();
263
+            try std.fmt.format(w, "# connected replica: connection_handle:{any} addr:{any} port:{?d} capabilities:", .{
264
+                r.connection_h,
265
+                connection.address,
266
+                r.port,
267
+            });
268
+            var wrote = false;
269
+            if (r.capabilities.psync2) {
270
+                if (wrote) try w.writeByte(',');
271
+                wrote = true;
272
+                try w.writeAll("psync2");
273
+            }
274
+            try w.writeByte('\n');
275
+        }
276
+    };
277
+
278
+    fn parse(ctx: ParseCtx) !Command {
279
+        var sections = std.AutoHashMap(Section, void).init(ctx.allocator);
280
+        while (ctx.args_stack.has_more()) {
281
+            const section_name = try ctx.args_stack.pop_str();
282
+            const section: Section = std.meta.stringToEnum(Section, section_name) orelse {
283
+                ctx.logger.err("unknown section name: '{s}'", .{section_name});
284
+                return SyntaxError.UnknownValue;
285
+            };
286
+            try sections.put(section, {});
287
+        }
288
+        return Command{ .info = InfoCommand{ .sections = sections } };
289
+    }
290
+
291
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
292
+        var buff = Buff.init(task.allocator);
293
+        for (KNOWN_SECTIONS) |known_section| {
294
+            if (!self.sections.contains(known_section.section)) continue;
295
+            try buff.append_cmt(known_section.label);
296
+            switch (known_section.section) {
297
+                .replication => {
298
+                    switch (server.replication_state) {
299
+                        .master => |v| {
300
+                            try buff.append_str("role", "master");
301
+                            try buff.append_replid("master_replid", v.replid);
302
+                            try buff.append_int("master_repl_offset", @intCast(v.repl_offset));
303
+                            var itr = v.replicas.ptr_iterator();
304
+                            while (itr.next()) |ptr| try buff.append_crep(ptr.*, server);
305
+                        },
306
+                        .replica => |v| {
307
+                            try buff.append_str("role", "slave");
308
+                            try buff.append_str("master_host", v.master_host);
309
+                            try buff.append_int("master_port", @intCast(v.master_port));
310
+                        },
311
+                    }
312
+                },
313
+            }
314
+        }
315
+        return Result.from_str(buff.al.items);
316
+    }
317
+};
318
+
319
+const KeysCommand = struct {
320
+    glob: glob.Glob,
321
+    _pattern: []const u8,
322
+
323
+    fn parse(ctx: ParseCtx) !Command {
324
+        const pattern = try ctx.args_stack.pop_str();
325
+        const glob_ = glob.parse_glob(pattern, ctx.allocator) catch |e| {
326
+            ctx.logger.err("invalid pattern: {} when parsing '{s}'", .{ e, pattern });
327
+            return SyntaxError.InvalidArgument;
328
+        };
329
+        return Command{ .keys = KeysCommand{ .glob = glob_, ._pattern = pattern } };
330
+    }
331
+
332
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
333
+        var elems_al = std.ArrayList(Elem).init(task.allocator);
334
+        server.store.m.lock();
335
+        defer server.store.m.unlock();
336
+        var key_iter = server.store.elems_db.keyIterator();
337
+        while (key_iter.next()) |key| {
338
+            const is_match = self.glob.match(key.*) catch |e| {
339
+                task.logger.err("internal error: {} when matching key: '{s}' against glob pattern '{s}'", .{
340
+                    e,
341
+                    key.*,
342
+                    self._pattern,
343
+                });
344
+                return e;
345
+            };
346
+            if (!is_match) continue;
347
+            try elems_al.append(Elem{ .str = key.* });
348
+        }
349
+        return Result.from_arr(elems_al.items);
350
+    }
351
+};
352
+
353
+const PingCommand = struct {
354
+    fn parse(task: ParseCtx) !Command {
355
+        _ = task;
356
+        return Command{ .ping = PingCommand{} };
357
+    }
358
+
359
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
360
+        _ = self;
361
+        _ = task;
362
+        _ = server;
363
+        return Result.from_str("PONG");
364
+    }
365
+};
366
+
367
+const PsyncCommand = struct {
368
+    replid: ?u160,
369
+    offset: isize,
370
+
371
+    fn parse(ctx: ParseCtx) !Command {
372
+        return Command{ .psync = PsyncCommand{
373
+            .replid = blk: {
374
+                const word = try ctx.args_stack.pop_str();
375
+                if (word.len == 1 and word[0] == '?') break :blk null;
376
+                break :blk std.fmt.parseInt(u160, word, 16) catch return SyntaxError.InvalidValue;
377
+            },
378
+            .offset = blk: {
379
+                const word = try ctx.args_stack.pop_str();
380
+                break :blk std.fmt.parseInt(isize, word, 10) catch return SyntaxError.InvalidValue;
381
+            },
382
+        } };
383
+    }
384
+
385
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
386
+        _ = self;
387
+        _ = server;
388
+        const ms = try task.require_master_state();
389
+        var cmd_al = std.ArrayList(u8).init(task.allocator);
390
+        try cmd_al.writer().print(
391
+            "FULLRESYNC {x} {d}",
392
+            .{ ms.replid, 0 },
393
+        );
394
+        return Result{ .elem = Elem{ .str = try cmd_al.toOwnedSlice() } };
395
+    }
396
+};
397
+
398
+const ReplconfCommand = struct {
399
+    subcommand: Subcmd,
400
+
401
+    const Subcmd = union(enum) {
402
+        listening_port: u16,
403
+        capa: []replication.Capability,
404
+        getack: void,
405
+    };
406
+
407
+    fn parse(ctx: ParseCtx) !Command {
408
+        var subcmd: Subcmd = undefined;
409
+        if (try ctx.args_stack.pop_literal("listening-port")) {
410
+            const port_str = try ctx.args_stack.pop_str();
411
+            const port = std.fmt.parseInt(u16, port_str, 10) catch return SyntaxError.InvalidValue;
412
+            subcmd = Subcmd{ .listening_port = port };
413
+        } else if (try ctx.args_stack.pop_literal("capa")) {
414
+            var capas_al = std.ArrayList(replication.Capability).init(ctx.allocator);
415
+            if (try ctx.args_stack.pop_literal("psync2")) {
416
+                try capas_al.append(.psync2);
417
+            } else return SyntaxError.InvalidValue;
418
+            subcmd = Subcmd{ .capa = capas_al.items };
419
+        } else if (try ctx.args_stack.pop_literal("getack")) {
420
+            _ = try ctx.args_stack.pop_literal("*"); // no explanation for this argument
421
+            subcmd = Subcmd{ .getack = {} };
422
+        } else return SyntaxError.UnknownValue;
423
+        return Command{ .replconf = .{ .subcommand = subcmd } };
424
+    }
425
+
426
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
427
+        _ = server;
428
+        switch (self.subcommand) {
429
+            .listening_port => |port| {
430
+                const ms = try task.require_master_state();
431
+                const replica = try ms.mkreplica(task.connection_h);
432
+                replica.port = port;
433
+                return Result.from_str("OK");
434
+            },
435
+            .capa => |new_capas| {
436
+                const ms = try task.require_master_state();
437
+                const replica = try ms.mkreplica(task.connection_h);
438
+                replica.set_capas(new_capas);
439
+                return Result.from_str("OK");
440
+            },
441
+            .getack => {
442
+                const rs = try task.require_replica_state();
443
+                var eb = ElemBuilder.init(task.allocator);
444
+                try eb.append_str("REPLCONF");
445
+                try eb.append_str("ACK");
446
+                try eb.append_fmt("{d}", .{rs.master_last_offset orelse 0});
447
+                return Result.from_arr(try eb.al.toOwnedSlice());
448
+            },
449
+        }
450
+    }
451
+};
452
+
453
+const SetCommand = struct {
454
+    key: []const u8,
455
+    elem: Elem,
456
+    xc: ExistenceConstraint,
457
+    px_update: store.PxUpdate,
458
+    do_get: bool,
459
+
460
+    const ExistenceConstraint = enum { yes, no, any };
461
+
462
+    fn parse(ctx: ParseCtx) !Command {
463
+        const key = try ctx.args_stack.pop_str();
464
+        const elem = try ctx.args_stack.pop_elem();
465
+        var do_get: bool = false;
466
+        var xc: ExistenceConstraint = .any;
467
+        var px_update: store.PxUpdate = .unset;
468
+        while (ctx.args_stack.has_more()) {
469
+            if (try ctx.args_stack.pop_literal("NX")) {
470
+                xc = .no;
471
+            } else if (try ctx.args_stack.pop_literal("XX")) {
472
+                xc = .yes;
473
+            } else if (try ctx.args_stack.pop_literal("GET")) {
474
+                do_get = true;
475
+            } else if (try ctx.args_stack.pop_literal("EX")) {
476
+                const param_str = try ctx.args_stack.pop_str();
477
+                const offset_s = std.fmt.parseInt(i64, param_str, 10) catch return SyntaxError.InvalidValue;
478
+                const offset_ms = std.math.mul(i64, offset_s, 1000) catch return SyntaxError.InvalidValue;
479
+                const ts_ms = std.math.add(i64, offset_ms, std.time.milliTimestamp()) catch return SyntaxError.InvalidValue;
480
+                px_update = .{ .new = ts_ms };
481
+            } else if (try ctx.args_stack.pop_literal("PX")) {
482
+                const param_str = try ctx.args_stack.pop_str();
483
+                const offset_ms = std.fmt.parseInt(i64, param_str, 10) catch return SyntaxError.InvalidValue;
484
+                const ts_ms = std.math.add(i64, offset_ms, std.time.milliTimestamp()) catch return SyntaxError.InvalidValue;
485
+                px_update = .{ .new = ts_ms };
486
+            } else if (try ctx.args_stack.pop_literal("EXAT")) {
487
+                const param_str = try ctx.args_stack.pop_str();
488
+                const ts_s = std.fmt.parseInt(i64, param_str, 10) catch return SyntaxError.InvalidValue;
489
+                const ts_ms = std.math.mul(i64, ts_s, 1000) catch return SyntaxError.InvalidValue;
490
+                px_update = .{ .new = ts_ms };
491
+            } else if (try ctx.args_stack.pop_literal("PXAT")) {
492
+                const param_str = try ctx.args_stack.pop_str();
493
+                const ts_ms = std.fmt.parseInt(i64, param_str, 10) catch return SyntaxError.InvalidValue;
494
+                px_update = .{ .new = ts_ms };
495
+            } else if (try ctx.args_stack.pop_literal("KEEPTTL")) {
496
+                px_update = .keep;
497
+            } else {
498
+                return SyntaxError.UnknownArgument;
499
+            }
500
+        }
501
+        return Command{ .set = SetCommand{
502
+            .key = key,
503
+            .elem = elem,
504
+            .do_get = do_get,
505
+            .xc = xc,
506
+            .px_update = px_update,
507
+        } };
508
+    }
509
+
510
+    fn _put_get_elem(self: @This(), server: *Server) !Elem {
511
+        var ok_elem = Elem{ .str = "OK" };
512
+        if (self.do_get) {
513
+            ok_elem = if (try server.store.get_elem(self.key)) |prev| prev else core.NBS;
514
+        }
515
+        try server.store.put_elem(self.key, self.elem, self.px_update);
516
+        return ok_elem;
517
+    }
518
+
519
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
520
+        server.store.m.lock();
521
+        defer server.store.m.unlock();
522
+        switch (self.xc) {
523
+            .yes => if (!server.store.elems_db.contains(self.key)) return Result.NBS,
524
+            .no => if (server.store.elems_db.contains(self.key)) return Result.NBS,
525
+            .any => {},
526
+        }
527
+        return Result{ .elem = self._put_get_elem(server) catch |e| {
528
+            task.logger.warn("error when storing element: {}", .{e});
529
+            return e;
530
+        } };
531
+    }
532
+};
533
+
534
+const WaitCommand = struct {
535
+    numreplicas: usize,
536
+    timeout_ms: u32,
537
+
538
+    fn parse(ctx: ParseCtx) !Command {
539
+        const numreplicas = try ctx.args_stack.pop_int(usize);
540
+        const timeout_ms = try ctx.args_stack.pop_int(u32);
541
+        return Command{ .wait = WaitCommand{
542
+            .numreplicas = numreplicas,
543
+            .timeout_ms = timeout_ms,
544
+        } };
545
+    }
546
+
547
+    fn run(self: @This(), task: *Task, server: *Server) !Result {
548
+        _ = server;
549
+        const ok_count = try task.check_replicas(self.numreplicas, self.timeout_ms);
550
+        return Result.from_int(@intCast(ok_count));
551
+    }
552
+};
553
+
554
+pub const Command = union(enum) {
555
+    _test: _TestCommand,
556
+    config: ConfigCommand,
557
+    echo: EchoCommand,
558
+    get: GetCommand,
559
+    info: InfoCommand,
560
+    keys: KeysCommand,
561
+    ping: PingCommand,
562
+    psync: PsyncCommand,
563
+    replconf: ReplconfCommand,
564
+    set: SetCommand,
565
+    wait: WaitCommand,
566
+
567
+    pub const Flag = enum { propagate, start_replica, ack_master };
568
+
569
+    pub fn parse(elem: ?Elem, logger: Logger, allocator: std.mem.Allocator) !Command {
570
+        const tokens = elem orelse return SyntaxError.NoCommand;
571
+        if (tokens != .arr) return SyntaxError.InvalidCommand;
572
+        if (tokens.arr.len == 0) return SyntaxError.InvalidCommand;
573
+        var args_stack = ElemStack{ .elems = tokens.arr };
574
+        const ctx = ParseCtx{ .args_stack = &args_stack, .logger = logger, .allocator = allocator };
575
+        var command: Command = undefined;
576
+        if (try args_stack.pop_literal("_TEST")) {
577
+            command = try _TestCommand.parse(ctx);
578
+        } else if (try args_stack.pop_literal("ECHO")) {
579
+            command = try EchoCommand.parse(ctx);
580
+        } else if (try args_stack.pop_literal("GET")) {
581
+            command = try GetCommand.parse(ctx);
582
+        } else if (try args_stack.pop_literal("INFO")) {
583
+            command = try InfoCommand.parse(ctx);
584
+        } else if (try args_stack.pop_literal("KEYS")) {
585
+            command = try KeysCommand.parse(ctx);
586
+        } else if (try args_stack.pop_literal("PING")) {
587
+            command = try PingCommand.parse(ctx);
588
+        } else if (try args_stack.pop_literal("PSYNC")) {
589
+            command = try PsyncCommand.parse(ctx);
590
+        } else if (try args_stack.pop_literal("REPLCONF")) {
591
+            command = try ReplconfCommand.parse(ctx);
592
+        } else if (try args_stack.pop_literal("SET")) {
593
+            command = try SetCommand.parse(ctx);
594
+        } else if (try args_stack.pop_literal("WAIT")) {
595
+            command = try WaitCommand.parse(ctx);
596
+        } else if (try args_stack.pop_literal("CONFIG")) {
597
+            command = try ConfigCommand.parse(ctx);
598
+        } else {
599
+            return SyntaxError.UnknownCommand;
600
+        }
601
+        try args_stack.assert_done();
602
+        return command;
603
+    }
604
+
605
+    pub fn run(
606
+        self: @This(),
607
+        task: *Task,
608
+        server: *Server,
609
+    ) Result {
610
+        return switch (self) {
611
+            inline else => |c| c.run(task, server) catch |e| Result.from_err(e),
612
+        };
613
+    }
614
+
615
+    pub fn should(self: Command, flag: Flag) bool {
616
+        return switch (flag) {
617
+            .propagate => switch (self) {
618
+                .set => true,
619
+                ._test => |impl| switch (impl.subcommand) {
620
+                    .slowset => true,
621
+                    else => false,
622
+                },
623
+                else => false,
624
+            },
625
+            .start_replica => switch (self) {
626
+                .psync => true,
627
+                else => false,
628
+            },
629
+            .ack_master => switch (self) {
630
+                .replconf => |c| c.subcommand == .getack,
631
+                else => false,
632
+            },
633
+        };
634
+    }
635
+};
636
+
637
+const ElemStack = struct {
638
+    elems: []const Elem,
639
+    cur: usize = 0,
640
+
641
+    fn _step(self: *ElemStack, n: usize) void {
642
+        //
643
+        // [|]      (0) +0 => 0 < 0     // ok
644
+        // [|]      (0) +1 => 0 < 1     // unreachable
645
+        // [|]      (0) +2 => 0 < 2     // unreachable
646
+        // [ ]!     (1) +0 => 0 < 1     // unreachable
647
+        // [ ]!     (1) +1 => 0 < 2     // unreachable
648
+        // [ ]!     (1) +2 => 0 < 3     // unreachable
649
+        // [ ]!     (2) +0 => 0 < 2     // unreachable
650
+        // [ ]!     (2) +1 => 0 < 3     // unreachable
651
+        // [ ]!     (2) +2 => 0 < 4     // unreachable
652
+        //
653
+        // [|_ ]    (0) +0 => 1 < 0     // ok
654
+        // [|_ ]    (0) +1 => 1 < 1     // ok
655
+        // [|_ ]    (0) +2 => 1 < 2     // unreachable
656
+        // [ _|]    (1) +0 => 1 < 1     // ok
657
+        // [ _|]    (1) +1 => 1 < 2     // unreachable
658
+        // [ _|]    (1) +2 => 1 < 3     // unreachable
659
+        // [ _ ]!   (2) +0 => 1 < 2     // unreachable
660
+        // [ _ ]!   (2) +1 => 1 < 3     // unreachable
661
+        // [ _ ]!   (2) +2 => 1 < 4     // unreachable
662
+        //
663
+        // [|_ _ ]  (0) +0 => 2 < 0     // ok
664
+        // [|_ _ ]  (0) +1 => 2 < 1     // ok
665
+        // [|_ _ ]  (0) +2 => 2 < 2     // ok
666
+        // [ _|_ ]  (1) +0 => 2 < 1     // ok
667
+        // [ _|_ ]  (1) +1 => 2 < 2     // ok
668
+        // [ _|_ ]  (1) +2 => 2 < 3     // unreachable
669
+        // [ _ _|]  (2) +0 => 2 < 2     // ok
670
+        // [ _ _|]  (2) +1 => 2 < 3     // unreachable
671
+        // [ _ _|]  (2) +2 => 2 < 4     // unreachable
672
+        //
673
+        if (self.elems.len < (self.cur + n)) unreachable;
674
+        self.cur = self.cur + n;
675
+    }
676
+    fn assert_done(self: ElemStack) !void {
677
+        if (self.has_more()) return SyntaxError.TooManyArguments;
678
+        return;
679
+    }
680
+    fn has_more(self: ElemStack) bool {
681
+        const rest = self.elems[self.cur..];
682
+        return rest.len > 0;
683
+    }
684
+    fn _maybe_next(self: ElemStack) ?Elem {
685
+        if (self.cur >= self.elems.len) return null;
686
+        return self.elems[self.cur];
687
+    }
688
+    fn pop_literal(self: *ElemStack, str: []const u8) !bool {
689
+        const next = self._maybe_next() orelse return SyntaxError.TooFewArguments;
690
+        if (next != .str) return SyntaxError.InvalidValue;
691
+        if (!util.eql_i(next.str, str)) return false;
692
+        self._step(1);
693
+        return true;
694
+    }
695
+    fn pop_elem(self: *ElemStack) !Elem {
696
+        const next = self._maybe_next() orelse return SyntaxError.TooFewArguments;
697
+        self._step(1);
698
+        return next;
699
+    }
700
+    fn pop_int(self: *ElemStack, comptime T: type) !T {
701
+        const elem = try self.pop_elem();
702
+        const ti: std.builtin.Type = @typeInfo(T);
703
+        if (ti != .Int) @compileError("not an integer type: " ++ @typeName(T) ++ " is " ++ @tagName(ti));
704
+        switch (elem) {
705
+            .int => |n| return @intCast(n),
706
+            .str => |s| return std.fmt.parseInt(T, s, 10) catch return SyntaxError.InvalidValue,
707
+            else => return SyntaxError.InvalidValue,
708
+        }
709
+    }
710
+    fn pop_str(self: *ElemStack) ![]const u8 {
711
+        const next = self._maybe_next() orelse return SyntaxError.TooFewArguments;
712
+        if (next != .str) return SyntaxError.InvalidValue;
713
+        self._step(1);
714
+        return next.str;
715
+    }
716
+};
717
+
718
+test "ElemStack" {
719
+    const Case = struct {
720
+        stack: *ElemStack,
721
+        const Case = @This();
722
+        fn init(elems: []const Elem) !Case {
723
+            var elems_al = std.ArrayList(Elem).init(std.heap.page_allocator);
724
+            try elems_al.appendSlice(elems);
725
+            const stack_ptr = try std.heap.page_allocator.create(ElemStack);
726
+            stack_ptr.elems = elems_al.items;
727
+            stack_ptr.cur = 0;
728
+            return .{ .stack = stack_ptr };
729
+        }
730
+        fn expect_fullness(self: *Case) !void {
731
+            try std.testing.expectEqual(true, self.stack.has_more());
732
+            try std.testing.expectEqual(SyntaxError.TooManyArguments, self.stack.assert_done());
733
+        }
734
+        fn expect_emptiness(self: *Case) !void {
735
+            try std.testing.expectEqual(false, self.stack.has_more());
736
+            try self.stack.assert_done();
737
+            try std.testing.expectEqual(SyntaxError.TooFewArguments, self.stack.pop_literal("FOO"));
738
+            try std.testing.expectEqual(SyntaxError.TooFewArguments, self.stack.pop_str());
739
+            try std.testing.expectEqual(SyntaxError.TooFewArguments, self.stack.pop_elem());
740
+        }
741
+    };
742
+
743
+    var c: Case = undefined;
744
+    const mkcase = Case.init;
745
+
746
+    c = try mkcase(&.{});
747
+    try c.expect_emptiness();
748
+
749
+    c = try mkcase(&.{Elem{ .null = null }});
750
+    try c.expect_fullness();
751
+    try std.testing.expectEqual(SyntaxError.InvalidValue, c.stack.pop_literal("FOO"));
752
+    try std.testing.expectEqual(SyntaxError.InvalidValue, c.stack.pop_str());
753
+    try std.testing.expectEqualDeep(Elem{ .null = null }, try c.stack.pop_elem());
754
+    try c.expect_emptiness();
755
+
756
+    c = try mkcase(&.{Elem{ .int = 3 }});
757
+    try c.expect_fullness();
758
+    try std.testing.expectEqual(SyntaxError.InvalidValue, c.stack.pop_literal("FOO"));
759
+    try std.testing.expectEqual(SyntaxError.InvalidValue, c.stack.pop_str());
760
+    try std.testing.expectEqualDeep(Elem{ .int = 3 }, try c.stack.pop_elem());
761
+    try c.expect_emptiness();
762
+
763
+    c = try mkcase(&.{Elem{ .str = "" }});
764
+    try c.expect_fullness();
765
+    try std.testing.expectEqual(false, c.stack.pop_literal("FOO"));
766
+    try std.testing.expectEqual("", c.stack.pop_str());
767
+    try c.expect_emptiness();
768
+
769
+    c = try mkcase(&.{Elem{ .str = "" }});
770
+    try c.expect_fullness();
771
+    try std.testing.expectEqualDeep(Elem{ .str = "" }, try c.stack.pop_elem());
772
+    try c.expect_emptiness();
773
+
774
+    c = try mkcase(&.{Elem{ .str = "FOO" }});
775
+    try c.expect_fullness();
776
+    try std.testing.expectEqual(true, c.stack.pop_literal("FOO"));
777
+    try c.expect_emptiness();
778
+
779
+    c = try mkcase(&.{Elem{ .str = "FOO" }});
780
+    try c.expect_fullness();
781
+    try std.testing.expectEqualDeep(Elem{ .str = "FOO" }, try c.stack.pop_elem());
782
+    try c.expect_emptiness();
783
+
784
+    c = try mkcase(&.{ Elem{ .str = "FOO" }, Elem{ .null = null } });
785
+    try c.expect_fullness();
786
+    try std.testing.expectEqual(true, c.stack.pop_literal("FOO"));
787
+    try std.testing.expectEqual(SyntaxError.InvalidValue, c.stack.pop_str());
788
+    try std.testing.expectEqualDeep(Elem{ .null = null }, try c.stack.pop_elem());
789
+    try c.expect_emptiness();
790
+
791
+    c = try mkcase(&.{ Elem{ .str = "FOO" }, Elem{ .null = null } });
792
+    try c.expect_fullness();
793
+    try std.testing.expectEqual(true, c.stack.pop_literal("FOO"));
794
+    try std.testing.expectEqualDeep(Elem{ .null = null }, try c.stack.pop_elem());
795
+    try c.expect_emptiness();
796
+
797
+    c = try mkcase(&.{ Elem{ .str = "FOO" }, Elem{ .int = 42 } });
798
+    try c.expect_fullness();
799
+    try std.testing.expectEqual(true, c.stack.pop_literal("FOO"));
800
+    try std.testing.expectEqual(SyntaxError.InvalidValue, c.stack.pop_str());
801
+    try std.testing.expectEqualDeep(Elem{ .int = 42 }, try c.stack.pop_elem());
802
+    try c.expect_emptiness();
803
+
804
+    c = try mkcase(&.{ Elem{ .str = "FOO" }, Elem{ .int = 42 } });
805
+    try c.expect_fullness();
806
+    try std.testing.expectEqual(true, c.stack.pop_literal("FOO"));
807
+    try std.testing.expectEqualDeep(Elem{ .int = 42 }, try c.stack.pop_elem());
808
+    try c.expect_emptiness();
809
+
810
+    c = try mkcase(&.{ Elem{ .str = "FOO" }, Elem{ .str = "bar" } });
811
+    try c.expect_fullness();
812
+    try std.testing.expectEqual(true, c.stack.pop_literal("FOO"));
813
+    try std.testing.expectEqual("bar", c.stack.pop_str());
814
+    try c.expect_emptiness();
815
+
816
+    c = try mkcase(&.{ Elem{ .str = "FOO" }, Elem{ .str = "bar" } });
817
+    try c.expect_fullness();
818
+    try std.testing.expectEqual(true, c.stack.pop_literal("FOO"));
819
+    try std.testing.expectEqualDeep(Elem{ .str = "bar" }, try c.stack.pop_elem());
820
+    try c.expect_emptiness();
821
+}
822
+
823
+const ElemBuilder = struct {
824
+    al: std.ArrayList(Elem),
825
+    allocator: std.mem.Allocator,
826
+
827
+    fn init(allocator: std.mem.Allocator) ElemBuilder {
828
+        return ElemBuilder{ .al = std.ArrayList(Elem).init(allocator), .allocator = allocator };
829
+    }
830
+
831
+    fn append_str(self: *ElemBuilder, str: []const u8) !void {
832
+        try self.al.append(Elem{ .str = try self.allocator.dupe(u8, str) });
833
+    }
834
+
835
+    fn append_fmt(self: *ElemBuilder, comptime fmt: []const u8, args: anytype) !void {
836
+        var str = std.ArrayList(u8).init(self.allocator);
837
+        try str.writer().print(fmt, args);
838
+        try self.al.append(Elem{ .str = try str.toOwnedSlice() });
839
+    }
840
+
841
+    fn to_arr(self: *ElemBuilder) !Elem {
842
+        return Elem{ .arr = try self.al.toOwnedSlice() };
843
+    }
844
+};

+ 490
- 0
src/redis/core.zig 查看文件

@@ -0,0 +1,490 @@
1
+const std = @import("std");
2
+
3
+const util = @import("../util.zig");
4
+
5
+pub const ElemType = enum { null, nbs, bool, int, double, err, str, bin, arr };
6
+
7
+pub const Elem = union(ElemType) {
8
+    null: ?void,
9
+    nbs: ?void,
10
+    bool: bool,
11
+    int: i64,
12
+    double: f64,
13
+    err: []const u8,
14
+    str: []const u8,
15
+    bin: []const u8,
16
+    arr: []const Elem,
17
+    pub fn format(self: Elem, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
18
+        _ = fmt;
19
+        _ = options;
20
+        try deep_fmt_elem(&self, writer, ELEM_MAX_DEPTH);
21
+    }
22
+};
23
+
24
+pub const ElemTree = struct {
25
+    root: ?*Elem,
26
+    raw_bytes: []const u8,
27
+    arena: std.heap.ArenaAllocator,
28
+
29
+    pub const Error = error{
30
+        ElemIsABinaryString,
31
+        ElemIsAScalar,
32
+        ElemIsAString,
33
+        ElemIsAnArray,
34
+        ElemIsAnError,
35
+        ElemIsAnInteger,
36
+        ElemNotABinaryString,
37
+        ElemNotAString,
38
+        ElemNotAnArray,
39
+        ElemNotAnError,
40
+        ElemNotAnInteger,
41
+        ElemNotExpected,
42
+        ElemStringNotAnInt,
43
+        ElemTooLong,
44
+        ElemTooShort,
45
+        ElemValueTooHigh,
46
+        ElemValueTooLow,
47
+        NoElem,
48
+    };
49
+
50
+    pub fn deinit(self: ElemTree) void {
51
+        self.arena.deinit();
52
+    }
53
+
54
+    pub fn format(self: ElemTree, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
55
+        _ = fmt;
56
+        _ = options;
57
+        if (self.root) |_| {
58
+            try writer.print("ElemTree{{.raw_bytes=({d}B),.root={?any}}}", .{ self.raw_bytes.len, self.root });
59
+        } else {
60
+            try writer.writeAll("ElemTree{}(STREAM_END)");
61
+        }
62
+    }
63
+
64
+    const Condition = struct {
65
+        idx: ?usize = null,
66
+
67
+        isa_arr: ?bool = null,
68
+        isa_bin: ?bool = null,
69
+        isa_str: ?bool = null,
70
+        isa_err: ?bool = null,
71
+        isa_int: ?bool = null,
72
+
73
+        len_gt: ?usize = null,
74
+        len_ge: ?usize = null,
75
+        len_eq: ?usize = null,
76
+        len_le: ?usize = null,
77
+        len_lt: ?usize = null,
78
+
79
+        int_gt: ?i64 = null,
80
+        int_ge: ?i64 = null,
81
+        int_eq: ?i64 = null,
82
+        int_le: ?i64 = null,
83
+        int_lt: ?i64 = null,
84
+
85
+        sint_gt: ?i64 = null,
86
+        sint_ge: ?i64 = null,
87
+        sint_eq: ?i64 = null,
88
+        sint_le: ?i64 = null,
89
+        sint_lt: ?i64 = null,
90
+
91
+        str_neq: ?[]const u8 = null,
92
+        str_eq: ?[]const u8 = null,
93
+        str_startswith: ?[]const u8 = null,
94
+        str_neq_i: ?[]const u8 = null,
95
+        str_eq_i: ?[]const u8 = null,
96
+        str_startswith_i: ?[]const u8 = null,
97
+
98
+        fn _needs_len(self: Condition) bool {
99
+            if (self.len_gt) |_| return true;
100
+            if (self.len_ge) |_| return true;
101
+            if (self.len_eq) |_| return true;
102
+            if (self.len_le) |_| return true;
103
+            if (self.len_lt) |_| return true;
104
+            return false;
105
+        }
106
+
107
+        fn _needs_int(self: Condition) bool {
108
+            if (self.int_gt) |_| return true;
109
+            if (self.int_ge) |_| return true;
110
+            if (self.int_eq) |_| return true;
111
+            if (self.int_le) |_| return true;
112
+            if (self.int_lt) |_| return true;
113
+            return false;
114
+        }
115
+
116
+        fn _needs_sint(self: Condition) bool {
117
+            if (self.sint_gt) |_| return true;
118
+            if (self.sint_ge) |_| return true;
119
+            if (self.sint_eq) |_| return true;
120
+            if (self.sint_le) |_| return true;
121
+            if (self.sint_lt) |_| return true;
122
+            return false;
123
+        }
124
+
125
+        fn _needs_str(self: Condition) bool {
126
+            if (self.str_neq) |_| return true;
127
+            if (self.str_eq) |_| return true;
128
+            if (self.str_startswith) |_| return true;
129
+            if (self.str_neq_i) |_| return true;
130
+            if (self.str_eq_i) |_| return true;
131
+            if (self.str_startswith_i) |_| return true;
132
+            return false;
133
+        }
134
+    };
135
+
136
+    pub fn _assert_cond(condition: Condition, elem: Elem, idx: ?usize) !void {
137
+        if (idx) |i| {
138
+            if (elem != .arr) return Error.ElemNotAnArray;
139
+            if (i >= elem.arr.len) return Error.ElemTooShort;
140
+            return _assert_cond(condition, elem.arr[i], null);
141
+        }
142
+
143
+        if (condition.isa_err) |goal| {
144
+            if (goal and elem != .err) return Error.ElemNotAnError;
145
+            if (!goal and elem == .err) return Error.ElemIsAnError;
146
+        }
147
+        if (condition.isa_str) |goal| {
148
+            if (goal and elem != .str) return Error.ElemNotAString;
149
+            if (!goal and elem == .str) return Error.ElemIsAString;
150
+        }
151
+        if (condition.isa_arr) |goal| {
152
+            if (goal and elem != .arr) return Error.ElemNotAnArray;
153
+            if (!goal and elem == .arr) return Error.ElemIsAnArray;
154
+        }
155
+        if (condition.isa_bin) |goal| {
156
+            if (goal and elem != .bin) return Error.ElemNotABinaryString;
157
+            if (!goal and elem == .bin) return Error.ElemIsABinaryString;
158
+        }
159
+        if (condition.isa_int) |goal| {
160
+            if (goal and elem != .arr) return Error.ElemNotAnInteger;
161
+            if (!goal and elem == .arr) return Error.ElemIsAnInteger;
162
+        }
163
+
164
+        if (condition._needs_len()) {
165
+            const val = switch (elem) {
166
+                .str => |e| e.len,
167
+                .bin => |e| e.len,
168
+                .err => |e| e.len,
169
+                .arr => |e| e.len,
170
+                else => return Error.ElemIsAScalar,
171
+            };
172
+            if (condition.len_gt) |goal| {
173
+                if (val <= goal) return Error.ElemTooShort;
174
+            }
175
+            if (condition.len_ge) |goal| {
176
+                if (val < goal) return Error.ElemTooShort;
177
+            }
178
+            if (condition.len_eq) |goal| {
179
+                if (val > goal) return Error.ElemTooLong;
180
+                if (val < goal) return Error.ElemTooShort;
181
+            }
182
+            if (condition.len_le) |goal| {
183
+                if (val > goal) return Error.ElemTooLong;
184
+            }
185
+            if (condition.len_lt) |goal| {
186
+                if (val >= goal) return Error.ElemTooLong;
187
+            }
188
+        }
189
+
190
+        if (condition._needs_int()) {
191
+            if (elem != .int) return Error.ElemNotAnInteger;
192
+            const val = elem.int;
193
+            if (condition.int_gt) |goal| {
194
+                if (val <= goal) return Error.ElemValueTooLow;
195
+            }
196
+            if (condition.int_ge) |goal| {
197
+                if (val < goal) return Error.ElemValueTooLow;
198
+            }
199
+            if (condition.int_eq) |goal| {
200
+                if (val > goal) return Error.ElemValueTooHigh;
201
+                if (val < goal) return Error.ElemValueTooLow;
202
+            }
203
+            if (condition.int_le) |goal| {
204
+                if (val > goal) return Error.ElemValueTooHigh;
205
+            }
206
+            if (condition.int_lt) |goal| {
207
+                if (val >= goal) return Error.ElemValueTooHigh;
208
+            }
209
+        }
210
+
211
+        if (condition._needs_sint()) {
212
+            if (elem != .str) return Error.ElemNotAString;
213
+            const val = std.fmt.parseInt(i64, elem.str, 10) catch return Error.ElemStringNotAnInt;
214
+            if (condition.sint_gt) |goal| {
215
+                if (val <= goal) return Error.ElemValueTooLow;
216
+            }
217
+            if (condition.sint_ge) |goal| {
218
+                if (val < goal) return Error.ElemValueTooLow;
219
+            }
220
+            if (condition.sint_eq) |goal| {
221
+                if (val > goal) return Error.ElemValueTooHigh;
222
+                if (val < goal) return Error.ElemValueTooLow;
223
+            }
224
+            if (condition.sint_le) |goal| {
225
+                if (val > goal) return Error.ElemValueTooHigh;
226
+            }
227
+            if (condition.sint_lt) |goal| {
228
+                if (val >= goal) return Error.ElemValueTooHigh;
229
+            }
230
+        }
231
+
232
+        if (condition._needs_str()) {
233
+            if (elem != .str) return Error.ElemNotAString;
234
+            const val = elem.str;
235
+            if (condition.str_eq) |goal| {
236
+                if (!std.mem.eql(u8, val, goal)) return Error.ElemNotExpected;
237
+            }
238
+            if (condition.str_neq) |goal| {
239
+                if (std.mem.eql(u8, val, goal)) return Error.ElemNotExpected;
240
+            }
241
+            if (condition.str_startswith) |goal| {
242
+                if (!std.mem.startsWith(u8, val, goal)) return Error.ElemNotExpected;
243
+            }
244
+            if (condition.str_eq_i) |goal| {
245
+                if (!util.eql_i(goal, val)) return Error.ElemNotExpected;
246
+            }
247
+            if (condition.str_neq_i) |goal| {
248
+                if (util.eql_i(goal, val)) return Error.ElemNotExpected;
249
+            }
250
+            if (condition.str_startswith_i) |goal| {
251
+                if (!util.startswith_i(goal, val)) return Error.ElemNotExpected;
252
+            }
253
+        }
254
+    }
255
+
256
+    pub fn assert_cond(self: ElemTree, condition: Condition) !void {
257
+        const elem = self.root orelse return Error.NoElem;
258
+        try _assert_cond(condition, elem.*, condition.idx);
259
+    }
260
+
261
+    pub fn assert_arr_len(self: ElemTree, len: usize) !void {
262
+        const elem = self.root orelse return Error.NoElem;
263
+        if (elem.* != .arr) return Error.ElemNotAnArray;
264
+        if (elem.arr.len < len) return Error.ElemTooShort;
265
+        if (elem.arr.len > len) return Error.ElemTooLong;
266
+    }
267
+
268
+    pub fn assert_bin_len_ge(self: ElemTree, len: usize) !void {
269
+        const elem = self.root orelse return Error.NoElem;
270
+        if (elem.* != .bin) return Error.ElemNotAString;
271
+        if (elem.arr.len < len) return Error.ElemTooShort;
272
+    }
273
+
274
+    pub fn assert_eql_at(self: ElemTree, idx: usize, text: []const u8) !void {
275
+        const elem = self.root orelse return Error.NoElem;
276
+        if (elem.* != .arr) return Error.ElemNotAnArray;
277
+        if (idx >= elem.arr.len) return Error.ElemTooShort;
278
+        const e = elem.arr[idx];
279
+        if (e != .str) return Error.ElemNotAString;
280
+        if (!util.eql_i(text, e.str)) return Error.ElemNotExpected;
281
+    }
282
+
283
+    pub fn assert_ok(self: ElemTree) !void {
284
+        try self.assert_cond(.{ .isa_err = false });
285
+    }
286
+
287
+    pub fn assert_parsed_int_eql_at(self: ElemTree, idx: usize, number: i64) !void {
288
+        const elem = self.root orelse return Error.NoElem;
289
+        if (elem.* != .arr) return Error.ElemNotAnArray;
290
+        if (idx >= elem.arr.len) return Error.ElemTooShort;
291
+        const e = elem.arr[idx];
292
+        if (e != .str) return Error.ElemNotAnArray;
293
+        const value = try std.fmt.parseInt(i64, e.str, 10);
294
+        if (value < number) return Error.ElemValueTooLow;
295
+        if (value > number) return Error.ElemValueTooHigh;
296
+    }
297
+
298
+    pub fn assert_startswith(self: ElemTree, text: []const u8) !void {
299
+        const elem = self.root orelse return Error.NoElem;
300
+        if (elem.* != .str) return Error.ElemNotAString;
301
+        if (!util.startswith_i(text, elem.str)) return Error.ElemNotExpected;
302
+    }
303
+
304
+    pub fn parse_int_at(self: ElemTree, idx: usize) !i64 {
305
+        try self.assert_cond(.{ .isa_str = true, .idx = idx });
306
+        return try std.fmt.parseInt(i64, self.root.?.*.arr[idx].str, 10);
307
+    }
308
+};
309
+
310
+pub const NBS = Elem{ .nbs = null };
311
+
312
+pub const ELEM_MAX_DEPTH: usize = 128;
313
+
314
+pub fn deep_copy_elem(elem: *const Elem, allocator: std.mem.Allocator, frames: usize) !*Elem {
315
+    if (frames < 1) return error.ValueTooComplex;
316
+    const new_ptr = try allocator.create(Elem);
317
+    switch (elem.*) {
318
+        .null => |value| new_ptr.* = Elem{ .null = value },
319
+        .nbs => |value| new_ptr.* = Elem{ .nbs = value },
320
+        .bool => |value| new_ptr.* = Elem{ .bool = value },
321
+        .int => |value| new_ptr.* = Elem{ .int = value },
322
+        .double => |value| new_ptr.* = Elem{ .double = value },
323
+        .str => |values| new_ptr.* = Elem{ .str = try allocator.dupe(u8, values) },
324
+        .bin => |values| new_ptr.* = Elem{ .bin = try allocator.dupe(u8, values) },
325
+        .err => |values| new_ptr.* = Elem{ .err = try allocator.dupe(u8, values) },
326
+        .arr => |values| {
327
+            const items = try allocator.alloc(Elem, values.len);
328
+            for (values, 0..) |*value, idx| {
329
+                const new_item_ptr = try deep_copy_elem(value, allocator, frames - 1);
330
+                items[idx] = new_item_ptr.*;
331
+            }
332
+            new_ptr.* = Elem{ .arr = items };
333
+        },
334
+    }
335
+    return new_ptr;
336
+}
337
+
338
+pub fn deep_fmt_elem(elem: *const Elem, writer: std.io.AnyWriter, frames: usize) !void {
339
+    if (frames < 1) return error.ValueTooComplex;
340
+    if (frames == ELEM_MAX_DEPTH) {
341
+        try writer.writeAll("Elem{");
342
+    } else {
343
+        try writer.writeAll(".{");
344
+    }
345
+    switch (elem.*) {
346
+        .null => try writer.print(".null", .{}),
347
+        .nbs => try writer.print(".nbs", .{}),
348
+        .bool => |value| try writer.print(".bool={}", .{value}),
349
+        .int => |value| try writer.print(".int={d}", .{value}),
350
+        .double => |value| try writer.print(".double={d}", .{value}),
351
+        .str => |value| try util.qstr(value, writer),
352
+        .bin => |value| try util.qblob(value, writer),
353
+        .err => |value| try writer.print(".err=\"{s}\"", .{value}),
354
+        .arr => |values| {
355
+            try writer.writeByte('[');
356
+            for (values, 0..) |*value, i| {
357
+                if (i > 0) try writer.writeByte(',');
358
+                try deep_fmt_elem(value, writer, frames - 1);
359
+            }
360
+            try writer.print("]", .{});
361
+        },
362
+    }
363
+    try writer.writeByte('}');
364
+}
365
+
366
+pub fn deep_free_elem(elem: *const Elem, allocator: std.mem.Allocator, frames: usize) !void {
367
+    if (frames < 1) return error.ValueTooComplex;
368
+    switch (elem.*) {
369
+        .str => |values| allocator.free(values),
370
+        .bin => |values| allocator.free(values),
371
+        .err => |values| allocator.free(values),
372
+        .arr => |values| for (values) |*value_ptr| try deep_free_elem(value_ptr, allocator, frames - 1),
373
+        else => {},
374
+    }
375
+    allocator.destroy(elem);
376
+}
377
+
378
+const Overstep = enum {
379
+    data_inside_prohibited_buff,
380
+    data_outside_required_buff,
381
+    data_mixed_layout,
382
+    items_inside_prohibited_buff,
383
+    items_outside_required_buff,
384
+    items_mixed_layout,
385
+    none,
386
+};
387
+
388
+fn find_overstep_sli(
389
+    comptime T: type,
390
+    buff_rng: util.Range,
391
+    comptime should_own: bool,
392
+    sli: []const T,
393
+) Overstep {
394
+    const items_rng = util.range_from_sli(T, sli) orelse return .none;
395
+    const items_rel = util.range_rel(items_rng, buff_rng) catch unreachable;
396
+    switch (items_rel) {
397
+        .inside, .same => if (!should_own) return .items_inside_prohibited_buff,
398
+        .left, .right => if (should_own) return .items_outside_required_buff,
399
+        else => return .items_outside_required_buff,
400
+    }
401
+    if (T == Elem) {
402
+        for (sli) |*item| {
403
+            const overstep = find_overstep_elem(buff_rng, should_own, item);
404
+            if (overstep != .none) return overstep;
405
+        }
406
+    }
407
+    return .none;
408
+}
409
+
410
+fn find_overstep_elem(
411
+    buff_rng: util.Range,
412
+    comptime should_own: bool,
413
+    elem: *const Elem,
414
+) Overstep {
415
+    const data_rng = util.range_from_ptr(Elem, elem);
416
+    // first, check elem's own memory
417
+    const own_rel = util.range_rel(data_rng, buff_rng) catch unreachable;
418
+    switch (own_rel) {
419
+        .inside, .same => if (!should_own) return .data_inside_prohibited_buff,
420
+        .left, .right => if (should_own) return .data_outside_required_buff,
421
+        else => return .data_mixed_layout,
422
+    }
423
+    // then, check any memory of elems
424
+    switch (elem.*) {
425
+        .nbs, .null, .bool, .int, .double => return .none,
426
+        .err, .str, .bin => |values| return find_overstep_sli(u8, buff_rng, should_own, values),
427
+        .arr => |values| return find_overstep_sli(Elem, buff_rng, should_own, values),
428
+    }
429
+    return .none;
430
+}
431
+
432
+fn assert_elem_ownership(
433
+    buff: []u8,
434
+    comptime should_own: bool,
435
+    elem: *const Elem,
436
+) !void {
437
+    const buff_rng = util.range_from_sli(u8, buff) orelse return error.InvalidBuff;
438
+    const overstep = find_overstep_elem(buff_rng, should_own, elem);
439
+    return switch (overstep) {
440
+        .data_inside_prohibited_buff => error.DataInsideProhibitedBuff,
441
+        .data_outside_required_buff => error.DataOutsideRequiredBuff,
442
+        .data_mixed_layout => error.DataMixedLayout,
443
+        .items_inside_prohibited_buff => error.ItemInsideProhibitedBuff,
444
+        .items_outside_required_buff => error.ItemOutsideRequiredBuff,
445
+        .items_mixed_layout => error.ItemMixedLayout,
446
+        .none => {},
447
+    };
448
+}
449
+
450
+fn assert_clones_ok(elem: *const Elem) !void {
451
+    const buff_a = try std.heap.page_allocator.alloc(u8, 1024);
452
+    const buff_b = try std.heap.page_allocator.alloc(u8, 1024);
453
+    defer std.heap.page_allocator.free(buff_a);
454
+    defer std.heap.page_allocator.free(buff_b);
455
+    var fba_a = std.heap.FixedBufferAllocator.init(buff_a);
456
+    var fba_b = std.heap.FixedBufferAllocator.init(buff_b);
457
+    const inter = try deep_copy_elem(elem, fba_a.allocator(), 10);
458
+    const clone = try deep_copy_elem(inter, fba_b.allocator(), 10);
459
+    try assert_elem_ownership(buff_b, true, clone);
460
+    try assert_elem_ownership(buff_a, false, clone);
461
+    try std.testing.expectEqualDeep(elem, clone);
462
+}
463
+
464
+test "deep copy" {
465
+    try assert_clones_ok(&Elem{ .null = {} });
466
+    try assert_clones_ok(&Elem{ .nbs = {} });
467
+    try assert_clones_ok(&Elem{ .bool = false });
468
+    try assert_clones_ok(&Elem{ .int = 123 });
469
+    try assert_clones_ok(&Elem{ .double = 123.45 });
470
+    try assert_clones_ok(&Elem{ .err = "" });
471
+    try assert_clones_ok(&Elem{ .err = "F" });
472
+    try assert_clones_ok(&Elem{ .err = "FOO" });
473
+    try assert_clones_ok(&Elem{ .str = "" });
474
+    try assert_clones_ok(&Elem{ .str = "F" });
475
+    try assert_clones_ok(&Elem{ .str = "FOO" });
476
+    try assert_clones_ok(&Elem{ .arr = &.{} });
477
+    try assert_clones_ok(&Elem{ .arr = &.{Elem{ .null = {} }} });
478
+    try assert_clones_ok(&Elem{ .arr = &.{Elem{ .arr = &.{Elem{ .null = {} }} }} });
479
+    try assert_clones_ok(&Elem{
480
+        .arr = &.{
481
+            Elem{ .int = 123 },
482
+            Elem{ .arr = &.{} },
483
+            Elem{ .arr = &.{
484
+                Elem{ .str = "" },
485
+                Elem{ .str = "def" },
486
+            } },
487
+            Elem{ .str = "abc" },
488
+        },
489
+    });
490
+}

+ 167
- 0
src/redis/logger.zig 查看文件

@@ -0,0 +1,167 @@
1
+const std = @import("std");
2
+
3
+const Server = @import("server.zig").Server;
4
+const Role = @import("task.zig").Role;
5
+
6
+const Logfile = struct {
7
+    name: []const u8,
8
+    fh: std.fs.File,
9
+    allocator: ?std.mem.Allocator,
10
+    ctx: Logger.Ctx,
11
+
12
+    fn deinit(self: Logfile) void {
13
+        if (self.allocator) |a| a.free(self.name);
14
+        self.fh.close();
15
+    }
16
+
17
+    fn _open_fh(dir: []const u8, name: []const u8) !std.fs.File {
18
+        const dh = try std.fs.cwd().makeOpenPath(dir, .{});
19
+        return dh.createFile(name, .{}) catch |e| switch (e) {
20
+            error.PathAlreadyExists => return try dh.openFile(name, .{}),
21
+            else => return e,
22
+        };
23
+    }
24
+
25
+    fn try_init(dir: []const u8, ctx: Logger.Ctx, allocator: ?std.mem.Allocator) ?Logfile {
26
+        const task_fn_fmt = "task-{s}{d:0>2}.log";
27
+        const name = switch (ctx) {
28
+            .root => "root.log",
29
+            .task => |c| std.fmt.allocPrint(allocator.?, task_fn_fmt, .{ c.task_id_pfx, c.task_id }) catch |e| {
30
+                std.log.warn("[LOGGER] could not format log filename: fmt={s}, task_id={d} e={!}", .{ task_fn_fmt, c.task_id, e });
31
+                return null;
32
+            },
33
+        };
34
+        const fh = _open_fh(dir, name) catch |e| {
35
+            std.log.warn("[LOGGER] could not open file: {s}/{s}, e={!}", .{ dir, name, e });
36
+            return null;
37
+        };
38
+        return .{
39
+            .name = name,
40
+            .fh = fh,
41
+            .allocator = allocator,
42
+            .ctx = ctx,
43
+        };
44
+    }
45
+
46
+    fn try_write(self: Logfile, comptime sigil: u8, msg: []const u8) void {
47
+        const w = self.fh.writer();
48
+        w.print("{c}: [{any}] {s}\n", .{ sigil, self.ctx, msg }) catch {
49
+            std.log.warn("[LOGGER] failed to write to log file: name='{s}'", .{self.name});
50
+        };
51
+    }
52
+};
53
+
54
+pub const Logger = struct {
55
+    options: Options,
56
+    ctx: Ctx,
57
+    logfile: ?Logfile,
58
+
59
+    const Options = struct {
60
+        debug: bool = false,
61
+        verbose: bool = false,
62
+        logdir: ?[]const u8,
63
+    };
64
+
65
+    pub const TaskCtx = struct {
66
+        task_id: usize,
67
+        task_id_pfx: []const u8,
68
+        thread_id: ?std.Thread.Id,
69
+        handle: Server.ConnectionPool.Handle,
70
+        address: std.net.Address,
71
+        stream: std.net.Stream,
72
+        role: Role,
73
+    };
74
+
75
+    pub const Ctx = union(enum) {
76
+        task: TaskCtx,
77
+        root: void,
78
+        pub fn format(self: Ctx, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
79
+            _ = fmt;
80
+            _ = options;
81
+            switch (self) {
82
+                .root => try writer.writeAll("t=ROOT"),
83
+                .task => |ctx| {
84
+                    try writer.print("t={s}{d}", .{ ctx.task_id_pfx, ctx.task_id });
85
+                    if (ctx.thread_id) |thread_id| {
86
+                        try writer.print(" T={any}", .{thread_id});
87
+                    } else {
88
+                        try writer.writeAll(" T=MAIN");
89
+                    }
90
+                    try writer.print(
91
+                        " c={any} a={any} h={any} r={s}",
92
+                        .{
93
+                            ctx.handle,
94
+                            ctx.address,
95
+                            ctx.stream.handle,
96
+                            @tagName(ctx.role),
97
+                        },
98
+                    );
99
+                },
100
+            }
101
+        }
102
+    };
103
+
104
+    pub fn init_for_task(options: Options, ctx: TaskCtx, allocator: std.mem.Allocator) Logger {
105
+        const ctx_ = .{ .task = ctx };
106
+        return .{
107
+            .options = options,
108
+            .ctx = ctx_,
109
+            .logfile = if (options.logdir) |logdir| Logfile.try_init(logdir, ctx_, allocator) else null,
110
+        };
111
+    }
112
+
113
+    pub fn init_root(options: Options) Logger {
114
+        const ctx = .{ .root = {} };
115
+        return .{
116
+            .options = options,
117
+            .ctx = ctx,
118
+            .logfile = if (options.logdir) |logdir| Logfile.try_init(logdir, ctx, null) else null,
119
+        };
120
+    }
121
+
122
+    pub fn deinit(self: Logger) void {
123
+        if (self.logfile) |logfile| logfile.deinit();
124
+    }
125
+
126
+    pub fn set_thread_id(self: *Logger, thread_id: std.Thread.Id) void {
127
+        switch (self.ctx) {
128
+            .root => unreachable,
129
+            .task => self.ctx.task.thread_id = thread_id,
130
+        }
131
+    }
132
+
133
+    fn try_write(self: Logger, comptime sigil: u8, msg: []const u8) void {
134
+        if (self.logfile == null) return;
135
+        self.logfile.?.try_write(sigil, msg);
136
+    }
137
+
138
+    pub fn debug(self: Logger, comptime fmt: []const u8, args: anytype) void {
139
+        if (!self.options.debug) return;
140
+        var arr: [1024]u8 = undefined;
141
+        const msg = std.fmt.bufPrint(@as([]u8, &arr), fmt, args) catch return;
142
+        self.try_write('D', msg);
143
+        std.log.debug("[{any}] {s}", .{ self.ctx, msg });
144
+    }
145
+
146
+    pub fn err(self: Logger, comptime fmt: []const u8, args: anytype) void {
147
+        var arr: [1024]u8 = undefined;
148
+        const msg = std.fmt.bufPrint(@as([]u8, &arr), fmt, args) catch return;
149
+        self.try_write('E', msg);
150
+        std.log.err("[{any}] {s}", .{ self.ctx, msg });
151
+    }
152
+
153
+    pub fn info(self: Logger, comptime fmt: []const u8, args: anytype) void {
154
+        if (!self.options.verbose) return;
155
+        var arr: [1024]u8 = undefined;
156
+        const msg = std.fmt.bufPrint(@as([]u8, &arr), fmt, args) catch return;
157
+        self.try_write('I', msg);
158
+        std.log.info("[{any}] {s}", .{ self.ctx, msg });
159
+    }
160
+
161
+    pub fn warn(self: Logger, comptime fmt: []const u8, args: anytype) void {
162
+        var arr: [1024]u8 = undefined;
163
+        const msg = std.fmt.bufPrint(@as([]u8, &arr), fmt, args) catch return;
164
+        self.try_write('W', msg);
165
+        std.log.warn("[{any}] {s}", .{ self.ctx, msg });
166
+    }
167
+};

+ 1074
- 0
src/redis/rdb.zig
文件差異過大導致無法顯示
查看文件


+ 279
- 0
src/redis/replication.zig 查看文件

@@ -0,0 +1,279 @@
1
+const std = @import("std");
2
+
3
+const core = @import("core.zig");
4
+const util = @import("../util.zig");
5
+
6
+const ConnectionHandle = @import("server.zig").Server.ConnectionPool.Handle;
7
+const Server = @import("server.zig").Server;
8
+const ServerConfig = @import("server.zig").Config;
9
+const Task = @import("task.zig").Task;
10
+const HandlerResult = @import("worker.zig").HandlerResult;
11
+const EMPTY_RDB = @import("rdb.zig").EMPTY_RDB;
12
+
13
+pub const Capability = enum {
14
+    psync2,
15
+};
16
+
17
+const Error = error{ TooManyReplicas, MemoryError, AlreadyExists, QueueFull, QueueEmpty };
18
+
19
+const CapabilitySet = struct {
20
+    psync2: bool = false,
21
+};
22
+
23
+pub const Request = union(enum) {
24
+    propagate: core.ElemTree,
25
+    getack: struct { elem: *const core.Elem, timeout_ms: u64 },
26
+    shutdown: void,
27
+
28
+    pub fn format(self: Request, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
29
+        _ = fmt;
30
+        _ = options;
31
+        try writer.writeAll("Request{");
32
+        switch (self) {
33
+            .propagate => |et| try writer.print(".propagate={s}", .{util.qb(et.raw_bytes)}),
34
+            .getack => |v| try writer.print(".getack={{timeout_ms={d}}}", .{v.timeout_ms}),
35
+            .shutdown => try writer.writeAll(".shutdown"),
36
+        }
37
+        try writer.writeByte('}');
38
+    }
39
+};
40
+
41
+const Response = union(enum) {
42
+    err: anyerror,
43
+    ack_offset: u64,
44
+    sent: void,
45
+
46
+    pub fn format(self: Response, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
47
+        _ = fmt;
48
+        _ = options;
49
+        try writer.writeAll("Response{");
50
+        switch (self) {
51
+            .err => |e| try writer.print(".err={!}", .{e}),
52
+            .ack_offset => |o| try writer.print(".ack_offset={d}", .{o}),
53
+            .sent => try writer.writeAll(".sent"),
54
+        }
55
+        try writer.writeByte('}');
56
+    }
57
+};
58
+
59
+pub const Replica = struct {
60
+    id: Id,
61
+    connection_h: ConnectionHandle,
62
+    capabilities: CapabilitySet,
63
+    port: ?u16,
64
+    last_propagation: ?i64 = null,
65
+
66
+    pub const Id = ConnectionHandle;
67
+
68
+    pub fn set_capas(self: *Replica, capas: []Capability) void {
69
+        for (capas) |capa| switch (capa) {
70
+            .psync2 => self.capabilities.psync2 = true,
71
+        };
72
+    }
73
+
74
+    pub fn set_port(self: *Replica, port: u16) !void {
75
+        self.port = port;
76
+    }
77
+};
78
+
79
+pub const MasterState = struct {
80
+    replid: u160,
81
+    repl_offset: u64,
82
+    replicas: ReplicaPool,
83
+
84
+    pub fn mkreplica(self: *MasterState, connection_h: ConnectionHandle) !*Replica {
85
+        if (self.replicas.get_ptr(connection_h)) |ptr| return ptr;
86
+        const new_id = try self.replicas.create(connection_h);
87
+        return self.replicas.get_ptr(new_id) orelse return Error.MemoryError;
88
+    }
89
+};
90
+
91
+pub const ReplicaState = struct {
92
+    master_host: []const u8,
93
+    master_port: u16,
94
+    master_last_replid: ?u160 = null,
95
+    master_last_offset: ?u64 = null,
96
+};
97
+
98
+pub const State = union(enum) {
99
+    master: MasterState,
100
+    replica: ReplicaState,
101
+
102
+    pub fn init(config: ServerConfig, allocator: std.mem.Allocator) !State {
103
+        if (config.replication_host) |h| {
104
+            return .{ .replica = .{
105
+                .master_host = h,
106
+                .master_port = config.replication_port,
107
+            } };
108
+        } else {
109
+            const r = std.crypto.random.int(u160);
110
+            return .{ .master = .{
111
+                .replid = r,
112
+                .repl_offset = 0,
113
+                .replicas = try ReplicaPool.init(
114
+                    config.replica_count,
115
+                    allocator,
116
+                ),
117
+            } };
118
+        }
119
+    }
120
+    pub fn deinit(self: State) void {
121
+        switch (self) {
122
+            .master => |m| m.replicas.deinit(),
123
+            .replica => {},
124
+        }
125
+    }
126
+};
127
+
128
+pub const ReplicaPool = struct {
129
+    buff: []?Replica,
130
+    allocator: std.mem.Allocator,
131
+    bq: BatchQueue,
132
+
133
+    pub const BatchQueue = util.BatchQueue(Request, Response, Replica.Id);
134
+
135
+    pub const PtrIterator = struct {
136
+        cur: usize = 0,
137
+        pool: ReplicaPool,
138
+        pub fn next(self: *PtrIterator) ?*Replica {
139
+            while (self.cur < self.pool.buff.len) {
140
+                if (self.pool.buff[self.cur]) |*item_ptr| {
141
+                    self.cur += 1;
142
+                    return item_ptr;
143
+                }
144
+                self.cur += 1;
145
+            }
146
+            return null;
147
+        }
148
+    };
149
+
150
+    fn init(max_replicas: usize, allocator: std.mem.Allocator) !ReplicaPool {
151
+        const buff = try allocator.alloc(?Replica, max_replicas);
152
+        for (0..max_replicas) |i| buff[i] = null;
153
+        return ReplicaPool{
154
+            .buff = buff,
155
+            .allocator = allocator,
156
+            .bq = try BatchQueue.init(max_replicas, allocator),
157
+        };
158
+    }
159
+
160
+    fn deinit(self: ReplicaPool) void {
161
+        self.allocator.free(self.buff);
162
+        self.bq.deinit();
163
+    }
164
+
165
+    fn _locate(self: ReplicaPool, id: Replica.Id) ?usize {
166
+        for (0..self.buff.len) |idx| {
167
+            if (self.buff[idx] == null) continue;
168
+            const item_ptr = &self.buff[idx].?;
169
+            if (item_ptr.id == id) return idx;
170
+        }
171
+        return null;
172
+    }
173
+
174
+    fn _locate_free(self: ReplicaPool) ?usize {
175
+        for (0..self.buff.len) |idx| {
176
+            if (self.buff[idx] == null) return idx;
177
+        }
178
+        return null;
179
+    }
180
+
181
+    pub fn ptr_iterator(self: ReplicaPool) PtrIterator {
182
+        return PtrIterator{ .pool = self };
183
+    }
184
+
185
+    pub fn count(self: ReplicaPool) usize {
186
+        var out: usize = 0;
187
+        for (self.buff) |item| out += if (item == null) 0 else 1;
188
+        return out;
189
+    }
190
+
191
+    fn create(self: *ReplicaPool, connection_h: ConnectionHandle) !Replica.Id {
192
+        const id = @as(Replica.Id, connection_h);
193
+        if (self._locate(id)) |_| return Error.AlreadyExists;
194
+        const free_idx = self._locate_free() orelse return Error.TooManyReplicas;
195
+        try self.bq.add_ctx(id);
196
+        self.buff[free_idx] = Replica{
197
+            .id = id,
198
+            .connection_h = connection_h,
199
+            .capabilities = .{},
200
+            .port = null,
201
+            .last_propagation = null,
202
+        };
203
+        return id;
204
+    }
205
+
206
+    pub fn get_ptr(self: ReplicaPool, id: Replica.Id) ?*Replica {
207
+        if (self._locate(id)) |idx| return &self.buff[idx].?;
208
+        return null;
209
+    }
210
+
211
+    pub fn remove(self: *ReplicaPool, id: Replica.Id) void {
212
+        const idx = self._locate(id) orelse return;
213
+        self.buff[idx] = null;
214
+        self.bq.delete_ctx(id);
215
+    }
216
+};
217
+
218
+/// Handshake with master and start handling incoming commands,
219
+/// mostly propagations.
220
+pub fn handle_replica2master(task: *Task, server: *Server) !HandlerResult {
221
+    task.logger.info("* replica2master handler listening", .{});
222
+    try task.handshake();
223
+    while (true) {
224
+        const et = try task.rcv_et();
225
+        defer et.deinit();
226
+        if (et.root == null) break; // connection closed by peeer
227
+        const cmd = try task.parse_cmd_or_reject(et.root.?) orelse break; // we rejected
228
+        const cmd_result = cmd.run(task, server);
229
+        if (cmd.should(.ack_master)) {
230
+            try task.send_elem(cmd_result.elem);
231
+        }
232
+        try task.bump_replica_offset(et.raw_bytes.len);
233
+    }
234
+    return .{};
235
+}
236
+
237
+/// Finalize handshake by sending RDB and begin handling requests
238
+/// for this replica.
239
+///
240
+/// The replica to serve is identified by connection handler
241
+/// *task.connection_h*; once the connection is removed from the connection
242
+/// pool, the handler will no longer resolve, ie. we assume replica has been
243
+/// removed and we quit.
244
+pub fn handle_master2replica(task: *Task, server: *Server) !HandlerResult {
245
+    _ = server;
246
+    const ms = try task.require_master_state();
247
+    var reqs_done: usize = 0;
248
+    try task.send_bin_elem(EMPTY_RDB);
249
+    task.logger.info("* master2replica handler ready", .{});
250
+    while (ms.replicas.get_ptr(task.connection_h)) |replica| { // "while we still have replica to care about"
251
+        task.logger.info("* waiting on replication request pipe", .{});
252
+        const ctx = try ms.replicas.bq.wait_request(replica.id);
253
+        task.logger.info("* received replication request: {any}", .{ctx.request});
254
+        switch (ctx.request) {
255
+            .shutdown => break,
256
+            .propagate => |et| {
257
+                task.send_blob(et.raw_bytes) catch |e| {
258
+                    task.logger.err("! failed to send element: {any} due to {!}", .{ et, e });
259
+                    try ctx.respond(.{ .err = e });
260
+                    break;
261
+                };
262
+                try ctx.respond(.{ .sent = {} });
263
+            },
264
+            .getack => |req| {
265
+                const offset = task.getack_one(req.elem) catch |e| {
266
+                    task.logger.err("! failed to get ACK within: {any} ns due to {!}", .{ req.timeout_ms, e });
267
+                    try ctx.respond(.{ .err = e });
268
+                    break;
269
+                };
270
+                try ctx.respond(.{ .ack_offset = @intCast(offset) });
271
+            },
272
+        }
273
+        task.logger.info("* processed replication request: {any}", .{ctx.request});
274
+        reqs_done +|= 1;
275
+    }
276
+    task.logger.debug(": reqs_done={d}", .{reqs_done});
277
+    task.logger.info("* shutting down master2replica propagation handler", .{});
278
+    return .{};
279
+}

+ 851
- 0
src/redis/resp.zig 查看文件

@@ -0,0 +1,851 @@
1
+const std = @import("std");
2
+
3
+const util = @import("../util.zig");
4
+
5
+const Elem = @import("core.zig").Elem;
6
+const ElemTree = @import("core.zig").ElemTree;
7
+const ElemType = @import("core.zig").ElemType;
8
+
9
+pub const Sigil = enum {
10
+    a_bulk_string, // bulk string (Elem.str) or null bulk string (Elem.nbs)
11
+    binary_string,
12
+    // ^^ Elem.bin: encoded like bulk string but without \r\n at the end (sigh...).
13
+    //
14
+    // This one is impossible to detect from the sigil so caller must know to expect
15
+    // binary string and use parse_binary_string() function.   (This happens eg. with
16
+    // the PSYNC2 command.)
17
+    simple_error,
18
+    simple_string,
19
+    integer,
20
+    null,
21
+    array,
22
+    nothing,
23
+};
24
+
25
+fn str_contains_only_digits(haystack: []const u8) bool {
26
+    const idx = std.mem.indexOfNone(u8, haystack, "0123456789");
27
+    return (idx == null);
28
+}
29
+
30
+fn str_contains_any(haystack: []const u8, needles: []const u8) bool {
31
+    const idx = std.mem.indexOfAny(u8, haystack, needles);
32
+    return (idx != null);
33
+}
34
+
35
+const MAX_READ_BYTES = 1024 * 1024;
36
+
37
+const Parser = struct {
38
+    reader: std.io.AnyReader,
39
+    arena: std.heap.ArenaAllocator,
40
+    raw: std.ArrayList(u8),
41
+    // ^^ we could be smarter and have all .err, .str and .bin refer to .raw,
42
+    // saving lots of memory, but it would require rewrite of some of the parser
43
+    // logic
44
+
45
+    pub const Error = error{
46
+        _NotImplemented,
47
+        ReaderError,
48
+        BadSigil,
49
+        BadSize,
50
+        EmptyCommand,
51
+        MissingData,
52
+        MissingBlob,
53
+        MissingSigil,
54
+        MissingArrayElement,
55
+        MissingArrayValue,
56
+        MissingSize,
57
+        MissingCRLF,
58
+        BadIntegerValue,
59
+        OutOfMemory,
60
+        InternalError,
61
+    };
62
+
63
+    fn init(reader: std.io.AnyReader, allocator: std.mem.Allocator) Parser {
64
+        var p: Parser = undefined;
65
+        p.arena = std.heap.ArenaAllocator.init(allocator);
66
+        p.raw = std.ArrayList(u8).init(p.arena.allocator());
67
+        p.reader = reader;
68
+        return p;
69
+    }
70
+
71
+    fn _pop_size(self: *Parser) Error!?usize {
72
+        // Size is positive int except for special-case of `-1`, which
73
+        // is only valid after bulk string sigil (`$`) and signifies
74
+        // that the element is "null bulk string" (apparently an older
75
+        // solution for "null").
76
+        // For simplicity we just report size as `null`.
77
+        const word = try self._stream_until_crlf();
78
+        defer self.arena.allocator().free(word);
79
+        if (std.mem.eql(u8, word, "-1")) return null;
80
+        if (!str_contains_only_digits(word)) return Error.BadSize;
81
+        const num = std.fmt.parseInt(usize, word, 10) catch return Error.BadSize;
82
+        return num;
83
+    }
84
+
85
+    fn _load_blob(self: *Parser, len: usize) Error![]u8 {
86
+        const dest = try self.arena.allocator().alloc(u8, len);
87
+        var rcvd_bytes_n: usize = 0;
88
+        while (rcvd_bytes_n < dest.len) {
89
+            const new_bytes_n = self.reader.read(dest[rcvd_bytes_n..]) catch |e| {
90
+                std.log.err("Parser._load_blob():e={}", .{e});
91
+                return Error.ReaderError;
92
+            };
93
+            if (new_bytes_n == 0) break;
94
+            rcvd_bytes_n = std.math.add(usize, rcvd_bytes_n, new_bytes_n) catch return Error.InternalError;
95
+        }
96
+        if (rcvd_bytes_n < dest.len) return Error.MissingData;
97
+        self.raw.appendSlice(dest) catch return Error.OutOfMemory;
98
+        return dest;
99
+    }
100
+
101
+    fn _stream_until_crlf(self: *Parser) Error![]u8 {
102
+        var dest = std.ArrayList(u8).init(self.arena.allocator());
103
+        var got_crlf = false;
104
+        while (true) {
105
+            // .streamUntilDelimiter() will drop the LF
106
+            self.reader.streamUntilDelimiter(dest.writer(), '\n', null) catch |e| switch (e) {
107
+                error.EndOfStream => break,
108
+                else => {
109
+                    std.log.err("Parser._stream_until_crlf():e={}", .{e});
110
+                    return Error.ReaderError;
111
+                },
112
+            };
113
+            if (dest.items.len > 0 and dest.getLast() == '\r') {
114
+                // CRLF confirmed; drop the CR as well
115
+                _ = dest.pop();
116
+                got_crlf = true;
117
+                break;
118
+            }
119
+            // this was just LF: put it back in
120
+            dest.append('\n') catch return Error.OutOfMemory;
121
+        }
122
+        if (!got_crlf) return Error.MissingCRLF;
123
+        self.raw.appendSlice(dest.items) catch return Error.OutOfMemory;
124
+        self.raw.appendSlice("\r\n") catch return Error.OutOfMemory;
125
+        return dest.toOwnedSlice() catch return Error.OutOfMemory;
126
+    }
127
+
128
+    fn __require_crlf(self: *Parser) Error!void {
129
+        const b1 = self.reader.readByte() catch return Error.MissingCRLF;
130
+        const b2 = self.reader.readByte() catch return Error.MissingCRLF;
131
+        self.raw.append(b1) catch return Error.OutOfMemory;
132
+        self.raw.append(b2) catch return Error.OutOfMemory;
133
+        if (b1 == '\r' and b2 == '\n') return;
134
+        return Error.MissingCRLF;
135
+    }
136
+
137
+    fn _popelem_array(self: *Parser) Error!Elem {
138
+        const len = try self._pop_size() orelse return Error.BadSize;
139
+        var togo = len;
140
+        var elem_al = std.ArrayList(Elem).init(self.arena.allocator());
141
+        while (togo > 0) {
142
+            const elem = try self._popelem() orelse return Error.MissingArrayElement;
143
+            try elem_al.append(elem);
144
+            togo = togo - 1;
145
+        }
146
+        return Elem{ .arr = elem_al.toOwnedSlice() catch return Error.OutOfMemory };
147
+    }
148
+
149
+    fn _popelem(self: *Parser) Error!?Elem {
150
+        switch (try self.__pop_sigil()) {
151
+            .nothing => return null,
152
+            .array => return try self._popelem_array(),
153
+            .integer => return try self._popelem_integer(),
154
+            .null => return try self._popelem_null(),
155
+            .simple_error => return try self._popelem_simple_error(),
156
+            .a_bulk_string => return try self._popelem_a_bulk_string(),
157
+            .simple_string => return try self._popelem_simple_string(),
158
+            .binary_string => unreachable,
159
+        }
160
+    }
161
+
162
+    fn _popelem_a_bulk_string(self: *Parser) Error!Elem {
163
+        if (try self._pop_size()) |len| {
164
+            // proper bulk_string with actual data
165
+            const buff = try self._load_blob(len);
166
+            try self.__require_crlf();
167
+            return Elem{ .str = buff };
168
+        } else {
169
+            // size was `-1` -> NBS
170
+            return Elem{ .nbs = null };
171
+        }
172
+    }
173
+
174
+    fn _popelem_binary_string(self: *Parser) Error!?Elem {
175
+        const sigil = try self.__pop_sigil();
176
+        if (sigil == .nothing) return null;
177
+        if (sigil != .a_bulk_string) return Error.BadSigil;
178
+        const len = try self._pop_size() orelse return Error.BadSize;
179
+        const buff = try self._load_blob(len);
180
+        return Elem{ .bin = buff };
181
+    }
182
+
183
+    fn _popelem_integer(self: *Parser) Error!Elem {
184
+        const bytes = try self._stream_until_crlf();
185
+        const int = std.fmt.parseInt(i64, bytes, 10) catch return Error.BadIntegerValue;
186
+        return Elem{ .int = int };
187
+    }
188
+
189
+    fn _popelem_null(self: *Parser) Error!Elem {
190
+        try self.__require_crlf();
191
+        return Elem{ .null = null };
192
+    }
193
+
194
+    fn _popelem_simple_error(self: *Parser) Error!Elem {
195
+        const bytes = try self._stream_until_crlf();
196
+        return Elem{ .err = bytes };
197
+    }
198
+
199
+    fn _popelem_simple_string(self: *Parser) Error!Elem {
200
+        const bytes = try self._stream_until_crlf();
201
+        return Elem{ .str = bytes };
202
+    }
203
+
204
+    fn __pop_sigil(self: *Parser) Error!Sigil {
205
+        const sigil = self.reader.readByte() catch return .nothing;
206
+        try self.raw.append(sigil);
207
+        switch (sigil) {
208
+            '*' => return .array,
209
+            '+' => return .simple_string,
210
+            '-' => return .simple_error,
211
+            '$' => return .a_bulk_string,
212
+            ':' => return .integer,
213
+            '_' => return .null,
214
+            else => return Error.BadSigil,
215
+        }
216
+    }
217
+
218
+    fn _as_tree(self: *Parser, root: ?Elem) Error!ElemTree {
219
+        return ElemTree{
220
+            .root = r: {
221
+                if (root == null) break :r null;
222
+                const ptr = self.arena.allocator().create(Elem) catch return Error.OutOfMemory;
223
+                ptr.* = root.?;
224
+                break :r ptr;
225
+            },
226
+            .raw_bytes = self.raw.toOwnedSlice() catch return Error.OutOfMemory,
227
+            .arena = self.arena,
228
+        };
229
+    }
230
+
231
+    fn parse(self: *Parser) Error!ElemTree {
232
+        //      const EP = self.arena.allocator().create(Elem) catch return Error.OutOfMemory;
233
+        //      std.log.err("EP: {*}", .{EP});
234
+        return try self._as_tree(try self._popelem());
235
+    }
236
+
237
+    fn parse_binary_string(self: *Parser) Error!ElemTree {
238
+        return try self._as_tree(try self._popelem_binary_string());
239
+    }
240
+};
241
+
242
+pub fn parse_et(reader: std.io.AnyReader, allocator: std.mem.Allocator) Parser.Error!ElemTree {
243
+    var parser = Parser.init(reader, allocator);
244
+    return try parser.parse();
245
+}
246
+
247
+/// measure how much bytes would *elem* take encoded
248
+pub fn predict_write(elem: Elem, allocator: std.mem.Allocator) !usize {
249
+    var al = std.ArrayList(u8).init(allocator);
250
+    defer al.deinit();
251
+    try write(elem, al.writer());
252
+    return al.items.len;
253
+}
254
+
255
+pub const ElemStreamTimer = struct {
256
+    allocator: std.mem.Allocator,
257
+    poller: Poller,
258
+
259
+    const StreamEnum = enum { file };
260
+    const Poller = std.io.Poller(StreamEnum);
261
+
262
+    pub const Error = error{ StreamClosed, StreamTimeout };
263
+    const ParseFn = *const fn (std.io.AnyReader, std.mem.Allocator) anyerror!ElemTree;
264
+
265
+    pub fn init(stream: std.net.Stream, allocator: std.mem.Allocator) ElemStreamTimer {
266
+        return .{
267
+            .poller = std.io.poll(
268
+                allocator,
269
+                StreamEnum,
270
+                .{ .file = std.fs.File{ .handle = stream.handle } },
271
+            ),
272
+            .allocator = allocator,
273
+        };
274
+    }
275
+
276
+    pub fn deinit(self: *ElemStreamTimer) void {
277
+        self.poller.deinit();
278
+    }
279
+
280
+    fn _get_slice(self: *ElemStreamTimer, timeout_ns: u64) ![]const u8 {
281
+        if (!(try self.poller.pollTimeout(timeout_ns))) return Error.StreamClosed;
282
+        const fifo = self.poller.fifo(.file);
283
+        if (fifo.readableLength() == 0) return Error.StreamTimeout;
284
+        return fifo.readableSlice(0);
285
+    }
286
+
287
+    fn _get_et(self: *ElemStreamTimer, parse_fn: ParseFn, timeout_ns: u64, allocator: std.mem.Allocator) !ElemTree {
288
+        var remaining_ns = timeout_ns;
289
+        while (true) {
290
+            if (remaining_ns == 0) return Error.StreamTimeout;
291
+            const start_nst = std.time.nanoTimestamp();
292
+            const data = try self._get_slice(remaining_ns);
293
+            var stream = std.io.fixedBufferStream(data);
294
+            const et = parse_fn(stream.reader().any(), allocator) catch continue;
295
+            const dur_ns = std.time.nanoTimestamp() - start_nst;
296
+            remaining_ns -|= @intCast(dur_ns);
297
+            if (et.root == null) unreachable; // .get_slice should never return slice of 0;
298
+            return et;
299
+        }
300
+    }
301
+
302
+    pub fn get_bin_et(self: *ElemStreamTimer, timeout_ns: u64, allocator: std.mem.Allocator) !ElemTree {
303
+        return self._get_et(
304
+            parse_binary_string_et,
305
+            timeout_ns,
306
+            allocator,
307
+        );
308
+    }
309
+
310
+    pub fn get_et(self: *ElemStreamTimer, timeout_ns: u64, allocator: std.mem.Allocator) !ElemTree {
311
+        return self._get_et(
312
+            parse_et,
313
+            timeout_ns,
314
+            allocator,
315
+        );
316
+    }
317
+};
318
+
319
+pub fn parse_binary_string_et(reader: std.io.AnyReader, allocator: std.mem.Allocator) Parser.Error!ElemTree {
320
+    var parser = Parser.init(reader, allocator);
321
+    return try parser.parse_binary_string();
322
+}
323
+
324
+pub fn require_blob(reader: std.io.AnyReader, allocator: std.mem.Allocator) Parser.Error![]const u8 {
325
+    var parser = Parser.init(reader, allocator);
326
+    const et = try parser.parse_binary_string();
327
+    const elem = et.root orelse error.MissingBlob;
328
+    return elem.bin;
329
+}
330
+
331
+pub fn write(data: Elem, writer: anytype) !void {
332
+    switch (data) {
333
+        .bool, .double, .null => return Parser.Error._NotImplemented,
334
+        .int => {
335
+            try std.fmt.format(writer, ":{d}\r\n", .{data.int});
336
+        },
337
+        .arr => {
338
+            try std.fmt.format(writer, "*{d}\r\n", .{data.arr.len});
339
+            for (data.arr) |item| {
340
+                try write(item, writer);
341
+            }
342
+        },
343
+        .nbs => {
344
+            try writer.writeAll("$-1\r\n");
345
+        },
346
+        .err => {
347
+            try std.fmt.format(writer, "-{s}\r\n", .{data.err});
348
+        },
349
+        .bin => {
350
+            try std.fmt.format(writer, "${d}\r\n", .{data.bin.len});
351
+            try writer.writeAll(data.bin);
352
+        },
353
+        .str => {
354
+            if (str_contains_any(data.str, "\r\n+")) {
355
+                try std.fmt.format(writer, "${d}\r\n", .{data.str.len});
356
+                try writer.writeAll(data.str);
357
+                try writer.writeAll("\r\n");
358
+            } else {
359
+                try std.fmt.format(writer, "+{s}\r\n", .{data.str});
360
+            }
361
+        },
362
+    }
363
+}
364
+
365
+const ParseTestCase = struct {
366
+    buff: []const u8,
367
+    result: anyerror!ElemTree,
368
+    gpa: std.heap.GeneralPurposeAllocator(.{}),
369
+
370
+    /// Initialize test case with source buffer *buff*
371
+    fn init(buff: []const u8) ParseTestCase {
372
+        var fbs = std.io.fixedBufferStream(buff);
373
+        var gpa = std.heap.GeneralPurposeAllocator(.{}){};
374
+        return ParseTestCase{
375
+            .buff = buff,
376
+            .gpa = gpa,
377
+            .result = parse_et(fbs.reader().any(), gpa.allocator()),
378
+        };
379
+    }
380
+
381
+    /// Initialize test case with source buffer *buff* and parse_binary_string()
382
+    fn init_bin(buff: []const u8) ParseTestCase {
383
+        var fbs = std.io.fixedBufferStream(buff);
384
+        var gpa = std.heap.GeneralPurposeAllocator(.{}){};
385
+        return ParseTestCase{
386
+            .buff = buff,
387
+            .gpa = gpa,
388
+            .result = parse_binary_string_et(fbs.reader().any(), gpa.allocator()),
389
+        };
390
+    }
391
+
392
+    fn _get_root(self: ParseTestCase) !Elem {
393
+        const tree = try self.result;
394
+        try std.testing.expectStringStartsWith(self.buff, tree.raw_bytes);
395
+        const root_ptr = tree.root orelse return error.TestFailNoRoot;
396
+        return root_ptr.*;
397
+    }
398
+
399
+    fn _get_arr_item(self: ParseTestCase, idx: usize) !Elem {
400
+        const elem = try self._get_root();
401
+        std.testing.expect(elem == ElemType.arr) catch return error.TestFailWrongTag;
402
+        std.testing.expect(elem.arr.len >= (idx + 1)) catch return error.TestFailWrongSize;
403
+        return elem.arr[idx];
404
+    }
405
+
406
+    /// Expect Parser.Error *oracle* thrown when parsing source buffer
407
+    fn expect_error(self: ParseTestCase, oracle: Parser.Error) !void {
408
+        try std.testing.expectEqual(oracle, self.result);
409
+    }
410
+
411
+    /// Expect null, ie. no elem at all found in the string (i.e. empty string was parsed)
412
+    fn expect_no_elem(self: ParseTestCase) !void {
413
+        const tree = try self.result;
414
+        try std.testing.expectEqualStrings(self.buff, tree.raw_bytes);
415
+        try std.testing.expectEqual(null, tree.root);
416
+    }
417
+
418
+    /// Expect ElemType.nbs
419
+    fn expect_nbs(self: ParseTestCase) !void {
420
+        const elem = try self._get_root();
421
+        std.testing.expect(elem == ElemType.nbs) catch return error.TestFailWrongTag;
422
+    }
423
+
424
+    /// Expect ElemType.null
425
+    fn expect_null(self: ParseTestCase) !void {
426
+        const elem = try self._get_root();
427
+        std.testing.expect(elem == ElemType.null) catch return error.TestFailWrongTag;
428
+    }
429
+
430
+    /// Expect ElemType.bool with bool value *oracle*
431
+    fn expect_bool(self: ParseTestCase, oracle: bool) !void {
432
+        const elem = try self._get_root();
433
+        std.testing.expect(elem == ElemType.bool) catch return error.TestFailWrongTag;
434
+        try std.testing.expectEqual(oracle, elem.bool);
435
+    }
436
+
437
+    /// Expect ElemType.double with f64 value *oracle*
438
+    fn expect_double(self: ParseTestCase, oracle: f64) !void {
439
+        const elem = try self._get_root();
440
+        std.testing.expect(elem == ElemType.double) catch return error.TestFailWrongTag;
441
+        try std.testing.expectEqual(oracle, elem.double);
442
+    }
443
+
444
+    /// Expect ElemType.int with i64 value *oracle*
445
+    fn expect_int(self: ParseTestCase, oracle: i64) !void {
446
+        const elem = try self._get_root();
447
+        std.testing.expect(elem == ElemType.int) catch return error.TestFailWrongTag;
448
+        try std.testing.expectEqual(oracle, elem.int);
449
+    }
450
+
451
+    /// Expect ElemType.err with string value *oracle*
452
+    fn expect_err(self: ParseTestCase, oracle: []const u8) !void {
453
+        const elem = try self._get_root();
454
+        std.testing.expect(elem == ElemType.err) catch return error.TestFailWrongTag;
455
+        try std.testing.expectEqualStrings(oracle, elem.err);
456
+    }
457
+
458
+    /// Expect ElemType.str with string value *oracle*
459
+    fn expect_str(self: ParseTestCase, oracle: []const u8) !void {
460
+        const elem = try self._get_root();
461
+        std.testing.expect(elem == ElemType.str) catch return error.TestFailWrongTag;
462
+        try std.testing.expectEqualStrings(oracle, elem.str);
463
+    }
464
+
465
+    /// Expect ElemType.bin with string value *oracle*
466
+    fn expect_bin(self: ParseTestCase, oracle: []const u8) !void {
467
+        const elem = try self._get_root();
468
+        std.testing.expect(elem == ElemType.bin) catch return error.TestFailWrongTag;
469
+        try std.testing.expectEqualStrings(oracle, elem.bin);
470
+    }
471
+
472
+    /// Expect ElemType.arr of length of *oracle*
473
+    fn expect_arr_len(self: ParseTestCase, oracle: usize) !void {
474
+        const elem = try self._get_root();
475
+        std.testing.expect(elem == ElemType.arr) catch return error.TestFailWrongTag;
476
+        try std.testing.expectEqual(oracle, elem.arr.len);
477
+    }
478
+
479
+    /// Expect ElemType.arr with ElemType.str at *idx*, with string value *oracle*
480
+    fn expect_str_at(self: ParseTestCase, idx: usize, oracle: []const u8) !void {
481
+        const elem = try self._get_arr_item(idx);
482
+        std.testing.expect(elem == ElemType.str) catch return error.TestFailWrongTag;
483
+        try std.testing.expectEqualStrings(oracle, elem.str);
484
+    }
485
+
486
+    /// Expect ElemType.arr with ElemType.bin at *idx*, with string value *oracle*
487
+    fn expect_bin_at(self: ParseTestCase, idx: usize, oracle: []const u8) !void {
488
+        const elem = try self._get_arr_item(idx);
489
+        std.testing.expect(elem == ElemType.bin) catch return error.TestFailWrongTag;
490
+        try std.testing.expectEqualStrings(oracle, elem.bin);
491
+    }
492
+
493
+    /// Expect ElemType.arr with ElemType.int at *idx*, with i64 value *oracle*
494
+    fn expect_int_at(self: ParseTestCase, idx: usize, oracle: i64) !void {
495
+        const elem = try self._get_arr_item(idx);
496
+        std.testing.expect(elem == ElemType.int) catch return error.TestFailWrongTag;
497
+        try std.testing.expectEqual(oracle, elem.int);
498
+    }
499
+
500
+    /// Expect ElemType.arr with ElemType.int at *idx*, with i64 value *oracle*
501
+    fn expect_null_at(self: ParseTestCase, idx: usize) !void {
502
+        const elem = try self._get_arr_item(idx);
503
+        std.testing.expect(elem == ElemType.null) catch return error.TestFailWrongTag;
504
+    }
505
+};
506
+
507
+test "parse()-NEW: nothing" {
508
+    const mkcase = ParseTestCase.init;
509
+    try mkcase("").expect_no_elem();
510
+}
511
+
512
+test "parse()-NEW: null" {
513
+    const mkcase = ParseTestCase.init;
514
+    try mkcase("_").expect_error(Parser.Error.MissingCRLF);
515
+    try mkcase("_\n").expect_error(Parser.Error.MissingCRLF);
516
+    try mkcase("_\r").expect_error(Parser.Error.MissingCRLF);
517
+    try mkcase("_\n\r").expect_error(Parser.Error.MissingCRLF);
518
+    try mkcase("_\r\n").expect_null();
519
+}
520
+
521
+test "parse()-NEW: nbs" {
522
+    const mkcase = ParseTestCase.init;
523
+    try mkcase("$").expect_error(Parser.Error.MissingCRLF);
524
+    try mkcase("$-").expect_error(Parser.Error.MissingCRLF);
525
+    try mkcase("$-1").expect_error(Parser.Error.MissingCRLF);
526
+    try mkcase("$-1\n").expect_error(Parser.Error.MissingCRLF);
527
+    try mkcase("$-1\r").expect_error(Parser.Error.MissingCRLF);
528
+    try mkcase("$-1\r\n").expect_nbs();
529
+    try mkcase("$-1\r\nX").expect_nbs();
530
+}
531
+
532
+test "parse()-NEW: simple error" {
533
+    const mkcase = ParseTestCase.init;
534
+    try mkcase("-").expect_error(Parser.Error.MissingCRLF);
535
+    try mkcase("-\n").expect_error(Parser.Error.MissingCRLF);
536
+    try mkcase("-\r").expect_error(Parser.Error.MissingCRLF);
537
+    try mkcase("-\n\r").expect_error(Parser.Error.MissingCRLF);
538
+    try mkcase("-\r\n").expect_err("");
539
+    try mkcase("-A\r\n").expect_err("A");
540
+    try mkcase("-AB\r\n").expect_err("AB");
541
+    try mkcase("-+\r\n").expect_err("+");
542
+}
543
+
544
+test "parse()-NEW: simple string" {
545
+    const mkcase = ParseTestCase.init;
546
+    try mkcase("+").expect_error(Parser.Error.MissingCRLF);
547
+    try mkcase("+\n").expect_error(Parser.Error.MissingCRLF);
548
+    try mkcase("+\r").expect_error(Parser.Error.MissingCRLF);
549
+    try mkcase("+\n\r").expect_error(Parser.Error.MissingCRLF);
550
+    try mkcase("+\r\n").expect_str("");
551
+    try mkcase("+A\r\n").expect_str("A");
552
+    try mkcase("+AB\r\n").expect_str("AB");
553
+    try mkcase("++\r\n").expect_str("+");
554
+}
555
+
556
+test "parse()-NEW: integer" {
557
+    const mkcase = ParseTestCase.init;
558
+    try mkcase(":").expect_error(Parser.Error.MissingCRLF);
559
+    try mkcase(":\n").expect_error(Parser.Error.MissingCRLF);
560
+    try mkcase(":\r").expect_error(Parser.Error.MissingCRLF);
561
+    try mkcase(":\n\r").expect_error(Parser.Error.MissingCRLF);
562
+    try mkcase(":\r\n").expect_error(Parser.Error.BadIntegerValue);
563
+    try mkcase(":A\r\n").expect_error(Parser.Error.BadIntegerValue);
564
+    try mkcase(":+A\r\n").expect_error(Parser.Error.BadIntegerValue);
565
+    try mkcase("::1\r\n").expect_error(Parser.Error.BadIntegerValue);
566
+    try mkcase(":1A\r\n").expect_error(Parser.Error.BadIntegerValue);
567
+    try mkcase(":A1\r\n").expect_error(Parser.Error.BadIntegerValue);
568
+    try mkcase(":0x01\r\n").expect_error(Parser.Error.BadIntegerValue);
569
+    try mkcase(":0\r\n").expect_int(0);
570
+    try mkcase(":1\r\n").expect_int(1);
571
+    try mkcase(":123\r\n").expect_int(123);
572
+    try mkcase(":-1\r\n").expect_int(-1);
573
+    try mkcase(":+1\r\n").expect_int(1);
574
+}
575
+
576
+test "parse()-NEW: bulk string" {
577
+    const mkcase = ParseTestCase.init;
578
+    try mkcase("$").expect_error(Parser.Error.MissingCRLF);
579
+    try mkcase("$1").expect_error(Parser.Error.MissingCRLF);
580
+    try mkcase("$X").expect_error(Parser.Error.MissingCRLF);
581
+    try mkcase("$XY").expect_error(Parser.Error.MissingCRLF);
582
+    try mkcase("$\r\n").expect_error(Parser.Error.BadSize);
583
+    try mkcase("$X\r\n").expect_error(Parser.Error.BadSize);
584
+    try mkcase("$XY\r\n").expect_error(Parser.Error.BadSize);
585
+    try mkcase("$1X\r\n").expect_error(Parser.Error.BadSize);
586
+    try mkcase("$1_000\r\n").expect_error(Parser.Error.BadSize);
587
+    try mkcase("$-2\r\n").expect_error(Parser.Error.BadSize); // -1 would be valid as NBS
588
+    try mkcase("$0x01\r\n").expect_error(Parser.Error.BadSize);
589
+    try mkcase("$1\r\n\r\n").expect_error(Parser.Error.MissingCRLF);
590
+    try mkcase("$1\r\nA").expect_error(Parser.Error.MissingCRLF);
591
+    try mkcase("$2\r\n\r\n").expect_error(Parser.Error.MissingCRLF);
592
+    try mkcase("$2\r\nA").expect_error(Parser.Error.MissingData);
593
+    try mkcase("$2\r\nAB").expect_error(Parser.Error.MissingCRLF);
594
+    try mkcase("$0\r\nA").expect_error(Parser.Error.MissingCRLF);
595
+    try mkcase("$0\r\nA\r\n").expect_error(Parser.Error.MissingCRLF);
596
+    try mkcase("$0\r\n\r\nX").expect_str("");
597
+    try mkcase("$0\r\n\r\n").expect_str("");
598
+    try mkcase("$1\r\nA\r\n").expect_str("A");
599
+    try mkcase("$1\r\n\r\r\n").expect_str("\r");
600
+    try mkcase("$1\r\n\n\r\n").expect_str("\n");
601
+    try mkcase("$2\r\nAB\r\n").expect_str("AB");
602
+    try mkcase("$2\r\n\r\n\r\n").expect_str("\r\n");
603
+    try mkcase("$9\r\n$3\r\nFOO\r\n\r\n").expect_str("$3\r\nFOO\r\n"); // including own code safely
604
+}
605
+
606
+test "parse()-NEW: binary string" {
607
+    const mkcase = ParseTestCase.init_bin;
608
+    try mkcase("$").expect_error(Parser.Error.MissingCRLF);
609
+    try mkcase("$1").expect_error(Parser.Error.MissingCRLF);
610
+    try mkcase("$X").expect_error(Parser.Error.MissingCRLF);
611
+    try mkcase("$XY").expect_error(Parser.Error.MissingCRLF);
612
+    try mkcase("$\r\n").expect_error(Parser.Error.BadSize);
613
+    try mkcase("$X\r\n").expect_error(Parser.Error.BadSize);
614
+    try mkcase("$XY\r\n").expect_error(Parser.Error.BadSize);
615
+    try mkcase("$1X\r\n").expect_error(Parser.Error.BadSize);
616
+    try mkcase("$1_000\r\n").expect_error(Parser.Error.BadSize);
617
+    try mkcase("$-2\r\n").expect_error(Parser.Error.BadSize); // -1 would be valid as NBS
618
+    try mkcase("$0x01\r\n").expect_error(Parser.Error.BadSize);
619
+    try mkcase("$0\r\n").expect_bin("");
620
+    try mkcase("$0\r\nA").expect_bin(""); // 'A' ignored
621
+    try mkcase("$1\r\nA").expect_bin("A");
622
+    try mkcase("$1\r\nAB").expect_bin("A"); // 'B' ignored
623
+    try mkcase("$1\r\n").expect_error(Parser.Error.MissingData);
624
+    try mkcase("$2\r\nAB").expect_bin("AB");
625
+    try mkcase("$2\r\nABC").expect_bin("AB"); // 'C' ignored
626
+    try mkcase("$2\r\nA").expect_error(Parser.Error.MissingData);
627
+}
628
+
629
+test "parse()-NEW: array: header" {
630
+    const mkcase = ParseTestCase.init;
631
+    try mkcase("*").expect_error(Parser.Error.MissingCRLF);
632
+    try mkcase("*1").expect_error(Parser.Error.MissingCRLF);
633
+    try mkcase("*12").expect_error(Parser.Error.MissingCRLF);
634
+    try mkcase("*X").expect_error(Parser.Error.MissingCRLF);
635
+    try mkcase("*XY").expect_error(Parser.Error.MissingCRLF);
636
+    try mkcase("*\r\n").expect_error(Parser.Error.BadSize);
637
+    try mkcase("*XY\r\n").expect_error(Parser.Error.BadSize);
638
+    try mkcase("*1X\r\n").expect_error(Parser.Error.BadSize);
639
+    try mkcase("*1_000\r\n").expect_error(Parser.Error.BadSize);
640
+    try mkcase("*-1\r\n").expect_error(Parser.Error.BadSize);
641
+    try mkcase("*0x01\r\n").expect_error(Parser.Error.BadSize);
642
+}
643
+
644
+test "parse()-NEW: array: empty" {
645
+    const mkcase = ParseTestCase.init;
646
+    try mkcase("*0\r").expect_error(Parser.Error.MissingCRLF);
647
+    try mkcase("*0\n").expect_error(Parser.Error.MissingCRLF);
648
+    try mkcase("*0\n\r").expect_error(Parser.Error.MissingCRLF);
649
+    try mkcase("*0\r\nX").expect_arr_len(0);
650
+    try mkcase("*0\r\nXY").expect_arr_len(0);
651
+    try mkcase("*0\r\n").expect_arr_len(0);
652
+}
653
+
654
+test "parse()-NEW: array: item header error" {
655
+    const mkcase = ParseTestCase.init;
656
+    try mkcase("*1\r\n").expect_error(Parser.Error.MissingArrayElement);
657
+    try mkcase("*1\r\nX").expect_error(Parser.Error.BadSigil);
658
+    try mkcase("*1\r\n*").expect_error(Parser.Error.MissingCRLF);
659
+}
660
+
661
+test "parse()-NEW: array: bulk string" {
662
+    const mkcase = ParseTestCase.init;
663
+    var case: ParseTestCase = undefined;
664
+
665
+    case = mkcase("*1\r\n$0\r\n\r\n");
666
+    try case.expect_arr_len(1);
667
+    try case.expect_str_at(0, "");
668
+    case = mkcase("*1\r\n$1\r\nA\r\n");
669
+    try case.expect_arr_len(1);
670
+    try case.expect_str_at(0, "A");
671
+    case = mkcase("*1\r\n$2\r\nAB\r\n");
672
+    try case.expect_arr_len(1);
673
+    try case.expect_str_at(0, "AB");
674
+    case = mkcase("*1\r\n$2\r\n\r\n\r\n");
675
+    try case.expect_arr_len(1);
676
+    try case.expect_str_at(0, "\r\n");
677
+}
678
+
679
+test "parse()-NEW: array: simple string header errors" {
680
+    const mkcase = ParseTestCase.init;
681
+    try mkcase("*1\r\n+").expect_error(Parser.Error.MissingCRLF);
682
+    try mkcase("*1\r\n+\n").expect_error(Parser.Error.MissingCRLF);
683
+    try mkcase("*1\r\n+\r").expect_error(Parser.Error.MissingCRLF);
684
+    try mkcase("*1\r\n+\n\r").expect_error(Parser.Error.MissingCRLF);
685
+}
686
+
687
+test "parse()-NEW: array: single, simple string" {
688
+    const mkcase = ParseTestCase.init;
689
+    var case: ParseTestCase = undefined;
690
+    case = mkcase("*1\r\n+\r\n");
691
+    try case.expect_arr_len(1);
692
+    try case.expect_str_at(0, "");
693
+    case = mkcase("*1\r\n+A\r\n");
694
+    try case.expect_arr_len(1);
695
+    try case.expect_str_at(0, "A");
696
+    case = mkcase("*1\r\n+AB\r\n");
697
+    try case.expect_arr_len(1);
698
+    try case.expect_str_at(0, "AB");
699
+}
700
+
701
+test "parse()-NEW: array: multiple: simple string, simple string" {
702
+    const mkcase = ParseTestCase.init;
703
+    var case: ParseTestCase = undefined;
704
+    case = mkcase("*2\r\n+\r\n+\r\n");
705
+    try case.expect_arr_len(2);
706
+    try case.expect_str_at(0, "");
707
+    try case.expect_str_at(1, "");
708
+    case = mkcase("*2\r\n+A\r\n+\r\n");
709
+    try case.expect_arr_len(2);
710
+    try case.expect_str_at(0, "A");
711
+    try case.expect_str_at(1, "");
712
+    case = mkcase("*2\r\n+\r\n+B\r\n");
713
+    try case.expect_arr_len(2);
714
+    try case.expect_str_at(0, "");
715
+    try case.expect_str_at(1, "B");
716
+    case = mkcase("*2\r\n+Foo bar\r\n+baz!\r\n");
717
+    try case.expect_arr_len(2);
718
+    try case.expect_str_at(0, "Foo bar");
719
+    try case.expect_str_at(1, "baz!");
720
+}
721
+
722
+test "parse()-NEW: array: multiple: simple string, bulk string" {
723
+    const mkcase = ParseTestCase.init;
724
+    var case: ParseTestCase = undefined;
725
+    case = mkcase("*2\r\n+\r\n$0\r\n\r\n");
726
+    try case.expect_arr_len(2);
727
+    try case.expect_str_at(0, "");
728
+    try case.expect_str_at(1, "");
729
+    case = mkcase("*2\r\n+A\r\n$0\r\n\r\n");
730
+    try case.expect_arr_len(2);
731
+    try case.expect_str_at(0, "A");
732
+    try case.expect_str_at(1, "");
733
+    case = mkcase("*2\r\n+\r\n$1\r\nB\r\n");
734
+    try case.expect_arr_len(2);
735
+    try case.expect_str_at(0, "");
736
+    try case.expect_str_at(1, "B");
737
+    case = mkcase("*2\r\n+Foo bar\r\n$4\r\nbaz!\r\n");
738
+    try case.expect_arr_len(2);
739
+    try case.expect_str_at(0, "Foo bar");
740
+    try case.expect_str_at(1, "baz!");
741
+}
742
+
743
+test "parse()-NEW: array: multiple: bulk string, simple string" {
744
+    const mkcase = ParseTestCase.init;
745
+    var case: ParseTestCase = undefined;
746
+    case = mkcase("*2\r\n$0\r\n\r\n+\r\n");
747
+    try case.expect_arr_len(2);
748
+    try case.expect_str_at(0, "");
749
+    try case.expect_str_at(1, "");
750
+    case = mkcase("*2\r\n+A\r\n$0\r\n\r\n");
751
+    try case.expect_arr_len(2);
752
+    try case.expect_str_at(0, "A");
753
+    try case.expect_str_at(1, "");
754
+    case = mkcase("*2\r\n$0\r\n\r\n+B\r\n");
755
+    try case.expect_arr_len(2);
756
+    try case.expect_str_at(0, "");
757
+    try case.expect_str_at(1, "B");
758
+    case = mkcase("*2\r\n$7\r\nFoo bar\r\n+baz!\r\n");
759
+    try case.expect_arr_len(2);
760
+    try case.expect_str_at(0, "Foo bar");
761
+    try case.expect_str_at(1, "baz!");
762
+}
763
+
764
+test "parse()-NEW: array: multiple: bulk string, bulk string" {
765
+    const mkcase = ParseTestCase.init;
766
+    var case: ParseTestCase = undefined;
767
+    case = mkcase("*2\r\n$0\r\n\r\n$0\r\n\r\n");
768
+    try case.expect_arr_len(2);
769
+    try case.expect_str_at(0, "");
770
+    try case.expect_str_at(1, "");
771
+    case = mkcase("*2\r\n$1\r\nA\r\n$0\r\n\r\n");
772
+    try case.expect_arr_len(2);
773
+    try case.expect_str_at(0, "A");
774
+    try case.expect_str_at(1, "");
775
+    case = mkcase("*2\r\n$0\r\n\r\n$1\r\nB\r\n");
776
+    try case.expect_arr_len(2);
777
+    try case.expect_str_at(0, "");
778
+    try case.expect_str_at(1, "B");
779
+    case = mkcase("*2\r\n$7\r\nFoo bar\r\n$4\r\nbaz!\r\n");
780
+    try case.expect_arr_len(2);
781
+    try case.expect_str_at(0, "Foo bar");
782
+    try case.expect_str_at(1, "baz!");
783
+}
784
+
785
+test "parse()-NEW: array: multiple: simple string, null" {
786
+    const mkcase = ParseTestCase.init;
787
+    var case: ParseTestCase = undefined;
788
+    case = mkcase("*2\r\n+\r\n_\r\n");
789
+    try case.expect_arr_len(2);
790
+    try case.expect_str_at(0, "");
791
+    try case.expect_null_at(1);
792
+    case = mkcase("*2\r\n+A\r\n_\r\n");
793
+    try case.expect_arr_len(2);
794
+    try case.expect_str_at(0, "A");
795
+    try case.expect_null_at(1);
796
+    case = mkcase("*2\r\n+Foo bar\r\n_\r\n");
797
+    try case.expect_arr_len(2);
798
+    try case.expect_str_at(0, "Foo bar");
799
+    try case.expect_null_at(1);
800
+}
801
+
802
+test "parse()-NEW: array: multiple: null, simple string" {
803
+    const mkcase = ParseTestCase.init;
804
+    var case: ParseTestCase = undefined;
805
+    case = mkcase("*2\r\n_\r\n+\r\n");
806
+    try case.expect_arr_len(2);
807
+    try case.expect_null_at(0);
808
+    try case.expect_str_at(1, "");
809
+    case = mkcase("*2\r\n_\r\n+A\r\n");
810
+    try case.expect_arr_len(2);
811
+    try case.expect_null_at(0);
812
+    try case.expect_str_at(1, "A");
813
+    case = mkcase("*2\r\n_\r\n+baz!\r\n");
814
+    try case.expect_arr_len(2);
815
+    try case.expect_null_at(0);
816
+    try case.expect_str_at(1, "baz!");
817
+}
818
+
819
+test "parse()-NEW: array: multiple: bulk string, null" {
820
+    const mkcase = ParseTestCase.init;
821
+    var case: ParseTestCase = undefined;
822
+    case = mkcase("*2\r\n$0\r\n\r\n_\r\n");
823
+    try case.expect_arr_len(2);
824
+    try case.expect_str_at(0, "");
825
+    try case.expect_null_at(1);
826
+    case = mkcase("*2\r\n+A\r\n_\r\n");
827
+    try case.expect_arr_len(2);
828
+    try case.expect_str_at(0, "A");
829
+    try case.expect_null_at(1);
830
+    case = mkcase("*2\r\n+Foo bar\r\n_\r\n");
831
+    try case.expect_arr_len(2);
832
+    try case.expect_str_at(0, "Foo bar");
833
+    try case.expect_null_at(1);
834
+}
835
+
836
+test "parse()-NEW: array: multiple: null, bulk string" {
837
+    const mkcase = ParseTestCase.init;
838
+    var case: ParseTestCase = undefined;
839
+    case = mkcase("*2\r\n_\r\n$0\r\n\r\n");
840
+    try case.expect_arr_len(2);
841
+    try case.expect_null_at(0);
842
+    try case.expect_str_at(1, "");
843
+    case = mkcase("*2\r\n_\r\n$1\r\nA\r\n");
844
+    try case.expect_arr_len(2);
845
+    try case.expect_null_at(0);
846
+    try case.expect_str_at(1, "A");
847
+    case = mkcase("*2\r\n_\r\n$4\r\nbaz!\r\n");
848
+    try case.expect_arr_len(2);
849
+    try case.expect_null_at(0);
850
+    try case.expect_str_at(1, "baz!");
851
+}

+ 251
- 0
src/redis/server.zig 查看文件

@@ -0,0 +1,251 @@
1
+const std = @import("std");
2
+
3
+const util = @import("../util.zig");
4
+const resp = @import("resp.zig");
5
+
6
+const replication = @import("replication.zig");
7
+
8
+const HandlerFn = @import("worker.zig").HandlerFn;
9
+const HandlerResult = @import("worker.zig").HandlerResult;
10
+const Logger = @import("logger.zig").Logger;
11
+const Role = @import("task.zig").Role;
12
+const Store = @import("store.zig").Store;
13
+const Task = @import("task.zig").Task;
14
+const WorkerPool = @import("worker.zig").WorkerPool;
15
+
16
+pub const Config = struct {
17
+    dir: ?[]const u8,
18
+    dbfilename: []const u8,
19
+    replication_host: ?[]const u8,
20
+    replication_port: u16,
21
+    max_dump_bytes: usize,
22
+    thread_count: usize,
23
+    replica_count: usize,
24
+    replica_buff_size: usize,
25
+    replica_timeout_ms: u64,
26
+    command_timeout_ms: u64,
27
+    port: u16,
28
+    debug: bool,
29
+    verbose: bool,
30
+    logdir: ?[]const u8,
31
+};
32
+
33
+pub const Connection = struct {
34
+    stream: std.net.Stream,
35
+    address: std.net.Address,
36
+};
37
+
38
+pub const Server = struct {
39
+    config: Config,
40
+    store: Store,
41
+    logger: Logger,
42
+    allocator: std.mem.Allocator,
43
+    replication_state: replication.State,
44
+    connection_pool: ConnectionPool,
45
+    last_task_id: usize = 0,
46
+    main_thread_id: std.Thread.Id,
47
+    cmd_worker_pool: CmdWorkerPool,
48
+    replica_worker_pool: ReplicaWorkerPool,
49
+
50
+    pub const CmdWorkerPool = WorkerPool("cmd", 0xDD);
51
+    pub const ReplicaWorkerPool = WorkerPool("rep", 0xDD);
52
+    pub const ConnectionPool = util.OpaquePool(Connection, 6, 48);
53
+
54
+    pub fn create(
55
+        config: Config,
56
+        threads_buff: []u8,
57
+        replicas_buff: []u8,
58
+        allocator: std.mem.Allocator,
59
+    ) !Server {
60
+        const logger = Logger.init_root(.{
61
+            .verbose = config.verbose,
62
+            .debug = config.debug,
63
+            .logdir = config.logdir,
64
+        });
65
+
66
+        const replication_state = try replication.State.init(config, allocator);
67
+
68
+        // validate config
69
+        {
70
+            const ideal_cpus = switch (replication_state) {
71
+                .master => 1 + config.thread_count + config.replica_count,
72
+                .replica => 1 + config.thread_count + 1,
73
+            };
74
+            const have_cpus = try std.Thread.getCpuCount();
75
+            switch (replication_state) {
76
+                .master => {
77
+                    if (config.thread_count == 0) {
78
+                        logger.err("cannot work with zero threads: config.thread_count=0", .{});
79
+                        return error.InvalidConfig;
80
+                    }
81
+                    if (have_cpus < ideal_cpus)
82
+                        logger.warn(
83
+                            "not enough CPUs; commands or replication might be blocked: have {d} but need {d} + {d} + 1 (commands, replicas, main thread)",
84
+                            .{ have_cpus, config.thread_count, config.replica_count },
85
+                        );
86
+                },
87
+                .replica => {
88
+                    if (have_cpus < ideal_cpus) {
89
+                        logger.warn(
90
+                            "not enough CPUs; commands or replication might be blocked: have {d} but need {d} + 1 + 1 (commands, replication, main thread)",
91
+                            .{ have_cpus, config.thread_count },
92
+                        );
93
+                    }
94
+                },
95
+            }
96
+        }
97
+
98
+        return Server{
99
+            .config = config,
100
+            .store = Store.init(allocator),
101
+            .logger = logger,
102
+            .allocator = allocator,
103
+            .replication_state = replication_state,
104
+            .connection_pool = try ConnectionPool.init(allocator),
105
+            .cmd_worker_pool = CmdWorkerPool.init(
106
+                threads_buff,
107
+                config.thread_count,
108
+                allocator,
109
+            ),
110
+            .replica_worker_pool = ReplicaWorkerPool.init(
111
+                replicas_buff,
112
+                config.replica_count,
113
+                allocator,
114
+            ),
115
+            .main_thread_id = std.Thread.getCurrentId(),
116
+        };
117
+    }
118
+
119
+    pub fn destroy(self: *Server) void {
120
+        self.cmd_worker_pool.destroy();
121
+        self.replica_worker_pool.destroy();
122
+    }
123
+
124
+    pub fn maybe_load_dump(self: *Server, allocator: std.mem.Allocator) !void {
125
+        if (self.config.dir == null) return;
126
+        const dir = self.config.dir.?;
127
+        const dbfilename = self.config.dbfilename;
128
+        self.store.load_rdb_dump(
129
+            dir,
130
+            dbfilename,
131
+            self.config.max_dump_bytes,
132
+            self.logger,
133
+            allocator,
134
+        ) catch |e| switch (e) {
135
+            error.FileNotFound => {
136
+                self.logger.warn("? ignoring non-existent dump file: {s}/{s}", .{ dir, dbfilename });
137
+                return;
138
+            },
139
+            else => {
140
+                self.logger.err("! error while loading dump file: {any} with {s}/{s}", .{ e, dir, dbfilename });
141
+                return e;
142
+            },
143
+        };
144
+    }
145
+
146
+    fn queue_task(self: *Server, role: Role, handler_fn: HandlerFn, connection_h: ConnectionPool.Handle) !void {
147
+        const task_args = Task.Args{
148
+            .role = role,
149
+            .connection_h = connection_h,
150
+            .timeout_ms = switch (role) {
151
+                .m2r, .r2m => self.config.replica_timeout_ms,
152
+                .cmd => self.config.command_timeout_ms,
153
+            },
154
+        };
155
+        switch (role) {
156
+            .m2r, .r2m => try self.replica_worker_pool.queue(handler_fn, task_args),
157
+            .cmd => try self.cmd_worker_pool.queue(handler_fn, task_args),
158
+        }
159
+    }
160
+
161
+    pub fn create_master_connection(self: *Server) !ConnectionPool.Handle {
162
+        const list = try std.net.getAddressList(
163
+            self.allocator,
164
+            self.replication_state.replica.master_host,
165
+            self.replication_state.replica.master_port,
166
+        );
167
+        defer list.deinit();
168
+        if (list.addrs.len == 0) return error.UnknownHostName;
169
+        var stream: std.net.Stream = undefined;
170
+        for (list.addrs) |addr| {
171
+            stream = std.net.tcpConnectToAddress(addr) catch |err| switch (err) {
172
+                error.ConnectionRefused => {
173
+                    continue;
174
+                },
175
+                else => return err,
176
+            };
177
+            return try self.connection_pool.put(Connection{
178
+                .stream = stream,
179
+                .address = addr,
180
+            });
181
+        }
182
+        return std.posix.ConnectError.ConnectionRefused;
183
+    }
184
+
185
+    pub fn serve(
186
+        self: *Server,
187
+        listener: *std.net.Server,
188
+    ) !void {
189
+        try self.cmd_worker_pool.start(self);
190
+        try self.replica_worker_pool.start(self);
191
+
192
+        if (self.replication_state == .replica) {
193
+            const handle = try self.create_master_connection();
194
+            try self.queue_task(
195
+                .r2m,
196
+                replication.handle_replica2master,
197
+                handle,
198
+            );
199
+        }
200
+
201
+        var conn: std.net.Server.Connection = undefined;
202
+        while (true) {
203
+            conn = try listener.accept();
204
+            const handle = try self.connection_pool.put(Connection{
205
+                .address = conn.address,
206
+                .stream = conn.stream,
207
+            });
208
+            try self.queue_task(
209
+                .cmd,
210
+                handle_cmd,
211
+                handle,
212
+            );
213
+        }
214
+    }
215
+};
216
+
217
+/// Handle incoming RESP commands.
218
+pub fn handle_cmd(task: *Task, server: *Server) !HandlerResult {
219
+    task.logger.info("* command handler started; awaiting commands from stream", .{});
220
+    while (true) {
221
+        const et = try task.rcv_et();
222
+        if (et.root == null) break; // connection closed by peeer
223
+        const cmd = try task.parse_cmd_or_reject(et.root.?) orelse break; // we rejected
224
+        const cmd_result = cmd.run(task, server);
225
+        try task.send_elem(cmd_result.elem);
226
+
227
+        // propagate?
228
+        if (cmd.should(.propagate) and cmd_result.ok) {
229
+            try task.maybe_propagate(et);
230
+        } else {
231
+            // only deinit if no propagation is happening
232
+            defer et.deinit();
233
+        }
234
+
235
+        // keep stream as new replica?
236
+        if (cmd.should(.start_replica)) {
237
+            if (!cmd_result.ok) {
238
+                task.logger.warn("? replica propagation setup failed", .{});
239
+                break;
240
+            }
241
+            task.logger.info("* re-using connection for replica propagation", .{});
242
+            try server.queue_task(
243
+                .m2r,
244
+                replication.handle_master2replica,
245
+                task.connection_h,
246
+            );
247
+            return .{ .connection_policy = .keep };
248
+        }
249
+    }
250
+    return .{};
251
+}

+ 118
- 0
src/redis/store.zig 查看文件

@@ -0,0 +1,118 @@
1
+const std = @import("std");
2
+
3
+const core = @import("core.zig");
4
+const rdb = @import("rdb.zig");
5
+
6
+const Elem = core.Elem;
7
+const Logger = @import("logger.zig").Logger;
8
+
9
+pub const PxUpdate = union(enum) {
10
+    unset,
11
+    keep,
12
+    new: i64,
13
+};
14
+
15
+pub const Store = struct {
16
+    pxats_db: std.StringHashMap(i64),
17
+    elems_db: std.StringHashMap(*Elem),
18
+    m: std.Thread.Mutex,
19
+    allocator: std.mem.Allocator,
20
+
21
+    const Error = error{
22
+        NoPreviousTtl,
23
+    };
24
+
25
+    pub fn init(allocator: std.mem.Allocator) Store {
26
+        return Store{
27
+            .m = std.Thread.Mutex{},
28
+            .pxats_db = std.StringHashMap(i64).init(allocator),
29
+            .elems_db = std.StringHashMap(*Elem).init(allocator),
30
+            .allocator = allocator,
31
+        };
32
+    }
33
+
34
+    fn _adopt_key(self: Store, key: []const u8) ![]const u8 {
35
+        if (self.elems_db.getEntry(key)) |old_entry| {
36
+            return old_entry.key_ptr.*;
37
+        } else {
38
+            return try self.allocator.dupe(u8, key);
39
+        }
40
+    }
41
+
42
+    fn _disown_key(self: *Store, key: []const u8) void {
43
+        const old_entry = self.elems_db.getEntry(key);
44
+        if (old_entry == null) return;
45
+        self.allocator.free(old_entry.?.key_ptr.*);
46
+    }
47
+
48
+    fn _remove_if_expired(self: *Store, key: []const u8) !void {
49
+        const pxat = self.pxats_db.get(key) orelse return;
50
+        const now = std.time.milliTimestamp();
51
+        if (now < pxat) return;
52
+        try self.delete_elem(key);
53
+    }
54
+
55
+    pub fn delete_elem(self: *Store, key: []const u8) !void {
56
+        const elem = self.elems_db.get(key) orelse return;
57
+        try core.deep_free_elem(elem, self.allocator, core.ELEM_MAX_DEPTH);
58
+        _ = self.pxats_db.remove(key);
59
+        _ = self.elems_db.remove(key);
60
+        self._disown_key(key);
61
+    }
62
+
63
+    pub fn get_elem(self: *Store, key: []const u8) !?Elem {
64
+        try self._remove_if_expired(key);
65
+        const elem_ptr = self.elems_db.get(key) orelse return null;
66
+        return elem_ptr.*;
67
+    }
68
+
69
+    pub fn put_elem(self: *Store, key: []const u8, value: Elem, px_update: PxUpdate) !void {
70
+        const our_pxat = switch (px_update) {
71
+            .unset => null,
72
+            .new => px_update.new,
73
+            .keep => self.pxats_db.get(key) orelse return Error.NoPreviousTtl,
74
+        };
75
+        const our_elem = try core.deep_copy_elem(&value, self.allocator, core.ELEM_MAX_DEPTH);
76
+        const our_key = try self._adopt_key(key);
77
+        try self.elems_db.put(our_key, our_elem);
78
+        try self.pxats_db.put(our_key, our_pxat orelse return);
79
+    }
80
+
81
+    pub fn describe(self: Store, logger: Logger) void {
82
+        var n: usize = 0;
83
+        var key_iter = self.elems_db.keyIterator();
84
+        while (key_iter.next()) |key| {
85
+            n += 1;
86
+            logger.debug("Store.describe():key={s}", .{key.*});
87
+        }
88
+        logger.debug("Store.describe():n={d}", .{n});
89
+    }
90
+
91
+    pub fn load_rdb_dump(
92
+        self: *Store,
93
+        dirpath: []const u8,
94
+        dbfilename: []const u8,
95
+        max_bytes: usize,
96
+        logger: Logger,
97
+        allocator: std.mem.Allocator,
98
+    ) !void {
99
+        const dir = try std.fs.openDirAbsolute(dirpath, .{});
100
+        const fh = try dir.openFile(dbfilename, .{ .mode = .read_only });
101
+        const content = try fh.readToEndAlloc(allocator, max_bytes);
102
+        var parser = rdb.Parser.init(content, allocator);
103
+        const rdb_file = try parser.parse();
104
+        for (rdb_file.database) |sub| {
105
+            var iter = sub.values.iterator();
106
+            while (iter.next()) |rdb_entry_ptr| {
107
+                const key = rdb_entry_ptr.key_ptr.*;
108
+                const elem = rdb_entry_ptr.value_ptr.*;
109
+                const px_update = if (sub.pxats.get(key)) |pxat|
110
+                    PxUpdate{ .new = pxat }
111
+                else
112
+                    PxUpdate{ .unset = {} };
113
+                logger.info("* loading elem: {s} | {any} ({any})", .{ key, elem, px_update });
114
+                try self.put_elem(key, elem, px_update);
115
+            }
116
+        }
117
+    }
118
+};

+ 345
- 0
src/redis/task.zig 查看文件

@@ -0,0 +1,345 @@
1
+const std = @import("std");
2
+
3
+const resp = @import("resp.zig");
4
+const util = @import("../util.zig");
5
+
6
+const Command = @import("command.zig").Command;
7
+const Elem = @import("core.zig").Elem;
8
+const ElemTree = @import("core.zig").ElemTree;
9
+const Logger = @import("logger.zig").Logger;
10
+const ConnectionHandle = @import("server.zig").Server.ConnectionPool.Handle;
11
+const ReplicationState = @import("replication.zig").State;
12
+const ReplicaState = @import("replication.zig").ReplicaState;
13
+const ReplicaRequest = @import("replication.zig").Request;
14
+const ReplicationBatch = @import("replication.zig").ReplicaPool.BatchQueue.Batch;
15
+const MasterState = @import("replication.zig").MasterState;
16
+const WorkerPool = @import("worker.zig").WorkerPool;
17
+const EMPTY_RDB = @import("rdb.zig").EMPTY_RDB;
18
+
19
+pub const Role = enum { cmd, r2m, m2r };
20
+
21
+pub const Task = struct {
22
+    connection_h: ConnectionHandle,
23
+    timeout_ms: u64,
24
+    stream: std.net.Stream,
25
+    logger: Logger,
26
+    replication_state: *ReplicationState,
27
+    allocator: std.mem.Allocator,
28
+
29
+    pub const Args = struct {
30
+        role: Role,
31
+        connection_h: ConnectionHandle,
32
+        timeout_ms: u64,
33
+    };
34
+
35
+    pub fn deinit(self: *Task) void {
36
+        self.logger.debug("* done", .{});
37
+        self.logger.deinit();
38
+    }
39
+
40
+    //
41
+    // replication state access helpers
42
+    //
43
+
44
+    pub fn get_master_state(self: Task) ?*MasterState {
45
+        if (self.replication_state.* == .replica) return null;
46
+        return &self.replication_state.master;
47
+    }
48
+
49
+    pub fn get_replica_state(self: Task) ?*ReplicaState {
50
+        if (self.replication_state.* == .master) return null;
51
+        return &self.replication_state.replica;
52
+    }
53
+
54
+    pub fn require_master_state(self: Task) !*MasterState {
55
+        if (self.replication_state.* == .replica) return error.NotAMaster;
56
+        return &self.replication_state.master;
57
+    }
58
+
59
+    pub fn require_replica_state(self: Task) !*ReplicaState {
60
+        if (self.replication_state.* == .master) return error.NotAReplica;
61
+        return &self.replication_state.replica;
62
+    }
63
+
64
+    //
65
+    // basic send/receive
66
+    //
67
+
68
+    pub fn require_bin_et(self: Task) !ElemTree {
69
+        var timer = resp.ElemStreamTimer.init(self.stream, self.allocator);
70
+        defer timer.deinit();
71
+        const et = timer.get_bin_et(self.timeout_ms * std.time.ns_per_ms, self.allocator) catch |e| {
72
+            self.logger.warn("? did not receive full RESP element: {!} (timeout_ms={d})", .{ e, self.timeout_ms });
73
+            return e;
74
+        };
75
+        errdefer et.deinit();
76
+        if (et.root == null) {
77
+            self.logger.info("* connection closed by peer", .{});
78
+            return error.ConnectionClosed;
79
+        }
80
+        return et;
81
+    }
82
+
83
+    pub fn require_et(self: Task) !ElemTree {
84
+        return self.require_et_within(self.timeout_ms);
85
+    }
86
+
87
+    pub fn require_et_within(self: Task, timeout_ms: u64) !ElemTree {
88
+        var timer = resp.ElemStreamTimer.init(self.stream, self.allocator);
89
+        defer timer.deinit();
90
+        const et = timer.get_et(timeout_ms * std.time.ns_per_ms, self.allocator) catch |e| {
91
+            self.logger.warn("? did not receive full RESP element: {!} within {d} ms", .{ e, timeout_ms });
92
+            return e;
93
+        };
94
+        errdefer et.deinit();
95
+        if (et.root == null) {
96
+            self.logger.info("* connection closed by peer", .{});
97
+            return error.ConnectionClosed;
98
+        }
99
+        return et;
100
+    }
101
+
102
+    pub fn rcv_et(self: Task) !ElemTree {
103
+        const et = resp.parse_et(self.stream.reader().any(), self.allocator) catch |e| {
104
+            self.logger.err("! error parsing RESP element: {!}", .{e});
105
+            return e;
106
+        };
107
+        if (et.root == null) {
108
+            std.debug.assert(et.raw_bytes.len == 0);
109
+            self.logger.info("* connection closed by peer", .{});
110
+        } else {
111
+            self.logger.info("< {any}", .{et});
112
+        }
113
+        return et;
114
+    }
115
+
116
+    pub fn send_bin_elem(self: Task, bin: []const u8) !void {
117
+        self.logger.info(">> {s}", .{util.qb(bin)});
118
+        const w = self.stream.writer().any();
119
+        w.print("${d}\r\n{s}", .{ bin.len, bin }) catch |e| {
120
+            self.logger.err("! error sending RESP .bin: {!}", .{e});
121
+            return e;
122
+        };
123
+    }
124
+
125
+    pub fn send_blob(self: Task, blob: []const u8) !void {
126
+        self.logger.info(">>> {s}", .{util.qb(blob)});
127
+        const w = self.stream.writer().any();
128
+        w.writeAll(blob) catch |e| {
129
+            self.logger.err("! error sending raw blob: {!}", .{e});
130
+            return e;
131
+        };
132
+    }
133
+
134
+    pub fn send_elem(self: Task, cmd_elem: Elem) !void {
135
+        self.logger.info("> {any}", .{cmd_elem});
136
+        resp.write(cmd_elem, self.stream.writer().any()) catch |e| {
137
+            self.logger.err("! error sending RESP element: {!}", .{e});
138
+            return e;
139
+        };
140
+    }
141
+
142
+    //
143
+    // simple operations
144
+    //
145
+
146
+    pub fn parse_cmd_or_reject(self: Task, cmd_elem: *Elem) !?Command {
147
+        return Command.parse(cmd_elem.*, self.logger, self.allocator) catch |e| {
148
+            try self.send_elem(Elem{ .err = @errorName(e) });
149
+            self.logger.err("! error parsing REDIS command: {!}", .{e});
150
+            return null;
151
+        };
152
+    }
153
+
154
+    pub fn sleep(self: Task, ms: usize) void {
155
+        self.logger.info("* sleeping for {d}ms", .{ms});
156
+        std.debug.assert(ms < 1_000_000);
157
+        const ns = std.math.mul(usize, ms, 1_000_000) catch unreachable;
158
+        std.time.sleep(ns);
159
+    }
160
+
161
+    //
162
+    // replication operations
163
+    //
164
+
165
+    pub fn handshake(
166
+        self: *Task,
167
+    ) !void {
168
+        const rs = try self.require_replica_state();
169
+        var response_et: ElemTree = undefined;
170
+        defer response_et.deinit();
171
+
172
+        // PING
173
+        try self.send_elem(
174
+            Elem{ .arr = &.{
175
+                Elem{ .str = "PING" },
176
+            } },
177
+        );
178
+        response_et = try self.rcv_et();
179
+        try response_et.assert_cond(.{ .str_eq_i = "PONG" });
180
+
181
+        // REPLCONF listening-port <OUR_PORT>
182
+        const port_str = try std.fmt.allocPrint(self.allocator, "{d}", .{rs.master_port});
183
+        defer self.allocator.free(port_str);
184
+        try self.send_elem(
185
+            Elem{ .arr = &.{
186
+                Elem{ .str = "REPLCONF" },
187
+                Elem{ .str = "listening-port" },
188
+                Elem{ .str = port_str },
189
+            } },
190
+        );
191
+        response_et = try self.rcv_et();
192
+        try response_et.assert_cond(.{ .str_eq_i = "OK" });
193
+
194
+        // REPLCONF capa
195
+        try self.send_elem(
196
+            Elem{ .arr = &.{
197
+                Elem{ .str = "REPLCONF" },
198
+                Elem{ .str = "capa" },
199
+                Elem{ .str = "psync2" },
200
+            } },
201
+        );
202
+        response_et = try self.rcv_et();
203
+        try response_et.assert_cond(.{ .str_eq_i = "OK" });
204
+
205
+        // PSYNC <REPLID> <OFFSET>
206
+        //  .. but since it's handshake, REPLID="?" and OFFSET=-1
207
+        try self.send_elem(
208
+            Elem{ .arr = &.{
209
+                Elem{ .str = "PSYNC" },
210
+                Elem{ .str = "?" },
211
+                Elem{ .str = "-1" },
212
+            } },
213
+        );
214
+        response_et = try self.rcv_et();
215
+        try response_et.assert_cond(.{ .str_startswith_i = "FULLRESYNC" });
216
+
217
+        // blob with master's RDB
218
+        response_et = try self.require_bin_et();
219
+        try response_et.assert_cond(.{ .isa_bin = true, .len_ge = EMPTY_RDB.len });
220
+    }
221
+
222
+    fn _bump_master_offset(self: Task, bytes_n: u64) !void {
223
+        const ms = try self.require_master_state();
224
+        const old_offset = ms.repl_offset;
225
+        ms.repl_offset = try std.math.add(u64, old_offset, bytes_n);
226
+        self.logger.debug(
227
+            ": bumped master offset: {d} + {d} => {d}",
228
+            .{ old_offset, bytes_n, ms.repl_offset },
229
+        );
230
+    }
231
+
232
+    pub fn _broadcast(self: Task, ms: *MasterState, req: ReplicaRequest, timeout_ms: u64) !*ReplicationBatch {
233
+        self.logger.info("* broadcasting request: {any} to {d} replicas", .{ req, ms.replicas.count() });
234
+        const batch_ptr = try ms.replicas.bq.queue_batch(req, try std.math.mul(u64, timeout_ms, std.time.ns_per_ms));
235
+        ms.replicas.bq._dump("_broadcast()");
236
+        return batch_ptr;
237
+    }
238
+
239
+    pub fn maybe_propagate(self: Task, et: ElemTree) !void {
240
+        defer et.deinit();
241
+        const ms = self.get_master_state() orelse return;
242
+        try self._bump_master_offset(et.raw_bytes.len);
243
+        const batch_ptr = try self._broadcast(ms, .{ .propagate = et }, 0);
244
+        batch_ptr.disown();
245
+        // while (batch.next()) |msg| {
246
+        //     self.logger.debug(": thread responded: {any}", .{msg});
247
+        //     switch (msg.response) {
248
+        //         .sent => {
249
+        //             self.logger.debug(
250
+        //                 ": replica acked, propagation sent: replica_id={d} duration_ms={d}",
251
+        //                 .{ msg.ctx_id, msg.duration_ms },
252
+        //             );
253
+        //         },
254
+        //         .err => {
255
+        //             self.logger.err(
256
+        //                 "! propagation was not sent, dropping replica: replica_id={d} duration_ms={d} err={!}",
257
+        //                 .{ msg.ctx_id, msg.duration_ms, msg.response.err },
258
+        //             );
259
+        //             ms.replicas.remove(msg.ctx_id);
260
+        //         },
261
+        //         else => {
262
+        //             unreachable; // unexpected response to .propagation request
263
+        //         },
264
+        //     }
265
+        //     ms.replicas.bq._dump(".maybe_propagate(), received propagation");
266
+        // }
267
+        // for (batch.jobs) |j| self.logger.debug(": j={any}", .{j});
268
+    }
269
+
270
+    pub fn bump_replica_offset(self: Task, bytes_n: u64) !void {
271
+        const rs = try self.require_replica_state();
272
+        const old_offset = rs.master_last_offset orelse 0;
273
+        rs.master_last_offset = try std.math.add(u64, old_offset, bytes_n);
274
+        self.logger.debug(
275
+            ": bumped replica offset: {d} + {d} => {d}",
276
+            .{ old_offset, bytes_n, rs.master_last_offset.? },
277
+        );
278
+    }
279
+
280
+    pub fn check_replicas(self: Task, numreplicas: usize, timeout_ms: u64) !usize {
281
+        if (numreplicas == 0) return 0;
282
+        const ms = try self.require_master_state();
283
+        const getack_elem = Elem{ .arr = &.{
284
+            Elem{ .str = "REPLCONF" },
285
+            Elem{ .str = "GETACK" },
286
+            Elem{ .str = "*" },
287
+        } };
288
+        const getack_len = try resp.predict_write(getack_elem, self.allocator);
289
+        var acked_count: usize = 0;
290
+        var acked_togo = numreplicas;
291
+        var batch = try self._broadcast(
292
+            ms,
293
+            .{ .getack = .{ .elem = &getack_elem, .timeout_ms = timeout_ms } },
294
+            timeout_ms,
295
+        );
296
+        defer batch.deinit();
297
+        while (batch.next()) |msg| {
298
+            self.logger.debug(": thread responded: {any}", .{msg});
299
+            switch (msg.response) {
300
+                .err => {
301
+                    self.logger.err(
302
+                        "! ACK failed, dropping replica: replica_id={d} duration_ms={d} err={!}",
303
+                        .{ msg.ctx_id, msg.duration_ms, msg.response.err },
304
+                    );
305
+                    ms.replicas.remove(msg.ctx_id);
306
+                },
307
+                .ack_offset => |offset| {
308
+                    if (offset == ms.repl_offset) {
309
+                        acked_count += 1;
310
+                        acked_togo -|= 1;
311
+                    } else {
312
+                        self.logger.debug(
313
+                            ": ACK not accepted: replica_id={d} duration_ms={d} .ack_offset={d} ms.repl_offset={d}",
314
+                            .{ msg.ctx_id, msg.duration_ms, offset, ms.repl_offset },
315
+                        );
316
+                    }
317
+                },
318
+                else => {
319
+                    unreachable; // unexpected response to .propagation request
320
+                },
321
+            }
322
+            if (acked_togo == 0) break; // enough to satisfy *numreplicas*
323
+        }
324
+        for (batch.jobs) |j| self.logger.debug(": j={any}", .{j});
325
+        for (batch.jobs) |j| {
326
+            if (j == .timeouted) self.logger.debug("* replica late for timeout: replica_id={d}", .{j.timeouted});
327
+        }
328
+        try self._bump_master_offset(getack_len);
329
+        return acked_count;
330
+    }
331
+
332
+    pub fn getack_one(self: Task, elem: *const Elem) !i64 {
333
+        if (self.replication_state.* == .replica) unreachable; // replica must not call this
334
+        self.logger.debug(": sending GETACK elem..", .{});
335
+        try self.send_elem(elem.*);
336
+        self.logger.debug(": ..sent, receiving response..", .{});
337
+        const et = try self.require_et();
338
+        self.logger.debug(": ..received: {any}", .{et});
339
+        defer et.deinit();
340
+        try et.assert_cond(.{ .len_eq = 3 });
341
+        try et.assert_cond(.{ .idx = 0, .str_eq_i = "REPLCONF" });
342
+        try et.assert_cond(.{ .idx = 1, .str_eq_i = "ACK" });
343
+        return try et.parse_int_at(2);
344
+    }
345
+};

+ 188
- 0
src/redis/worker.zig 查看文件

@@ -0,0 +1,188 @@
1
+const std = @import("std");
2
+const builtin = @import("builtin");
3
+
4
+const Connection = @import("server.zig").Connection;
5
+const Logger = @import("logger.zig").Logger;
6
+const Server = @import("server.zig").Server;
7
+const Task = @import("task.zig").Task;
8
+const Role = @import("task.zig").Role;
9
+
10
+pub const HandlerResult = struct {
11
+    connection_policy: enum { close, keep } = .close,
12
+};
13
+
14
+pub const HandlerFn = *const fn (*Task, *Server) anyerror!HandlerResult;
15
+
16
+pub fn WorkerPool(comptime pfx: []const u8, comptime buff_mask: ?u8) type {
17
+    return struct {
18
+        threads_buff: []u8,
19
+        arena: std.heap.ArenaAllocator,
20
+        m: std.Thread.Mutex,
21
+        c: std.Thread.Condition,
22
+        job_queue: JobList,
23
+        thread_count: usize,
24
+        state: enum { new, running, stopped, disabled },
25
+        next_task_id: usize = 0,
26
+        server: ?*Server = null,
27
+
28
+        const TaskIdPfx = pfx;
29
+        const BuffMask = buff_mask;
30
+
31
+        const JobList = std.SinglyLinkedList(Job);
32
+        const WorkerPoolT = @This();
33
+
34
+        pub const Workspace = struct {
35
+            buff: []u8,
36
+            fba: std.heap.FixedBufferAllocator,
37
+        };
38
+
39
+        pub const Job = struct {
40
+            handler_fn: HandlerFn,
41
+            task_args: Task.Args,
42
+            task_id: usize,
43
+        };
44
+
45
+        pub fn init(
46
+            threads_buff: []u8,
47
+            thread_count: usize,
48
+            allocator: std.mem.Allocator,
49
+        ) WorkerPoolT {
50
+            return WorkerPoolT{
51
+                .thread_count = thread_count,
52
+                .threads_buff = threads_buff,
53
+                .arena = std.heap.ArenaAllocator.init(allocator),
54
+                .m = std.Thread.Mutex{},
55
+                .c = std.Thread.Condition{},
56
+                .job_queue = JobList{},
57
+                .state = .new,
58
+            };
59
+        }
60
+
61
+        pub fn start(self: *WorkerPoolT, server: *Server) !void {
62
+            self.server = server;
63
+            if (self.thread_count == 0) {
64
+                self.state = .disabled;
65
+                return;
66
+            }
67
+            const thread_count = self.thread_count;
68
+            if (@mod(self.threads_buff.len, thread_count) > 0) return error.BuffTooLarge;
69
+            const buff_len = @divExact(self.threads_buff.len, thread_count);
70
+            self.state = .running;
71
+            for (0..thread_count) |idx| {
72
+                const ws_ptr = try self.arena.allocator().create(Workspace);
73
+                const bgn = idx * buff_len;
74
+                const ws_buff = self.threads_buff[bgn..(bgn + buff_len)];
75
+                ws_ptr.* = Workspace{
76
+                    .buff = ws_buff,
77
+                    .fba = std.heap.FixedBufferAllocator.init(ws_buff),
78
+                };
79
+                _ = try std.Thread.spawn(.{}, WorkerPoolT._worker_fn, .{ ws_ptr, self });
80
+            }
81
+        }
82
+
83
+        pub fn destroy(self: *WorkerPoolT) void {
84
+            self.state = .stopped;
85
+            self.arena.deinit();
86
+        }
87
+
88
+        fn _worker_fn(
89
+            ws: *Workspace,
90
+            pool: *WorkerPoolT,
91
+        ) void {
92
+            const thread_id = std.Thread.getCurrentId();
93
+            const server = pool.server.?; // server pointer must be set by now
94
+            while (pool.state == .running) {
95
+                var maybe_job: ?Job = null;
96
+                {
97
+                    pool.m.lock();
98
+                    defer pool.m.unlock();
99
+                    while (pool.job_queue.first == null) {
100
+                        pool.c.wait(&pool.m);
101
+                    }
102
+                    const jnode = pool.job_queue.popFirst().?;
103
+                    maybe_job = jnode.*.data;
104
+                    pool.arena.allocator().destroy(jnode);
105
+                }
106
+                var job = maybe_job.?;
107
+
108
+                // reset and wipe ws memory
109
+                ws.fba.reset();
110
+                if (WorkerPoolT.BuffMask) |m| {
111
+                    for (ws.buff) |*b| b.* = m;
112
+                }
113
+
114
+                // create new task
115
+                const connection: Connection = server.connection_pool.get(job.task_args.connection_h) orelse {
116
+                    unreachable; // spawning task for a nonexistent connection!?
117
+                };
118
+                const logger_ctx = Logger.TaskCtx{
119
+                    .task_id = job.task_id,
120
+                    .task_id_pfx = WorkerPoolT.TaskIdPfx,
121
+                    .thread_id = if (thread_id == server.main_thread_id) null else thread_id,
122
+                    .handle = job.task_args.connection_h,
123
+                    .address = connection.address,
124
+                    .stream = connection.stream,
125
+                    .role = job.task_args.role,
126
+                };
127
+                var task = Task{
128
+                    .connection_h = job.task_args.connection_h,
129
+                    .timeout_ms = job.task_args.timeout_ms,
130
+                    .replication_state = &server.replication_state,
131
+                    .stream = connection.stream,
132
+                    .logger = Logger.init_for_task(server.logger.options, logger_ctx, ws.fba.allocator()),
133
+                    .allocator = ws.fba.allocator(),
134
+                };
135
+                defer task.deinit();
136
+
137
+                // run the handler
138
+                const result = job.handler_fn(
139
+                    &task,
140
+                    server,
141
+                ) catch |e| {
142
+                    _ = server.connection_pool.delete(task.connection_h);
143
+                    task.stream.close();
144
+                    task.logger.err("handler returned error: {!}", .{e});
145
+                    server.logger.err(
146
+                        "connection handler returned error:a connection_h={any} address={any} e={!}",
147
+                        .{ job.task_args.connection_h, connection.address, e },
148
+                    );
149
+                    return;
150
+                };
151
+
152
+                switch (result.connection_policy) {
153
+                    .close => {
154
+                        task.logger.info("* task done; closing connection", .{});
155
+                        task.stream.close();
156
+                        _ = server.connection_pool.delete(task.connection_h);
157
+                    },
158
+                    .keep => {
159
+                        task.logger.info("* re-using connection for next task", .{});
160
+                    },
161
+                }
162
+            }
163
+        }
164
+
165
+        pub fn queue(
166
+            self: *WorkerPoolT,
167
+            handler_fn: HandlerFn,
168
+            task_args: Task.Args,
169
+        ) !void {
170
+            if (self.state == .disabled) return error.WorkerPoolDisabled;
171
+            if (self.state == .stopped) return error.WorkerPoolStopped;
172
+            if (self.state == .new) return error.WorkerPoolNotStarted;
173
+            {
174
+                self.m.lock();
175
+                defer self.m.unlock();
176
+                const jnode_ptr = try self.arena.allocator().create(JobList.Node);
177
+                jnode_ptr.* = JobList.Node{ .data = Job{
178
+                    .handler_fn = handler_fn,
179
+                    .task_args = task_args,
180
+                    .task_id = self.next_task_id,
181
+                } };
182
+                self.next_task_id += 1;
183
+                self.job_queue.prepend(jnode_ptr);
184
+            }
185
+            self.c.signal();
186
+        }
187
+    };
188
+}

+ 2192
- 0
src/util.zig
文件差異過大導致無法顯示
查看文件


+ 74
- 0
watch_logs.sh 查看文件

@@ -0,0 +1,74 @@
1
+#!/bin/bash
2
+
3
+
4
+usage() {
5
+    local self="${0##*/}"
6
+    local err=$1; shift
7
+    echo >&2 "usage: $self DIR [DIR...]"
8
+    echo >&2 ""
9
+    echo >&2 "$err"
10
+    exit 3
11
+}
12
+
13
+warn() {
14
+    echo >&2 "$@"
15
+}
16
+
17
+die() {
18
+    echo >&2 "$@"
19
+    exit 3
20
+}
21
+
22
+list_files() {
23
+    local dir
24
+    for dir in "${Dirs[@]}"; do
25
+        echo "$dir/root.log"
26
+        seq -f "$dir/task-rep%02.0f.log" 0 3
27
+        seq -f "$dir/task-cmd%02.0f.log" 0 9
28
+    done
29
+}
30
+
31
+open_files() {
32
+    local i=0
33
+    local path=0
34
+    for path in "$@"; do
35
+        twinner -t "$path" -O "$Tmp/pid-$i" -P -c "tail -F \"$path\"" &
36
+        sleep 0.1   # allow WM to have predictable order of windows
37
+        ((i++))
38
+    done
39
+}
40
+
41
+onclose() {
42
+    warn ""
43
+    warn "got SIGINT; killing: Pids=(${Pids[*]})"
44
+    kill "${Pids[@]}"
45
+    rm -r "$Tmp"
46
+}
47
+
48
+main() {
49
+    test "$#" -eq 0 && usage "no DIRs?"
50
+    local Dirs=("$@")
51
+    local filelist=()
52
+    local Pids=()
53
+    local Tmp
54
+
55
+    Tmp=$(mktemp -d /tmp/watch_logs.XXXXXXXX)
56
+
57
+    list_files >"$Tmp/files"
58
+    mapfile -t filelist <"$Tmp/files"
59
+
60
+    open_files "${filelist[@]}"
61
+    sleep 2
62
+    cat "$Tmp/pid-"* > "$Tmp/pids"
63
+    mapfile -t Pids <"$Tmp/pids"
64
+
65
+    warn "spawned children: ${Pids[*]}"
66
+    warn "press ^C to close all open files"
67
+    wait # "${Pids[@]}"
68
+
69
+    rm -r "$Tmp"
70
+}
71
+
72
+trap onclose SIGINT
73
+
74
+main "$@"

+ 1
- 1
your_program.sh 查看文件

@@ -21,4 +21,4 @@ set -e # Exit early if any commands fail
21 21
 #
22 22
 # - Edit this to change how your program runs locally
23 23
 # - Edit .codecrafters/run.sh to change how your program runs remotely
24
-exec zig-out/bin/main "$@"
24
+exec zig-out/bin/main --verbose "$@"