@@ -46,12 +46,18 @@ class PostgresConnectionPool {
4646 PostgresConnectionPool ({
4747 required this .createAndConnect,
4848 this .idleDisposeDelay = defaultIdleDisposeDelay,
49+ this .maxEntries = defaultMaxEntries,
4950 });
5051
5152 static const Duration defaultIdleDisposeDelay = Duration (seconds: 8 );
5253
54+ /// Max distinct pool keys `(connection id, database, mode)` . When full,
55+ /// least-recently-used **idle** slots (`refs == 0` ) are closed first.
56+ static const int defaultMaxEntries = 32 ;
57+
5358 final PostgresPoolConnectionFactory createAndConnect;
5459 final Duration idleDisposeDelay;
60+ final int maxEntries;
5561
5662 final Map <String , _PoolEntry > _pool = {};
5763
@@ -67,6 +73,7 @@ class PostgresConnectionPool {
6773 final k = keyFor (row.id, database, mode);
6874 var entry = _pool[k];
6975 if (entry != null ) {
76+ entry.touch ();
7077 entry.idleTimer? .cancel ();
7178 entry.idleTimer = null ;
7279 entry.refs++ ;
@@ -77,12 +84,35 @@ class PostgresConnectionPool {
7784 return PgLease ._(this , k, entry.connection);
7885 }
7986
87+ _evictIfNeededBeforeNewSlot ();
88+
8089 final conn = await createAndConnect (row, database: database, mode: mode);
8190 entry = _PoolEntry (conn)..refs = 1 ;
8291 _pool[k] = entry;
8392 return PgLease ._(this , k, conn);
8493 }
8594
95+ /// Drops idle LRU slots until there is room for one more key.
96+ void _evictIfNeededBeforeNewSlot () {
97+ while (_pool.length >= maxEntries) {
98+ final idle = _pool.entries.where ((e) => e.value.refs == 0 ).toList ();
99+ if (idle.isEmpty) {
100+ throw StateError (
101+ 'PostgreSQL connection pool exhausted: $maxEntries slots in use.' ,
102+ );
103+ }
104+ idle.sort ((a, b) => a.value.lastUsed.compareTo (b.value.lastUsed));
105+ _removeEntryClosing (idle.first.key);
106+ }
107+ }
108+
109+ void _removeEntryClosing (String k) {
110+ final entry = _pool.remove (k);
111+ if (entry == null ) return ;
112+ entry.idleTimer? .cancel ();
113+ unawaited (entry.connection.forceClose ());
114+ }
115+
86116 void _release (String k) {
87117 final entry = _pool[k];
88118 if (entry == null ) return ;
@@ -106,10 +136,7 @@ class PostgresConnectionPool {
106136 PgSessionMode mode = PgSessionMode .readOnly,
107137 }) {
108138 final k = keyFor (row.id, database, mode);
109- final entry = _pool.remove (k);
110- if (entry == null ) return ;
111- entry.idleTimer? .cancel ();
112- unawaited (entry.connection.forceClose ());
139+ _removeEntryClosing (k);
113140 }
114141
115142 /// Closes all pooled connections (e.g. app shutdown).
@@ -123,9 +150,12 @@ class PostgresConnectionPool {
123150}
124151
125152class _PoolEntry {
126- _PoolEntry (this .connection);
153+ _PoolEntry (this .connection) : lastUsed = DateTime . now () ;
127154
128155 final PostgresConnection connection;
129156 int refs = 0 ;
130157 Timer ? idleTimer;
158+ DateTime lastUsed;
159+
160+ void touch () => lastUsed = DateTime .now ();
131161}
0 commit comments