@@ -323,10 +323,11 @@ pub const Pool = struct {
323323 pub fn queryOne (self : * Pool , sql : []const u8 , binds : []const core.Value ) ! core.OwnedRow {
324324 var lease = try self .acquire ();
325325 errdefer if (lease .open ) lease .discard () catch {};
326- const owned = (try lease .conn ()).queryOne (sql , binds ) catch | err | {
326+ var owned = (try lease .conn ()).queryOne (sql , binds ) catch | err | {
327327 lease .finishAfterError (err );
328328 return err ;
329329 };
330+ errdefer owned .deinit ();
330331 try lease .release ();
331332 return owned ;
332333 }
@@ -340,6 +341,7 @@ pub const Pool = struct {
340341 lease .finishAfterError (err );
341342 return err ;
342343 };
344+ errdefer core .OwnedRow .freeSlice (self .allocator , owned );
343345 try lease .release ();
344346 return owned ;
345347 }
@@ -431,7 +433,14 @@ pub const Lease = struct {
431433 }
432434
433435 if (self .pool .idle .items .len < self .pool .effectiveMaxIdle ()) {
434- try self .pool .idle .append (self .pool .allocator , self .db );
436+ self .pool .idle .append (self .pool .allocator , self .db ) catch | err | {
437+ self .conn_value .close ();
438+ self .db .deinit ();
439+ self .pool .open_count - |= 1 ;
440+ self .open = false ;
441+ self .pool .notifyAvailable ();
442+ return err ;
443+ };
435444 self .conn_value .close ();
436445 } else {
437446 self .conn_value .close ();
@@ -3000,6 +3009,72 @@ test "SQLite pool timed acquire unblocks after concurrent release" {
30003009 try std .testing .expectEqual (@as (usize , 0 ), ctx .err_len );
30013010}
30023011
3012+ test "SQLite lease release consumes connection when idle growth is OOM" {
3013+ var failing = std .testing .FailingAllocator .init (std .testing .allocator , .{});
3014+ var pool = try Pool .init (failing .allocator (), std .testing .io , .{
3015+ .max_open = 1 ,
3016+ .max_idle = 1 ,
3017+ });
3018+ defer pool .deinit ();
3019+
3020+ var lease = try pool .acquire ();
3021+ try (try lease .conn ()).ping ();
3022+ failing .fail_index = failing .alloc_index ;
3023+ try std .testing .expectError (error .OutOfMemory , lease .release ());
3024+ try std .testing .expect (failing .has_induced_failure );
3025+ try std .testing .expect (! lease .open );
3026+ try std .testing .expectEqual (@as (usize , 0 ), pool .stats ().open );
3027+ try std .testing .expectEqual (@as (usize , 0 ), pool .stats ().leased );
3028+
3029+ failing .fail_index = std .math .maxInt (usize );
3030+ try pool .ping ();
3031+ try std .testing .expectEqual (@as (usize , 1 ), pool .stats ().idle );
3032+ }
3033+
3034+ test "SQLite owned pool results unwind when release observes shutdown" {
3035+ const CloseOnQuery = struct {
3036+ pool : * Pool ,
3037+ closed : bool = false ,
3038+
3039+ fn after (raw : ? * anyopaque , _ : core.QueryEnd ) void {
3040+ const self : * @This () = @ptrCast (@alignCast (raw .? ));
3041+ if (self .closed ) return ;
3042+ self .closed = true ;
3043+ self .pool .deinit ();
3044+ }
3045+
3046+ fn hooks (self : * @This ()) core.Hooks {
3047+ return .{ .ctx = self , .after_query = after };
3048+ }
3049+ };
3050+
3051+ {
3052+ var pool = try Pool .init (std .testing .allocator , std .testing .io , .{ .max_open = 1 });
3053+ defer pool .deinit ();
3054+ _ = try pool .exec ("create table release_one (id integer primary key)" , &.{});
3055+ _ = try pool .exec ("insert into release_one (id) values (1)" , &.{});
3056+ var state = CloseOnQuery { .pool = & pool };
3057+ pool .config .hooks = state .hooks ();
3058+ try std .testing .expectError (
3059+ error .PoolClosed ,
3060+ pool .queryOne ("select id from release_one" , &.{}),
3061+ );
3062+ }
3063+
3064+ {
3065+ var pool = try Pool .init (std .testing .allocator , std .testing .io , .{ .max_open = 1 });
3066+ defer pool .deinit ();
3067+ _ = try pool .exec ("create table release_all (id integer primary key)" , &.{});
3068+ _ = try pool .exec ("insert into release_all (id) values (1), (2)" , &.{});
3069+ var state = CloseOnQuery { .pool = & pool };
3070+ pool .config .hooks = state .hooks ();
3071+ try std .testing .expectError (
3072+ error .PoolClosed ,
3073+ pool .queryAll ("select id from release_all order by id" , &.{}),
3074+ );
3075+ }
3076+ }
3077+
30033078test "SQLite pool queryOne returns single owned row" {
30043079 var pool = try Pool .init (std .testing .allocator , std .testing .io , .{ .max_open = 2 });
30053080 defer pool .deinit ();
0 commit comments