Summary / Описание проблемы
В пулах соединений (PostgresConnectionPool, MysqlConnectionPool, SqliteConnectionPool) проверка наличия активного соединения в _pool[k] и асинхронное создание нового соединения (await createAndConnect(...)) не синхронизированы между собой:
var entry = _pool[k];
if (entry != null) { ... return PgLease._(...); }
...
final conn = await createAndConnect(row, database: database, mode: mode);
entry = _PoolEntry(conn)..refs = 1;
_pool[k] = entry;
Поскольку вызов createAndConnect выполняется асинхронно (занимая до нескольких сотен миллисекунд на установку TCP/TLS сокета), если за это время приходит второй параллельный запрос acquire(...) для того же ключа k, он также видит _pool[k] == null и запускает повторное создание соединения.
В результате оба вызова открывают отдельные сокеты. Последний завершившийся вызов перезаписывает _pool[k] = entry, оставляя соединение от первого вызова сиротой (orphaned): его счетчик ссылок (refs) сбивается, оно больше никогда не вернется в пул и не будет закрыто при вызове _release(k), приводя к утечке сокетов в ОС.
Scope & Component
- Scope:
desktop
- Component:
Core / Connections Pool
- Kind:
bug
- Severity:
high
- Detected in version:
dev / 0.5.0
Местоположение в коде
- Файлы:
lib/core/database/postgres_connection_pool.dart (строки ~68-94, метод acquire)
lib/core/database/mysql_connection_pool.dart (строки ~64-82, метод acquire)
lib/core/database/sqlite_connection_pool.dart (строки ~66-89, метод acquire)
Шаги воспроизведения
- Запустить два параллельных асинхронных запроса на получение соединения (
acquire) для одной и той же базы данных/ключа до того, как установится первое соединение (например, при одновременной загрузке вкладок или быстром клике по дереву объектов).
- Проверить количество установленных TCP-соединений с сервером БД (
netstat или pg_stat_activity).
- Фактический результат: Создаются два (или более) физических сокета для одного ключа пула. При закрытии вкладок одно из соединений остается висеть в
netstat навсегда.
- Ожидаемый результат: Пул должен отслеживать выполняющиеся в данный момент асинхронные попытки подключения (
Map<String, Future<_PoolEntry>> _pendingAcquires), чтобы параллельные запросы дожидались уже инициированного createAndConnect вместо повторного открытия сокетов.
Acceptance Criteria / Критерии приемки
Summary / Описание проблемы
В пулах соединений (
PostgresConnectionPool,MysqlConnectionPool,SqliteConnectionPool) проверка наличия активного соединения в_pool[k]и асинхронное создание нового соединения (await createAndConnect(...)) не синхронизированы между собой:Поскольку вызов
createAndConnectвыполняется асинхронно (занимая до нескольких сотен миллисекунд на установку TCP/TLS сокета), если за это время приходит второй параллельный запросacquire(...)для того же ключаk, он также видит_pool[k] == nullи запускает повторное создание соединения.В результате оба вызова открывают отдельные сокеты. Последний завершившийся вызов перезаписывает
_pool[k] = entry, оставляя соединение от первого вызова сиротой (orphaned): его счетчик ссылок (refs) сбивается, оно больше никогда не вернется в пул и не будет закрыто при вызове_release(k), приводя к утечке сокетов в ОС.Scope & Component
desktopCore / Connections Poolbughighdev / 0.5.0Местоположение в коде
lib/core/database/postgres_connection_pool.dart(строки ~68-94, методacquire)lib/core/database/mysql_connection_pool.dart(строки ~64-82, методacquire)lib/core/database/sqlite_connection_pool.dart(строки ~66-89, методacquire)Шаги воспроизведения
acquire) для одной и той же базы данных/ключа до того, как установится первое соединение (например, при одновременной загрузке вкладок или быстром клике по дереву объектов).netstatилиpg_stat_activity).netstatнавсегда.Map<String, Future<_PoolEntry>> _pendingAcquires), чтобы параллельные запросы дожидались уже инициированногоcreateAndConnectвместо повторного открытия сокетов.Acceptance Criteria / Критерии приемки
PostgresConnectionPool,MysqlConnectionPoolиSqliteConnectionPoolдобавлен механизм учета in-flight (в процессе подключения) запросов по ключуk.acquire(...)вызывается во время активного выполненияcreateAndConnectдля того же ключа, второй вызов ожидает завершения первойFutureи использует созданное соединение.createAndConnectпри конкурентном вызовеacquireдля одного ключа.