Skip to content

bug(connections): async race condition in connection pools (Postgres, MySQL, SQLite) creates duplicate sockets and leaks orphaned entries #275

Description

@ZhuchkaTriplesix

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)

Шаги воспроизведения

  1. Запустить два параллельных асинхронных запроса на получение соединения (acquire) для одной и той же базы данных/ключа до того, как установится первое соединение (например, при одновременной загрузке вкладок или быстром клике по дереву объектов).
  2. Проверить количество установленных TCP-соединений с сервером БД (netstat или pg_stat_activity).
  3. Фактический результат: Создаются два (или более) физических сокета для одного ключа пула. При закрытии вкладок одно из соединений остается висеть в netstat навсегда.
  4. Ожидаемый результат: Пул должен отслеживать выполняющиеся в данный момент асинхронные попытки подключения (Map<String, Future<_PoolEntry>> _pendingAcquires), чтобы параллельные запросы дожидались уже инициированного createAndConnect вместо повторного открытия сокетов.

Acceptance Criteria / Критерии приемки

  • В PostgresConnectionPool, MysqlConnectionPool и SqliteConnectionPool добавлен механизм учета in-flight (в процессе подключения) запросов по ключу k.
  • Если acquire(...) вызывается во время активного выполнения createAndConnect для того же ключа, второй вызов ожидает завершения первой Future и использует созданное соединение.
  • Написаны unit-тесты, проверяющие отсутствие дублирующих вызовов createAndConnect при конкурентном вызове acquire для одного ключа.

Metadata

Metadata

Labels

bugSomething isn't workingconnectionsDatabase connections, URI parsing, poolscoreCore library logic and services

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions