An HTTP/1.1 server for zig
0

Configure Feed

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

http.zig / src / buffer.zig
6.0 kB 213 lines
1const std = @import("std"); 2const metrics = @import("metrics.zig"); 3const blockingMode = @import("httpz.zig").blockingMode; 4 5const Io = std.Io; 6const Mutex = Io.Mutex; 7const Allocator = std.mem.Allocator; 8 9pub const Buffer = struct { 10 data: []u8, 11 type: Type, 12 13 const Type = enum { 14 arena, 15 static, 16 pooled, 17 dynamic, 18 }; 19}; 20 21pub const Pool = struct { 22 io: Io, 23 mutex: Mutex, 24 available: usize, 25 buffers: []Buffer, 26 allocator: Allocator, 27 buffer_size: usize, 28 29 pub fn init(io: Io, allocator: Allocator, count: usize, buffer_size: usize) !Pool { 30 const buffers = try allocator.alloc(Buffer, count); 31 errdefer allocator.free(buffers); 32 33 var initialized: usize = 0; 34 errdefer { 35 for (0..initialized) |i| { 36 allocator.free(buffers[i].data); 37 } 38 } 39 40 for (0..count) |i| { 41 buffers[i] = .{ 42 .type = .pooled, 43 .data = try allocator.alloc(u8, buffer_size), 44 }; 45 initialized += 1; 46 } 47 48 return .{ 49 .io = io, 50 .mutex = .init, 51 .buffers = buffers, 52 .available = count, 53 .allocator = allocator, 54 .buffer_size = buffer_size, 55 }; 56 } 57 58 pub fn deinit(self: *Pool) void { 59 const allocator = self.allocator; 60 for (self.buffers) |buf| { 61 allocator.free(buf.data); 62 } 63 allocator.free(self.buffers); 64 } 65 66 pub fn static(self: Pool, size: usize) !Buffer { 67 return .{ 68 .type = .static, 69 .data = try self.allocator.alloc(u8, size), 70 }; 71 } 72 73 pub fn alloc(self: *Pool, size: usize) !Buffer { 74 return self.allocType(self.allocator, .dynamic, size); 75 } 76 77 pub fn arenaAlloc(self: *Pool, arena: Allocator, size: usize) !Buffer { 78 return self.allocType(arena, .arena, size); 79 } 80 81 // Returns a pooled buffer, or null if the pool is empty. Never falls back 82 // to allocating. Useful when the caller wants to use the pool as a hot path 83 // and handle the empty case with a different strategy. 84 pub fn tryAlloc(self: *Pool) ?Buffer { 85 self.mutex.lockUncancelable(self.io); 86 defer self.mutex.unlock(self.io); 87 88 const available = self.available; 89 if (available == 0) { 90 return null; 91 } 92 const index = available - 1; 93 const buffer = self.buffers[index]; 94 self.available = index; 95 return buffer; 96 } 97 98 fn allocType(self: *Pool, allocator: Allocator, buffer_type: Buffer.Type, size: usize) !Buffer { 99 if (size > self.buffer_size) { 100 metrics.allocBufferLarge(size); 101 return .{ 102 .type = buffer_type, 103 .data = try allocator.alloc(u8, size), 104 }; 105 } 106 107 self.mutex.lockUncancelable(self.io); 108 const available = self.available; 109 if (available == 0) { 110 self.mutex.unlock(self.io); 111 metrics.allocBufferEmpty(size); 112 return .{ 113 .type = buffer_type, 114 .data = try allocator.alloc(u8, size), 115 }; 116 } 117 defer self.mutex.unlock(self.io); 118 119 const index = available - 1; 120 const buffer = self.buffers[index]; 121 self.available = index; 122 return buffer; 123 } 124 125 pub fn free(self: *Pool, buffer: Buffer) void { 126 switch (buffer.type) { 127 .arena => {}, 128 .pooled => self.release(buffer), 129 .static => self.allocator.free(buffer.data), 130 .dynamic => self.allocator.free(buffer.data), 131 } 132 } 133 134 pub fn release(self: *Pool, buffer: Buffer) void { 135 switch (buffer.type) { 136 .static, .arena => {}, 137 .dynamic => self.allocator.free(buffer.data), 138 .pooled => { 139 self.mutex.lockUncancelable(self.io); 140 defer self.mutex.unlock(self.io); 141 const available = self.available; 142 self.buffers[available] = buffer; 143 self.available = available + 1; 144 }, 145 } 146 } 147}; 148 149const t = @import("t.zig"); 150 151test "BufferPool race" { 152 if (comptime blockingMode()) return error.SkipZigTest; 153 154 var pool = try Pool.init(t.io, t.allocator, 1, 64); 155 defer pool.deinit(); 156 157 const Ctx = struct { 158 fn worker(p: *Pool) void { 159 var i: usize = 0; 160 while (i < 500_000) : (i += 1) { 161 const buf = p.tryAlloc() orelse continue; 162 std.atomic.spinLoopHint(); 163 p.release(buf); 164 } 165 } 166 }; 167 168 var threads: [4]std.Thread = undefined; 169 for (&threads) |*thr| thr.* = try std.Thread.spawn(.{}, Ctx.worker, .{&pool}); 170 for (&threads) |*thr| thr.join(); 171} 172 173test "BufferPool" { 174 var pool = try Pool.init(t.io, t.allocator, 2, 10); 175 defer pool.deinit(); 176 177 { 178 // bigger than our buffers in pool 179 const buffer = try pool.alloc(11); 180 defer pool.release(buffer); 181 try t.expectEqual(.dynamic, buffer.type); 182 try t.expectEqual(11, buffer.data.len); 183 } 184 185 { 186 // smaller than our buffers in pool 187 const buf1 = try pool.alloc(4); 188 try t.expectEqual(.pooled, buf1.type); 189 try t.expectEqual(10, buf1.data.len); 190 191 const buf2 = try pool.alloc(5); 192 try t.expectEqual(.pooled, buf2.type); 193 try t.expectEqual(10, buf2.data.len); 194 195 try t.expectEqual(false, &buf1.data[0] == &buf2.data[0]); 196 197 // no more buffers in the pool, creates a dynamic buffer 198 const buf3 = try pool.alloc(6); 199 try t.expectEqual(.dynamic, buf3.type); 200 try t.expectEqual(6, buf3.data.len); 201 202 pool.release(buf1); 203 204 // now has items! 205 const buf4 = try pool.alloc(6); 206 try t.expectEqual(.pooled, buf4.type); 207 try t.expectEqual(10, buf4.data.len); 208 209 pool.release(buf2); 210 pool.release(buf3); 211 pool.release(buf4); 212 } 213}