Connection.close(timeout=) waits forever on a pending cancel when the server never acknowledges it
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 68/100
調査の方向性
asyncpg/protocol/protocol.pyx で close(self, timeout)、_request_cancel()、_handle_waiter_on_connection_lost()、_on_connection_lost() を読み始めます。issue に記載されているサーバーがフリーズしたケースを再現し、その後 close(timeout=2) が戻り、transport の喪失によって cancel_waiter が pending のまま残らないことを確認します。その間、接続は期待どおり abort にフォールバックします。
索引モデルが issue の本文から書いたものです。
説明
Summary
When a statement times out (command_timeout) while the server, or a pooler in front of it, is frozen, asyncpg requests a cancel and then every later operation on that connection, including close(timeout=...), awaits the cancel acknowledgement with no bound. The timeout argument of close() does not cover that wait, and a subsequent transport loss does not resolve it either, so the connection can never be closed gracefully and any caller that awaits close() hangs indefinitely.
Versions
- asyncpg 0.30.0 and 0.31.0 (same code shape in both)
- Python 3.12.3, Linux
- Observed through SQLAlchemy 2.0.52's asyncpg dialect, which calls
Connection.close(timeout=2)when invalidating a connection after aTimeoutError, but the behaviour is asyncpg's.
Where in the source (0.31.0)
asyncpg/protocol/protocol.pyx,close(self, timeout): awaitsself.cancel_sent_waiterand thenif self.cancel_waiter is not None: await self.cancel_waiterbefore the part that is guarded bytimeout._request_cancel()(called from_on_timeout()) createscancel_waiter; it is resolved only by a ReadyForQuery arriving on the original socket._handle_waiter_on_connection_lost()and_on_connection_lost()resolveself.waiteronly;cancel_waiteris left pending when the transport is lost.abort()returns early whenself.closingis already set, so cancelling a stuckclose()from outside and then callingConnection._abort()does not close the transport.
Reproduction
- Run PostgreSQL behind pgbouncer (transaction pooling), or plain PostgreSQL.
- Open a connection with
command_timeout=5, runSELECT pg_sleep(40). - While it runs, freeze the server process (
podman pause/kill -STOPon postgres, or on pgbouncer). - The statement raises
asyncio.TimeoutErrorafter 5 s and asyncpg starts a cancel task. - Now
await conn.close(timeout=2): it never returns while the freeze lasts. If the frozen side is later closed by a pooler timeout (pgbouncerquery_timeoutcloses the client socket),close()still never returns because the transport loss resolves only the query waiter.
Observed with an asyncio task-stack watchdog: the caller sits in Connection.close → protocol.close → await self.cancel_waiter, and the _cancel task sits in connect_utils awaiting the cancel connection's on_disconnect, for as long as the server stays frozen (minutes; unbounded).
Expected
close(timeout=t)should bound the wait for the cancel acknowledgement byt(or by the connection'scommand_timeout) and fall back to aborting the transport._on_connection_lost()should resolvecancel_waiter(with the same connection-lost exception it uses forwaiter) so that a lost transport cannot leave a permanently pending cancel.
Workaround we use
An application-level guard that, on TimeoutError/CancelledError, runs asyncio.wait_for(conn.close(timeout=g), g) and on expiry calls conn.terminate() followed by an explicit conn._transport.abort(), because terminate() alone leaves the socket open once close() has marked the protocol as closing.
- 主要言語
- Python
- スター
- 8.1k
- フォーク
- 469
- 平均マージ
- 4時間 42分
- マージ済み PR(30日)
- 6
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
MagicStack/asyncpg のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
MagicStack/asyncpg#1357 ·
-
tests fail in 2032 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
MagicStack/asyncpg#997 ·
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
MagicStack/asyncpg#1354 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 72/100
MagicStack/asyncpg#1342 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 56/100
MagicStack/asyncpg#1340 · コメント 1 件 ·
MagicStack/asyncpg の issue をすべて見る
似ている issue
-
documentation help wanted
難易度 2/5 1〜3時間 初心者へのやさしさ 90/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 90/100
simonw/sqlite-utils#872 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100