this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

migrate to zig 0.16 (zat v0.3.0-alpha.7)

- Io.Threaded with explicit io passing via init() per module
- Io.Mutex, Io.Timestamp, Io.net, smp_allocator
- thread-per-connection, ArrayList .empty
- Dockerfile updated to zig 0.16.0-dev.3059

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz 8e2e3352 2cec1702

+71 -42
+2 -2
Dockerfile
··· 4 4 ca-certificates curl xz-utils libsqlite3-dev \ 5 5 && rm -rf /var/lib/apt/lists/* 6 6 7 - RUN curl -fSL https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz \ 7 + RUN curl -fSL https://ziglang.org/builds/zig-x86_64-linux-0.16.0-dev.3059+42e33db9d.tar.xz \ 8 8 | tar xJ -C /opt 9 - ENV PATH=/opt/zig-x86_64-linux-0.15.2:$PATH 9 + ENV PATH=/opt/zig-x86_64-linux-0.16.0-dev.3059+42e33db9d:$PATH 10 10 11 11 WORKDIR /build 12 12 COPY build.zig build.zig.zon ./
+6 -4
build.zig
··· 23 23 .root_source_file = b.path("src/main.zig"), 24 24 .target = target, 25 25 .optimize = optimize, 26 + .link_libc = true, 26 27 .imports = imports, 27 28 }); 29 + exe_mod.linkSystemLibrary("sqlite3", .{}); 30 + 28 31 const exe = b.addExecutable(.{ 29 32 .name = "labelz", 30 33 .root_module = exe_mod, 31 34 }); 32 - exe.linkLibC(); 33 - exe.linkSystemLibrary("sqlite3"); 34 35 b.installArtifact(exe); 35 36 36 37 const run_exe = b.addRunArtifact(exe); ··· 51 52 .root_source_file = b.path(file), 52 53 .target = target, 53 54 .optimize = optimize, 55 + .link_libc = true, 54 56 .imports = imports, 55 57 }); 58 + test_mod.linkSystemLibrary("sqlite3", .{}); 59 + 56 60 const t = b.addTest(.{ 57 61 .root_module = test_mod, 58 62 }); 59 - t.linkLibC(); 60 - t.linkSystemLibrary("sqlite3"); 61 63 test_step.dependOn(&b.addRunArtifact(t).step); 62 64 } 63 65 }
+5 -5
build.zig.zon
··· 2 2 .name = .labelz, 3 3 .version = "0.0.1", 4 4 .fingerprint = 0xcc0dbab526d8712d, 5 - .minimum_zig_version = "0.15.0", 5 + .minimum_zig_version = "0.16.0", 6 6 .dependencies = .{ 7 7 .zat = .{ 8 - .url = "https://tangled.org/zat.dev/zat/archive/v0.2.16.tar.gz", 9 - .hash = "zat-0.2.16-5PuC7tjwBADbnwV5y8ztKUHhGHMJHh2HouvoYImnZ7y5", 8 + .url = "https://tangled.org/zat.dev/zat/archive/v0.3.0-alpha.7.tar.gz", 9 + .hash = "zat-0.3.0-alpha.7-5PuC7uNjBQDv28db31DEKkFn1tU5I4f1GfJs-RrG8_pS", 10 10 }, 11 11 .websocket = .{ 12 - .url = "https://github.com/zzstoatzz/websocket.zig/archive/395d0f4.tar.gz", 13 - .hash = "websocket-0.1.0-ZPISdVJ8AwD7U03ARGgHclzlYSd9GeU91_WDXjRyjYdh", 12 + .url = "https://github.com/zzstoatzz/websocket.zig/archive/edeca26.tar.gz", 13 + .hash = "websocket-0.1.0-ZPISdSmqAwCbwcFtrAQC_q9cegdw-iHyrjCftgfMz-Nf", 14 14 }, 15 15 }, 16 16 .paths = .{
+4 -4
src/identity.zig
··· 247 247 } 248 248 249 249 fn buildGenesisJson(allocator: Allocator, f: OpFields) ![]u8 { 250 - var buf: std.ArrayList(u8) = .{}; 251 - errdefer buf.deinit(allocator); 252 - const w = buf.writer(allocator); 250 + var aw: std.Io.Writer.Allocating = .init(allocator); 251 + errdefer aw.deinit(); 252 + const w = &aw.writer; 253 253 254 254 try w.writeAll("{"); 255 255 try w.writeAll("\"type\":\"plc_operation\""); ··· 278 278 try w.writeAll(f.sig_b64); 279 279 try w.writeAll("\"}"); 280 280 281 - return try buf.toOwnedSlice(allocator); 281 + return try aw.toOwnedSlice(); 282 282 } 283 283 284 284 // === tests ===
+33 -14
src/main.zig
··· 11 11 const store_mod = @import("store.zig"); 12 12 const server_mod = @import("server.zig"); 13 13 14 + const Io = std.Io; 14 15 const Label = label_mod.Label; 15 16 const Keypair = zat.Keypair; 16 17 const JetstreamClient = zat.JetstreamClient; 17 18 const Allocator = std.mem.Allocator; 18 19 const log = std.log.scoped(.labelz); 20 + 21 + // Threaded Io instance — initialized in main() before any threads are spawned. 22 + // Override debug_threaded_io so std.debug.print is multi-thread safe. 23 + var app_threaded_io: Io.Threaded = undefined; 24 + pub const std_options_debug_threaded_io: ?*Io.Threaded = &app_threaded_io; 19 25 20 26 const Config = struct { 21 27 /// labeler DID (src field on labels) ··· 50 56 keypair: *const Keypair, 51 57 store: *store_mod.Store, 52 58 server: *ServerType, 59 + io: Io, 53 60 54 61 pub fn onEvent(self: *EventHandler, event: zat.JetstreamEvent) void { 55 62 switch (event) { ··· 88 95 }) catch return; 89 96 90 97 var ts_buf: [32]u8 = undefined; 91 - const cts = nowIso8601(&ts_buf); 98 + const cts = nowIso8601(self.io, &ts_buf); 92 99 93 100 var label = Label{ 94 101 .src = self.config.did, ··· 115 122 }; 116 123 117 124 pub fn main() !void { 118 - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; 119 - defer _ = gpa.deinit(); 120 - const allocator = gpa.allocator(); 125 + const allocator = std.heap.smp_allocator; 126 + 127 + app_threaded_io = Io.Threaded.init(allocator, .{}); 128 + const io = app_threaded_io.io(); 129 + 130 + // init server module with io 131 + server_mod.init(io); 121 132 122 133 const config = try loadConfig(); 123 134 ··· 145 156 log.info("rules: {d} keyword rules active", .{config.rules.len}); 146 157 147 158 // start jetstream consumer (blocks forever) 148 - var client = JetstreamClient.init(allocator, .{ 159 + var client = JetstreamClient.init(io, allocator, .{ 149 160 .wanted_collections = config.collections, 150 161 }); 151 162 ··· 155 166 .keypair = &keypair, 156 167 .store = &store, 157 168 .server = &server, 169 + .io = io, 158 170 }; 159 171 160 - client.subscribe(&handler); 172 + try client.subscribe(&handler); 173 + } 174 + 175 + fn getenv(name: [*:0]const u8) ?[]const u8 { 176 + return if (std.c.getenv(name)) |p| std.mem.span(p) else null; 161 177 } 162 178 163 179 fn loadConfig() !Config { 164 - const did = std.posix.getenv("LABELZ_DID") orelse { 180 + const did = getenv("LABELZ_DID") orelse { 165 181 log.err("LABELZ_DID environment variable required", .{}); 166 182 return error.MissingConfig; 167 183 }; 168 184 169 - const key_hex = std.posix.getenv("LABELZ_SECRET_KEY") orelse { 185 + const key_hex = getenv("LABELZ_SECRET_KEY") orelse { 170 186 log.err("LABELZ_SECRET_KEY environment variable required (64 hex chars)", .{}); 171 187 return error.MissingConfig; 172 188 }; ··· 182 198 return error.InvalidConfig; 183 199 }; 184 200 185 - const port: u16 = if (std.posix.getenv("LABELZ_PORT")) |p| 201 + const port: u16 = if (getenv("LABELZ_PORT")) |p| 186 202 std.fmt.parseInt(u16, p, 10) catch 4100 187 203 else 188 204 4100; 189 205 190 - const db_path: [*:0]const u8 = if (std.posix.getenv("LABELZ_DB")) |p| 206 + const db_path: [*:0]const u8 = if (getenv("LABELZ_DB")) |p| 191 207 @ptrCast(p.ptr) 192 208 else 193 209 "/data/labelz.db"; ··· 218 234 return false; 219 235 } 220 236 221 - fn nowIso8601(buf: *[32]u8) []const u8 { 222 - const ts = std.time.timestamp(); 223 - const epoch: std.time.epoch.EpochSeconds = .{ .secs = @intCast(ts) }; 237 + fn nowIso8601(io: Io, buf: *[32]u8) []const u8 { 238 + const ts_ns = Io.Timestamp.now(io, .real).nanoseconds; 239 + const ts_secs: u64 = @intCast(@divFloor(ts_ns, std.time.ns_per_s)); 240 + const epoch: std.time.epoch.EpochSeconds = .{ .secs = ts_secs }; 224 241 const day = epoch.getDaySeconds(); 225 242 const yd = epoch.getEpochDay().calculateYearDay(); 226 243 const md = yd.calculateMonthDay(); ··· 247 264 } 248 265 249 266 test "nowIso8601 produces valid format" { 267 + // use a test io 268 + const io: Io = .{}; 250 269 var buf: [32]u8 = undefined; 251 - const ts = nowIso8601(&buf); 270 + const ts = nowIso8601(io, &buf); 252 271 // should look like 2024-01-01T00:00:00.000Z 253 272 try std.testing.expectEqual(@as(usize, 24), ts.len); 254 273 try std.testing.expectEqual(@as(u8, 'T'), ts[10]);
+20 -12
src/server.zig
··· 11 11 const label_mod = @import("label.zig"); 12 12 const store_mod = @import("store.zig"); 13 13 14 + const Io = std.Io; 14 15 const Allocator = std.mem.Allocator; 15 16 const log = std.log.scoped(.server); 16 17 18 + // module state — initialized via init(), not from a global 19 + var io: Io = undefined; 20 + 21 + pub fn init(app_io: Io) void { 22 + io = app_io; 23 + } 24 + 17 25 pub const Server = struct { 18 26 allocator: Allocator, 19 27 store: *store_mod.Store, 20 28 subscribers: std.ArrayList(*Subscriber), 21 - mutex: std.Thread.Mutex = .{}, 29 + mutex: Io.Mutex = .init, 22 30 23 31 pub fn init(allocator: Allocator, store: *store_mod.Store) Server { 24 32 return .{ 25 33 .allocator = allocator, 26 34 .store = store, 27 - .subscribers = .{}, 35 + .subscribers = .empty, 28 36 }; 29 37 } 30 38 ··· 41 49 }; 42 50 defer self.allocator.free(frame); 43 51 44 - self.mutex.lock(); 45 - defer self.mutex.unlock(); 52 + self.mutex.lockUncancelable(io); 53 + defer self.mutex.unlock(io); 46 54 47 55 var i: usize = 0; 48 56 while (i < self.subscribers.items.len) { ··· 90 98 }; 91 99 } 92 100 93 - self.mutex.lock(); 94 - defer self.mutex.unlock(); 101 + self.mutex.lockUncancelable(io); 102 + defer self.mutex.unlock(io); 95 103 self.subscribers.append(self.allocator, sub) catch { 96 104 self.allocator.destroy(sub); 97 105 }; ··· 99 107 100 108 /// remove a subscriber connection. 101 109 pub fn removeSubscriber(self: *Server, conn: *websocket.Conn) void { 102 - self.mutex.lock(); 103 - defer self.mutex.unlock(); 110 + self.mutex.lockUncancelable(io); 111 + defer self.mutex.unlock(io); 104 112 105 113 for (self.subscribers.items, 0..) |sub, i| { 106 114 if (sub.conn == conn) { ··· 220 228 } 221 229 222 230 // build JSON response 223 - var buf: std.ArrayList(u8) = .{}; 224 - defer buf.deinit(self.allocator); 225 - const w = buf.writer(self.allocator); 231 + var aw: std.Io.Writer.Allocating = .init(self.allocator); 232 + defer aw.deinit(); 233 + const w = &aw.writer; 226 234 227 235 w.writeAll("{\"labels\":[") catch return; 228 236 ··· 233 241 234 242 w.writeAll("]}") catch return; 235 243 236 - httpRespond(conn, "200 OK", "application/json", buf.items); 244 + httpRespond(conn, "200 OK", "application/json", w.buffered()); 237 245 } 238 246 }; 239 247
+1 -1
src/store.zig
··· 146 146 147 147 fn collectRows(self: *Store, allocator: Allocator, stmt: *c.sqlite3_stmt) ![]StoredLabel { 148 148 _ = self; 149 - var results: std.ArrayList(StoredLabel) = .{}; 149 + var results: std.ArrayList(StoredLabel) = .empty; 150 150 errdefer { 151 151 for (results.items) |item| { 152 152 allocator.free(item.encoded);