feat: introduce ReaderCtx and readerWorker to server
This commit is contained in:
1 parent
449a952c3e
commit
f34107ef56
1 file changed
+152
+152
@@ -3441,3 +3441,155 @@ test "server rejects a malformed table_export frame (bad format byte) with bad_r
|
||||
defer frame.deinit();
|
||||
try std.testing.expectEqual(@intFromEnum(protocol.Status.bad_request), frame.tag);
|
||||
|
||||
// The connection must still be usable afterward.
|
||||
try protocol.writeFrame(stream.writer(), @intFromEnum(protocol.Op.ping), "");
|
||||
var pingFrame = try protocol.readFrame(allocator, stream.reader());
|
||||
defer pingFrame.deinit();
|
||||
try std.testing.expectEqual(@intFromEnum(protocol.Status.ok), pingFrame.tag);
|
||||
}
|
||||
|
||||
test "table_export requires only readonly role, table_import requires readwrite" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
{
|
||||
var table = try querymod.Table.open(&db, allocator, "people");
|
||||
defer table.deinit();
|
||||
try table.put(.{ .int = 1 }, &.{});
|
||||
}
|
||||
|
||||
const store = authmod.Store.init(&db);
|
||||
try store.createUser(allocator, "viewer", "pass", .readonly, 16);
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
server.enableAuth(store);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
var client = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
defer client.close();
|
||||
try client.authenticate("viewer", "pass");
|
||||
|
||||
var exported = try client.tableExport(allocator, "people", .csv);
|
||||
defer exported.deinit(allocator);
|
||||
try std.testing.expectEqual(@as(u64, 1), exported.rowCount);
|
||||
|
||||
try std.testing.expectError(client_mod.ClientError.ServerForbidden, client.tableImport("people", .csv, exported.content));
|
||||
}
|
||||
|
||||
// `table_delete` shares the same underlying-sstable-reshaping hazard as
|
||||
// `table_update` above (see that test's header comment for the full
|
||||
// rationale), but where `updateWhere` reads-then-writes each matched row in
|
||||
// place, `deleteWhere` reads-then-REMOVES it - a sharper edge again, since a
|
||||
// removed row's index entries must never resurface even if a `flush`/
|
||||
// `compact` reshapes the underlying sstables mid-scan. Three buckets
|
||||
// (warsaw/krakow/gdansk) stay untouched for the whole test and are the
|
||||
// reader threads' stable invariant (always exactly 10 rows each); a fourth
|
||||
// bucket (poznan) is what the deleter thread progressively drains one row
|
||||
// at a time via a "seq" field unrelated to the "city" index, so every
|
||||
// delete is a distinct, real structural change rather than a no-op repeat.
|
||||
test "table_delete survives concurrent flush and compact reshaping the underlying sstables" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
// Small target size so a handful of `flush`/`compact` calls do real
|
||||
// structural work instead of needing megabytes of data first - same
|
||||
// trick as `table_update`'s own concurrency test above.
|
||||
var db = try Db.open(allocator, path, .{ .sstableTargetBytes = 512 });
|
||||
defer db.close();
|
||||
|
||||
const stableBucketRows = 10;
|
||||
const poznanRows = 10;
|
||||
{
|
||||
var accounts = try querymod.Table.open(&db, allocator, "accounts");
|
||||
defer accounts.deinit();
|
||||
try accounts.createIndex("city");
|
||||
var pk: i64 = 1;
|
||||
var i: i64 = 0;
|
||||
while (i < stableBucketRows) : (i += 1) {
|
||||
inline for (.{ "warsaw", "krakow", "gdansk" }) |city| {
|
||||
try accounts.put(.{ .int = pk }, &.{.{ .name = "city", .value = .{ .text = city } }});
|
||||
pk += 1;
|
||||
}
|
||||
}
|
||||
var seq: i64 = 0;
|
||||
while (seq < poznanRows) : (seq += 1) {
|
||||
try accounts.put(.{ .int = pk }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "poznan" } },
|
||||
.{ .name = "seq", .value = .{ .int = seq } },
|
||||
});
|
||||
pk += 1;
|
||||
}
|
||||
try db.flush();
|
||||
}
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
const readerThreads = 4;
|
||||
const readerIters = 20;
|
||||
const reshapeThreads = 2;
|
||||
const reshapeIters = 15;
|
||||
|
||||
const ReaderCtx = struct {
|
||||
allocator: Allocator,
|
||||
addr: std.net.Address,
|
||||
ok: bool = false,
|
||||
};
|
||||
const readerWorker = struct {
|
||||
fn run(ctx: *ReaderCtx) void {
|
||||
var client = client_mod.Client.connect(ctx.allocator, ctx.addr) catch return;
|
||||
defer client.close();
|
||||
var i: usize = 0;
|
||||
while (i < readerIters) : (i += 1) {
|
||||
inline for (.{ "warsaw", "krakow", "gdansk" }) |city| {
|
||||
const buf = std.fmt.allocPrint(ctx.allocator, "city eq s:{s}", .{city}) catch return;
|
||||
defer ctx.allocator.free(buf);
|
||||
const rows = client.tableSelect(ctx.allocator, "accounts", buf, 0, 0, &.{}) catch return;
|
||||
defer protocol.freeRows(ctx.allocator, rows);
|
||||
if (rows.len != stableBucketRows) return;
|
||||
for (rows) |row| {
|
||||
const got = row.get("city") orelse return;
|
||||
if (!std.mem.eql(u8, got.text, city)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.ok = true;
|
||||
}
|
||||
}.run;
|
||||
|
||||
const ReshapeCtx = struct {
|
||||
allocator: Allocator,
|
||||
addr: std.net.Address,
|
||||
thread: usize,
|
||||
ok: bool = false,
|
||||
};
|
||||
const reshapeWorker = struct {
|
||||
fn run(ctx: *ReshapeCtx) void {
|
||||
var client = client_mod.Client.connect(ctx.allocator, ctx.addr) catch return;
|
||||
defer client.close();
|
||||
var i: usize = 0;
|
||||
while (i < reshapeIters) : (i += 1) {
|
||||
var buf: [32]u8 = undefined;
|
||||
const key = std.fmt.bufPrint(&buf, "delete-churn-{d}-{d}", .{ ctx.thread, i }) catch return;
|
||||
client.set(key, "x") catch return;
|
||||
client.flush() catch return;
|
||||
client.compact() catch return;
|
||||
}
|
||||
ctx.ok = true;
|
||||
}
|
||||
}.run;
|
||||
|
||||
Reference in new issue
Block a user