From 1f8e8ac0e403808f1e20678cad97f482a25ba2c7 Mon Sep 17 00:00:00 2001 From: Rene Schallner Date: Thu, 19 Jan 2023 20:34:47 +0100 Subject: [PATCH] saner mem management, thread-safety --- examples/endpoint/endpoint.zig | 17 +++--- examples/endpoint/main.zig | 5 +- examples/endpoint/users.zig | 21 ++----- examples/hello_json/hello_json.zig | 14 +---- src/util.zig | 88 +++++++++++++++++++++++++++++- 5 files changed, 107 insertions(+), 38 deletions(-) diff --git a/examples/endpoint/endpoint.zig b/examples/endpoint/endpoint.zig index c175c2b..3433a5f 100644 --- a/examples/endpoint/endpoint.zig +++ b/examples/endpoint/endpoint.zig @@ -11,6 +11,9 @@ var alloc: std.mem.Allocator = undefined; var endpoint: zap.SimpleEndpoint = undefined; var users: Users = undefined; +// 100MB of json buffer +var jsonbuf: [100 * 1024 * 1024]u8 = undefined; + pub fn init( a: std.mem.Allocator, user_path: []const u8, @@ -53,7 +56,7 @@ fn getUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { } if (userIdFromPath(path)) |id| { if (users.get(id)) |user| { - if (zap.stringify(user, .{})) |json| { + if (zap.stringifyBuf(&jsonbuf, user, .{})) |json| { _ = r.sendJson(json); } } @@ -67,7 +70,7 @@ fn listUsers(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { if (users.list(&l)) {} else |_| { return; } - if (zap.stringifyArrayList(User, &l, .{})) |maybe_json| { + if (zap.stringifyArrayListBuf(&jsonbuf, User, &l, .{})) |maybe_json| { if (maybe_json) |json| { _ = r.sendJson(json); } @@ -88,7 +91,7 @@ fn postUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { if (maybe_user) |u| { defer std.json.parseFree(User, u, .{ .allocator = alloc }); if (users.addByName(u.first_name, u.last_name)) |id| { - if (zap.stringify(.{ .status = "OK", .id = id }, .{})) |json| { + if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { _ = r.sendJson(json); } } else |_| { @@ -117,14 +120,14 @@ fn putUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { .{ .allocator = alloc }, ); if (users.update(id, u.first_name, u.last_name)) { - if (zap.stringify(.{ + if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id, }, .{})) |json| { _ = r.sendJson(json); } } else { - if (zap.stringify(.{ + if (zap.stringifyBuf(&jsonbuf, .{ .status = "ERROR", .id = id, }, .{})) |json| { @@ -143,11 +146,11 @@ fn deleteUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { if (r.path) |path| { if (userIdFromPath(path)) |id| { if (users.delete(id)) { - if (zap.stringify(.{ .status = "OK", .id = id }, .{})) |json| { + if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { _ = r.sendJson(json); } } else { - if (zap.stringify(.{ + if (zap.stringifyBuf(&jsonbuf, .{ .status = "ERROR", .id = id, }, .{})) |json| { diff --git a/examples/endpoint/main.zig b/examples/endpoint/main.zig index 6a78183..15e2d5b 100644 --- a/examples/endpoint/main.zig +++ b/examples/endpoint/main.zig @@ -3,7 +3,10 @@ const zap = @import("zap"); const Endpoint = @import("endpoint.zig"); pub fn main() !void { - const allocator = std.heap.page_allocator; + var gpa = std.heap.GeneralPurposeAllocator(.{ + .thread_safe = true, + }){}; + var allocator = gpa.allocator(); // setup listener var listener = zap.SimpleEndpointListener.init( allocator, diff --git a/examples/endpoint/users.zig b/examples/endpoint/users.zig index 7d17e44..1fe6964 100644 --- a/examples/endpoint/users.zig +++ b/examples/endpoint/users.zig @@ -32,10 +32,7 @@ pub fn init(a: std.mem.Allocator) Self { // the request will be freed (and its mem reused by facilio) when it's // completed, so we take copies of the names pub fn addByName(self: *Self, first: ?[]const u8, last: ?[]const u8) !usize { - // TODO: get rid of the temp allocation here - var temp = try self.alloc.alloc(InternalUser, 1); - defer self.alloc.free(temp); - var user = temp[0]; + var user: InternalUser = undefined; user.firstnamelen = 0; user.lastnamelen = 0; if (first) |firstname| { @@ -66,7 +63,7 @@ pub fn delete(self: *Self, id: usize) bool { pub fn get(self: *Self, id: usize) ?User { // we don't care about locking here, as our usage-pattern is unlikely to // get a user by id that is not known yet - if (self.users.get(id)) |pUser| { + if (self.users.getPtr(id)) |pUser| { return .{ .id = pUser.id, .first_name = pUser.firstnamebuf[0..pUser.firstnamelen], @@ -83,11 +80,9 @@ pub fn update( last: ?[]const u8, ) bool { // we don't care about locking here - var user: ?InternalUser = self.users.get(id); - // we got a copy apparently, so we need to put again - if (user) |*pUser| { - pUser.*.firstnamelen = 0; - pUser.*.lastnamelen = 0; + if (self.users.getPtr(id)) |pUser| { + pUser.firstnamelen = 0; + pUser.lastnamelen = 0; if (first) |firstname| { std.mem.copy(u8, pUser.firstnamebuf[0..], firstname); pUser.firstnamelen = firstname.len; @@ -96,12 +91,6 @@ pub fn update( std.mem.copy(u8, pUser.lastnamebuf[0..], lastname); pUser.lastnamelen = lastname.len; } - _ = self.users.remove(id); - if (self.users.put(id, pUser.*)) { - return true; - } else |_| { - return false; - } } return false; } diff --git a/examples/hello_json/hello_json.zig b/examples/hello_json/hello_json.zig index d5619cc..0549d89 100644 --- a/examples/hello_json/hello_json.zig +++ b/examples/hello_json/hello_json.zig @@ -18,8 +18,9 @@ fn on_request(r: zap.SimpleRequest) void { const user_id: usize = @intCast(usize, the_path[6] - 0x30); const user = users.get(user_id); + var buf: [100]u8 = undefined; var json_to_send: []const u8 = undefined; - if (stringify(user, .{})) |json| { + if (zap.stringifyBuf(&buf, user, .{})) |json| { json_to_send = json; } else { json_to_send = "null"; @@ -39,17 +40,6 @@ fn setupUserData(a: std.mem.Allocator) !void { try users.put(2, .{ .first_name = "Your", .last_name = "Mom" }); } -var buf: [100]u8 = undefined; -fn stringify(value: anytype, options: std.json.StringifyOptions) ?[]const u8 { - var fba = std.heap.FixedBufferAllocator.init(&buf); - var string = std.ArrayList(u8).init(fba.allocator()); - if (std.json.stringify(value, options, string.writer())) { - return string.items; - } else |_| { // error - return null; - } -} - pub fn main() !void { var a = std.heap.page_allocator; try setupUserData(a); diff --git a/src/util.zig b/src/util.zig index 81ab270..237cf54 100644 --- a/src/util.zig +++ b/src/util.zig @@ -6,8 +6,15 @@ const std = @import("std"); // 1MB JSON buffer var jsonbuf: [1024 * 1024]u8 = undefined; +var mutex: std.Thread.Mutex = .{}; -pub fn stringify(value: anytype, options: std.json.StringifyOptions) ?[]const u8 { +/// use default 1MB buffer, mutex-protected +pub fn stringify( + value: anytype, + options: std.json.StringifyOptions, +) ?[]const u8 { + mutex.lock(); + defer mutex.unlock(); var fba = std.heap.FixedBufferAllocator.init(&jsonbuf); var string = std.ArrayList(u8).init(fba.allocator()); if (std.json.stringify(value, options, string.writer())) { @@ -17,7 +24,14 @@ pub fn stringify(value: anytype, options: std.json.StringifyOptions) ?[]const u8 } } -pub fn stringifyArrayList(comptime T: anytype, list: *std.ArrayList(T), options: std.json.StringifyOptions) !?[]const u8 { +/// use default 1MB buffer, mutex-protected +pub fn stringifyArrayList( + comptime T: anytype, + list: *std.ArrayList(T), + options: std.json.StringifyOptions, +) !?[]const u8 { + mutex.lock(); + defer mutex.unlock(); var fba = std.heap.FixedBufferAllocator.init(&jsonbuf); var string = std.ArrayList(u8).init(fba.allocator()); var writer = string.writer(); @@ -31,3 +45,73 @@ pub fn stringifyArrayList(comptime T: anytype, list: *std.ArrayList(T), options: try writer.writeByte(']'); return string.items; } + +/// provide your own buf, NOT mutex-protected! +pub fn stringifyBuf( + buffer: []u8, + value: anytype, + options: std.json.StringifyOptions, +) ?[]const u8 { + var fba = std.heap.FixedBufferAllocator.init(buffer); + var string = std.ArrayList(u8).init(fba.allocator()); + if (std.json.stringify(value, options, string.writer())) { + return string.items; + } else |_| { // error + return null; + } +} + +/// provide your own buf, NOT mutex-protected +pub fn stringifyArrayListBuf( + buffer: []u8, + comptime T: anytype, + list: *std.ArrayList(T), + options: std.json.StringifyOptions, +) !?[]const u8 { + var fba = std.heap.FixedBufferAllocator.init(buffer); + var string = std.ArrayList(u8).init(fba.allocator()); + var writer = string.writer(); + try writer.writeByte('['); + var first: bool = true; + for (list.items) |user| { + if (!first) try writer.writeByte(','); + first = false; + try std.json.stringify(user, options, string.writer()); + } + try writer.writeByte(']'); + return string.items; +} + +/// provide your own allocator, NOT mutex-protected +pub fn stringifyAlloc( + a: std.mem.Allocator, + value: anytype, + options: std.json.StringifyOptions, +) ?[]const u8 { + var string = std.ArrayList(u8).init(a); + if (std.json.stringify(value, options, string.writer())) { + return string.items; + } else |_| { // error + return null; + } +} + +/// provide your own allocator, NOT mutex-protected +pub fn stringifyArrayListAlloc( + a: std.mem.Allocator, + comptime T: anytype, + list: *std.ArrayList(T), + options: std.json.StringifyOptions, +) !?[]const u8 { + var string = std.ArrayList(u8).init(a); + var writer = string.writer(); + try writer.writeByte('['); + var first: bool = true; + for (list.items) |user| { + if (!first) try writer.writeByte(','); + first = false; + try std.json.stringify(user, options, string.writer()); + } + try writer.writeByte(']'); + return string.items; +}