Skip to content

Commit cc82b1a

Browse files
committed
Fix connection memory leak
When the idle pool is full and the oldest connection is freed, free the connection instance.
1 parent 0df531a commit cc82b1a

File tree

1 file changed

+37
-27
lines changed

1 file changed

+37
-27
lines changed

src/http/client.zig

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ pub const Client = struct {
4949
http_proxy: ?Uri,
5050
root_ca: tls.config.CertBundle,
5151
tls_verify_host: bool = true,
52-
idle_connections: IdleConnections,
53-
connection_pool: std.heap.MemoryPool(Connection),
52+
connection_manager: ConnectionManager,
5453

5554
const Opts = struct {
5655
tls_verify_host: bool = true,
@@ -65,17 +64,16 @@ pub const Client = struct {
6564
const state_pool = try StatePool.init(allocator, max_concurrent);
6665
errdefer state_pool.deinit(allocator);
6766

68-
const idle_connections = IdleConnections.init(allocator, opts.max_idle_connection);
69-
errdefer idle_connections.deinit();
67+
const connection_manager = ConnectionManager.init(allocator, opts.max_idle_connection);
68+
errdefer connection_manager.deinit();
7069

7170
return .{
7271
.root_ca = root_ca,
7372
.allocator = allocator,
7473
.state_pool = state_pool,
7574
.http_proxy = opts.http_proxy,
76-
.idle_connections = idle_connections,
7775
.tls_verify_host = opts.tls_verify_host,
78-
.connection_pool = std.heap.MemoryPool(Connection).init(allocator),
76+
.connection_manager = connection_manager,
7977
};
8078
}
8179

@@ -85,8 +83,7 @@ pub const Client = struct {
8583
self.root_ca.deinit(allocator);
8684
}
8785
self.state_pool.deinit(allocator);
88-
self.idle_connections.deinit();
89-
self.connection_pool.deinit();
86+
self.connection_manager.deinit();
9087
}
9188

9289
pub fn request(self: *Client, method: Request.Method, uri: *const Uri) !Request {
@@ -307,36 +304,33 @@ pub const Request = struct {
307304
self._connection = null;
308305

309306
if (self._keepalive == false) {
310-
self.destroyConnection(connection);
307+
self._client.connection_manager.destroy(connection);
311308
return;
312309
}
313310

314-
self._client.idle_connections.put(connection) catch |err| {
311+
self._client.connection_manager.keepIdle(connection) catch |err| {
315312
self.destroyConnection(connection);
316313
log.err("failed to release connection to pool: {}", .{err});
317314
};
318315
}
319316

320317
fn createConnection(self: *Request, socket: posix.socket_t, blocking: bool) !*Connection {
321318
const client = self._client;
322-
const connection = try client.connection_pool.create();
323-
errdefer client.connection_pool.destroy(connection);
319+
const connection, const owned_host = try client.connection_manager.create(self._connect_host);
324320

325321
connection.* = .{
326322
.tls = null,
327323
.socket = socket,
328324
.blocking = blocking,
325+
.host = owned_host,
329326
.port = self._connect_port,
330-
.host = try client.allocator.dupe(u8, self._connect_host),
331327
};
332328

333329
return connection;
334330
}
335331

336332
fn destroyConnection(self: *Request, connection: *Connection) void {
337-
const client = self._client;
338-
connection.deinit(client.allocator);
339-
client.connection_pool.destroy(connection);
333+
self._client.connection_manager.destroy(connection);
340334
}
341335

342336
const AddHeaderOpts = struct {
@@ -403,9 +397,10 @@ pub const Request = struct {
403397
posix.close(socket);
404398
return err;
405399
};
400+
self._connection = connection;
406401

407402
if (self._secure) {
408-
connection.tls = .{
403+
self._connection.?.tls = .{
409404
.blocking = try tls.client(std.net.Stream{ .handle = socket }, .{
410405
.host = self._connect_host,
411406
.root_ca = self._client.root_ca,
@@ -415,7 +410,6 @@ pub const Request = struct {
415410
};
416411
}
417412

418-
self._connection = connection;
419413
self._connection_from_keepalive = false;
420414
}
421415

@@ -605,7 +599,7 @@ pub const Request = struct {
605599
return null;
606600
}
607601

608-
return self._client.idle_connections.get(self._secure, self._connect_host, self._connect_port, blocking);
602+
return self._client.connection_manager.get(self._secure, self._connect_host, self._connect_port, blocking);
609603
}
610604

611605
fn createSocket(self: *Request, blocking: bool) !struct { posix.socket_t, std.net.Address } {
@@ -2219,29 +2213,31 @@ const StatePool = struct {
22192213
// always re-use the connection (just toggle the socket's blocking status), but
22202214
// for TLS, we'd need to see if the two different TLS objects (blocking and non
22212215
// blocking) can be converted from each other.
2222-
const IdleConnections = struct {
2216+
const ConnectionManager = struct {
22232217
max: usize,
22242218
idle: List,
22252219
count: usize,
22262220
mutex: Thread.Mutex,
22272221
allocator: Allocator,
22282222
node_pool: std.heap.MemoryPool(Node),
2223+
connection_pool: std.heap.MemoryPool(Connection),
22292224

22302225
const List = std.DoublyLinkedList(*Connection);
22312226
const Node = List.Node;
22322227

2233-
fn init(allocator: Allocator, max: usize) IdleConnections {
2228+
fn init(allocator: Allocator, max: usize) ConnectionManager {
22342229
return .{
22352230
.max = max,
22362231
.count = 0,
22372232
.idle = .{},
22382233
.mutex = .{},
22392234
.allocator = allocator,
22402235
.node_pool = std.heap.MemoryPool(Node).init(allocator),
2236+
.connection_pool = std.heap.MemoryPool(Connection).init(allocator),
22412237
};
22422238
}
22432239

2244-
fn deinit(self: *IdleConnections) void {
2240+
fn deinit(self: *ConnectionManager) void {
22452241
const allocator = self.allocator;
22462242

22472243
self.mutex.lock();
@@ -2253,9 +2249,10 @@ const IdleConnections = struct {
22532249
node = next;
22542250
}
22552251
self.node_pool.deinit();
2252+
self.connection_pool.deinit();
22562253
}
22572254

2258-
fn get(self: *IdleConnections, secure: bool, host: []const u8, port: u16, blocking: bool) ?*Connection {
2255+
fn get(self: *ConnectionManager, secure: bool, host: []const u8, port: u16, blocking: bool) ?*Connection {
22592256
self.mutex.lock();
22602257
defer self.mutex.unlock();
22612258

@@ -2273,28 +2270,41 @@ const IdleConnections = struct {
22732270
return null;
22742271
}
22752272

2276-
fn put(self: *IdleConnections, connection: *Connection) !void {
2273+
fn keepIdle(self: *ConnectionManager, connection: *Connection) !void {
22772274
self.mutex.lock();
22782275
defer self.mutex.unlock();
22792276

22802277
var node: *Node = undefined;
22812278
if (self.count == self.max) {
22822279
const oldest = self.idle.popFirst() orelse {
22832280
std.debug.assert(self.max == 0);
2284-
connection.deinit(self.allocator);
2281+
self.destroy(connection);
22852282
return;
22862283
};
2287-
oldest.data.deinit(self.allocator);
2284+
self.destroy(oldest.data);
22882285
// re-use the node
22892286
node = oldest;
22902287
} else {
2291-
self.count += 1;
22922288
node = try self.node_pool.create();
2289+
self.count += 1;
22932290
}
22942291

22952292
node.data = connection;
22962293
self.idle.append(node);
22972294
}
2295+
2296+
fn create(self: *ConnectionManager, host: []const u8) !struct { *Connection, []const u8 } {
2297+
const connection = try self.connection_pool.create();
2298+
errdefer self.connection_pool.destroy(connection);
2299+
2300+
const owned_host = try self.allocator.dupe(u8, host);
2301+
return .{ connection, owned_host };
2302+
}
2303+
2304+
fn destroy(self: *ConnectionManager, connection: *Connection) void {
2305+
connection.deinit(self.allocator);
2306+
self.connection_pool.destroy(connection);
2307+
}
22982308
};
22992309

23002310
const testing = @import("../testing.zig");

0 commit comments

Comments
 (0)