Part of #1212.
Problem
A lease is released by key, not by the pool entry it was taken from.
PgLease.release() calls _pool._release(_key) (lib/core/database/postgres_connection_pool.dart:44-49). _release looks up _pool[k] and decrements its refs (:156-169).
interrupt() removes the entry and force-closes its connection (:173-180 → _removeEntryClosing). Leases taken before still point at the closed connection, but the key is free.
- The next
acquire() with that key creates a new entry. When an old lease is released afterwards, _release finds the new entry by key and decrements its refs.
Effects:
refs of the new entry reaches 0 while a caller still uses it. The idle timer (defaultIdleDisposeDelay, 4 s, :63) then disconnects the session under that caller: Not connected to PostgreSQL on the next statement, seconds after an unrelated action.
refs can go negative; later releases and the idle timer no longer match the real number of users.
The same code exists in MysqlConnectionPool._release (lib/core/database/mysql_connection_pool.dart:143) and SqliteConnectionPool._release (lib/core/database/sqlite_connection_pool.dart:150).
interrupt() runs often: every table tab dispose (#1215), the SQL editor's Cancel (PostgresSqlExecutionDelegate.cancelQuery, lib/features/postgresql/postgres_sql_workspace.dart:163-171), and the timeout paths.
Scope
- A lease keeps a reference to the
_PoolEntry it was given (or the entry's generation number). release() decrements only that entry; if the entry is no longer in the pool, release is a no-op.
- Apply to all three pools. Consider one generic pool implementation for the three drivers, since the bookkeeping is identical.
- Debug assert that
refs never goes below 0.
Acceptance
Part of #1212.
Problem
A lease is released by key, not by the pool entry it was taken from.
PgLease.release()calls_pool._release(_key)(lib/core/database/postgres_connection_pool.dart:44-49)._releaselooks up_pool[k]and decrements itsrefs(:156-169).interrupt()removes the entry and force-closes its connection (:173-180→_removeEntryClosing). Leases taken before still point at the closed connection, but the key is free.acquire()with that key creates a new entry. When an old lease is released afterwards,_releasefinds the new entry by key and decrements itsrefs.Effects:
refsof the new entry reaches 0 while a caller still uses it. The idle timer (defaultIdleDisposeDelay, 4 s,:63) then disconnects the session under that caller:Not connected to PostgreSQLon the next statement, seconds after an unrelated action.refscan go negative; later releases and the idle timer no longer match the real number of users.The same code exists in
MysqlConnectionPool._release(lib/core/database/mysql_connection_pool.dart:143) andSqliteConnectionPool._release(lib/core/database/sqlite_connection_pool.dart:150).interrupt()runs often: every table tab dispose (#1215), the SQL editor's Cancel (PostgresSqlExecutionDelegate.cancelQuery,lib/features/postgresql/postgres_sql_workspace.dart:163-171), and the timeout paths.Scope
_PoolEntryit was given (or the entry's generation number).release()decrements only that entry; if the entry is no longer in the pool, release is a no-op.refsnever goes below 0.Acceptance
interrupt, acquire lease B (new entry), release A → B'srefsis still 1 and B is not disconnected after the idle delay.disconnectAll, changes nothing.refscannot become negative (assert covered by a test).