diff --git a/src/pool.zig b/src/pool.zig index 62ad6bd..b256431 100644 --- a/src/pool.zig +++ b/src/pool.zig @@ -536,6 +536,47 @@ test "Pool: deinit while the reconnector is retrying" { try t.expectEqual(true, elapsed < std.time.ns_per_s); } +// Regression: the reconnector task exits once it has restored everything it +// was asked for, and a connection lost after that must start it again. It used +// to be spawned once per pool, so every later loss stayed missing until the +// pool reported PoolExhausted. +test "Pool: the reconnector restores a connection lost after it finished" { + const io = t.io; + + // the lazily opened connection is the reconnector's first job + var pool = try Pool.init(io, t.allocator, .{ + .size = 2, + .auth = t.authOpts(.{}), + .connect = t.connectOpts(), + .connect_on_init_count = 1, + }); + defer pool.deinit(); + try expectPoolRestored(pool, 2); + + // what release() does when it cannot replace a dirty connection inline + const lost = try pool.acquire(); + lost.deinit(); + t.allocator.destroy(lost); + pool._mutex.lockUncancelable(io); + pool._missing += 1; + pool._mutex.unlock(io); + try pool._reconnector.reconnect(); + + try expectPoolRestored(pool, 2); +} + +fn expectPoolRestored(pool: *Pool, size: usize) !void { + const io = t.io; + const start = std.Io.Timestamp.now(io, .awake); + while (true) { + const s = pool.stats(); + if (s.missing == 0 and s.available == size) return; + const elapsed = start.durationTo(std.Io.Timestamp.now(io, .awake)).toNanoseconds(); + if (elapsed > 5 * std.time.ns_per_s) return error.PoolNotRestored; + try std.Io.sleep(io, .fromNanoseconds(10 * std.time.ns_per_ms), .awake); + } +} + test "Pool: Release" { var pool = try Pool.init(t.io, t.allocator, .{ .size = 2,