Something went wrong. Try again.
zig utility library forked from karlseguin.tngl.sh/zul
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183const std = @import("std");
const Io = std.Io;const Allocator = std.mem.Allocator;
pub const GrowingOpts = struct { count: usize,};
pub fn Growing(comptime T: type, comptime C: type) type { return struct { _ctx: C, _io: Io, _items: []*T, _available: usize, _mutex: Io.Mutex, _allocator: Allocator,
const Self = @This();
pub fn init(io: Io, allocator: Allocator, ctx: C, opts: GrowingOpts) !Self { const count = opts.count;
const items = try allocator.alloc(*T, count); errdefer allocator.free(items);
var initialized: usize = 0; errdefer { for (0..initialized) |i| { items[i].deinit(); allocator.destroy(items[i]); } }
for (0..count) |i| { items[i] = try allocator.create(T); errdefer allocator.destroy(items[i]); items[i].* = if (C == void) try T.init(allocator) else try T.init(allocator, ctx); initialized += 1; }
return .{ ._ctx = ctx, ._io = io, ._mutex = .init, ._items = items, ._available = count, ._allocator = allocator, }; }
pub fn deinit(self: *Self) void { const allocator = self._allocator; for (self._items) |item| { item.deinit(); allocator.destroy(item); } allocator.free(self._items); }
pub fn acquire(self: *Self) !*T { const items = self._items;
self._mutex.lockUncancelable(self._io); const available = self._available; if (available == 0) { // dont hold the lock over factory self._mutex.unlock(self._io);
const allocator = self._allocator; const item = try allocator.create(T); item.* = if (C == void) try T.init(allocator) else try T.init(allocator, self._ctx); return item; }
const index = available - 1; const item = items[index]; self._available = index; self._mutex.unlock(self._io); return item; }
pub fn release(self: *Self, item: *T) void { item.reset();
var items = self._items; self._mutex.lockUncancelable(self._io); const available = self._available; if (available == items.len) { self._mutex.unlock(self._io); item.deinit(); self._allocator.destroy(item); return; } items[available] = item; self._available = available + 1; self._mutex.unlock(self._io); } };}
const t = @import("zul.zig").testing;test "pool: acquire and release" { var p = try Growing(TestPoolItem, void).init(t.io, t.allocator, {}, .{ .count = 2 }); defer p.deinit();
const i1a = try p.acquire(); try t.expectEqual(0, i1a.data[0]); i1a.data[0] = 250;
const i2a = try p.acquire(); const i3a = try p.acquire(); // this should be dynamically generated
try t.expectEqual(false, i1a.data.ptr == i2a.data.ptr); try t.expectEqual(false, i2a.data.ptr == i3a.data.ptr);
p.release(i1a);
const i1b = try p.acquire(); try t.expectEqual(0, i1b.data[0]); // ensure we called reset try t.expectEqual(true, i1a.data.ptr == i1b.data.ptr);
p.release(i3a); p.release(i2a); p.release(i1b);}
test "pool: threadsafety" { var p = try Growing(TestPoolItem, void).init(t.io, t.allocator, {}, .{ .count = 3 }); defer p.deinit();
// initialize this to 0 since we're asserting that it's 0 for (p._items) |item| { item.data[0] = 0; }
const t1 = try std.Thread.spawn(.{}, testPool, .{&p}); const t2 = try std.Thread.spawn(.{}, testPool, .{&p}); const t3 = try std.Thread.spawn(.{}, testPool, .{&p});
t1.join(); t2.join(); t3.join();}
fn testPool(p: *Growing(TestPoolItem, void)) !void { const random = t.Random.random();
for (0..5000) |_| { var sb = p.acquire() catch unreachable; // no other thread should have set this to 255 std.debug.assert(sb.data[0] == 0);
sb.data[0] = 255; try std.Io.sleep(t.io, .fromNanoseconds(random.uintAtMost(u32, 100_000)), .real); sb.data[0] = 0; p.release(sb); }}
const TestPoolItem = struct { data: []u8, allocator: Allocator,
fn init(allocator: Allocator) !TestPoolItem { const data = try allocator.alloc(u8, 1); data[0] = 0;
return .{ .data = data, .allocator = allocator, }; }
fn deinit(self: *TestPoolItem) void { self.allocator.free(self.data); }
fn reset(self: *TestPoolItem) void { self.data[0] = 0; }};