diff --git a/src/http/http2/pool.zig b/src/http/http2/pool.zig index 5ee77f7..cdb6595 100644 --- a/src/http/http2/pool.zig +++ b/src/http/http2/pool.zig @@ -50,6 +50,8 @@ pub const H2ConnectionPool = struct { /// Initialize pool with backend servers pub fn init(backends: []const BackendServer, allocator: std.mem.Allocator) Self { + std.debug.assert(backends.len <= MAX_BACKENDS); + var pool = Self{ .slots = undefined, .slot_state = undefined, @@ -89,10 +91,16 @@ pub const H2ConnectionPool = struct { for (&self.slot_state[backend_idx], &self.slots[backend_idx], 0..) |*state, *slot, i| { if (state.* == .available) { if (slot.*) |conn| { - // Skip stale connections - DON'T destroy here! - // Other requests may still hold references. Let release() handle cleanup. + // Destroy stale connections so slots don't remain permanently unavailable. + // Available slots must have ref_count == 0. if (self.isConnectionStale(conn)) { - log.debug("Connection stale, skipping: backend={d} slot={d}", .{ backend_idx, i }); + log.debug("Connection stale, destroying: backend={d} slot={d}", .{ backend_idx, i }); + // backend_mutex serializes getOrCreate calls; release() marks a slot + // available only after its acq_rel fetchSub drops ref_count to 0. + std.debug.assert(conn.ref_count.load(.monotonic) == 0); + slot.* = null; + state.* = .empty; + self.destroyConnection(conn, io); continue; }