Repository navigation
Conversation
|
Hi, The pr template asks you to open an issue first. Please do that |
| await_(asyncio.shield(self._connection.close(timeout=2))) | ||
| except ( | ||
| asyncio.TimeoutError, | ||
| asyncio.CancelledError, |
There was a problem hiding this comment.
why remove CancelledError here? is it 100% impossible for CancelledError to be raised from sheild()?
|
From what I understand, it is possible only if the shielded function
cancels itself from within.
But I'm no asyncio expert.
…On Sun, Nov 10, 2024, 8:37 a.m. Michael Bayer ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In lib/sqlalchemy/dialects/postgresql/asyncpg.py
<#12076 (comment)>
:
> except (
asyncio.TimeoutError,
- asyncio.CancelledError,
why remove CancelledError here? is it 100% impossible for CancelledError
to be raised from sheild()?
—
Reply to this email directly, view it on GitHub
<#12076 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AASBOTOXEWYJBEZPTS6IFPLZ75OQTAVCNFSM6AAAAABRQFJDJ6VHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDIMRVG43DINBQGE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
I would leave it, if only to make sure it's still there if the code above changes. I'm not sure it's apparent but this block of code has been a struggle for years to get asyncpg to close connections |
|
Fair.
I'll add it back later today (not at the terminal for a while)
On Sun, Nov 10, 2024, 9:48 a.m. Michael Bayer ***@***.***>
wrote:
… From what I understand, it is possible only if the shielded function
cancels itself from within. But I'm no asyncio expert.
… <#m_1772178836461687846_>
On Sun, Nov 10, 2024, 8:37 a.m. Michael Bayer *@*.*> wrote: @.**
commented on this pull request. ------------------------------ In
lib/sqlalchemy/dialects/postgresql/asyncpg.py <#12076 (comment)
<#12076 (comment)>>
: > except ( asyncio.TimeoutError, - asyncio.CancelledError, why remove
CancelledError here? is it 100% impossible for CancelledError to be raised
from sheild()? — Reply to this email directly, view it on GitHub <#12076
(review)
<#12076 (review)>>,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AASBOTOXEWYJBEZPTS6IFPLZ75OQTAVCNFSM6AAAAABRQFJDJ6VHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDIMRVG43DINBQGE
. You are receiving this because you authored the thread.Message ID: *@*
.***>
I would leave it, if only to make sure it's still there if the code above
changes. I'm not sure it's apparent but this block of code has been a
struggle for years to get asyncpg to close connections
—
Reply to this email directly, view it on GitHub
<#12076 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AASBOTO2GOOTQR7M5XQAO4LZ75W5JAVCNFSM6AAAAABRQFJDJ6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDINRWG43DIMBSGI>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
|
I would need to re-look at the old PRs that changed this bit. If I'm not mistaken there was a reason why shield was not added here. In any case it's very annoying that asyncio behaves one way while anyio behaves in another way... |
|
Indeed, having asyncio and anyio behave differently in the error path is surprising. I see what you mean by connection and sessions both using shield, I'll run some tests to see why it doesn't seem to be working as intended. btw I suggested the fix in this PR as a way to avoid the leak observed in the reproducer script, but I have very little exposure to async python and sqlalchemy in general, so feel free to ignore the proposed fix. |
|
ok the key here seems to be that the reproducer uses here's a traceback from where SA starts to invalidate the session/connection (and ends up trying to abort the connection: And the traceback that It is possible that the transaction |
If terminate is called from an asyncio.CancelledError exception handler, under an anyio task group, await will bail with asyncio.CancelledError right away, leading to a connection leak. Shielding the close() call allows the connection to be properly closed in those cases.
c7e6729 to
6ae261e
Compare
sqla-tester
left a comment
There was a problem hiding this comment.
OK, this is sqla-tester setting up my work on behalf of zzzeek to try to get revision 6ae261e of this pull request into gerrit so we can run tests and reviews and stuff
|
New Gerrit review created for change 6ae261e: https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5594 |
|
is there any way of testing this? |
|
the only way I could reproduce and test this reliably is with the code in the description and observing that the socket is closed with netstat + a select in In other words, I'm not sure how to add a test for this. |
|
Michael Bayer (zzzeek) wrote: this is fine can we work out a changelog message and a 2.0 port View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5594 |
|
Federico Caselli (CaselIT) wrote: I don't particularly like changing stuff randomly until hopefully things stop breaking, but I guess in asyncio that's the expectation... Doesn't seems to break any test, so hopefully it's fine View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5594 |
|
Would you be willing to backport this change to the 1.4 branch? I'm noticing a similar issue there, and if I replicate the change you're making here (sqlalchemy/dialects/postgresql/asyncpg.py:749) then the issue is resolved. |
|
It's not a given that 1.4 will have other releases. It's probably best if you patch the dialect with the changed method |
|
Michael Bayer (zzzeek) wrote: OK changelog + 2.0 backport? View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5594 |
|
Michael Bayer (zzzeek) wrote: mypy-recheck View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5667 |
|
Gerrit review https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5594 has been merged. Congratulations! :) |
|
Gerrit review https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5667 has been merged. Congratulations! :) |
Added an additional ``shield()`` call within the connection terminate process of the asyncpg driver, to mitigate an issue where terminate would be prevented from completing under the anyio concurrency library. Fixes #12077 Closes: #12076 Pull-request: #12076 Pull-request-sha: 6ae261e Change-Id: Iaec63d02b620201dc60ffdad76ba9d61d427cac1 (cherry picked from commit 2f6ca65)
I hit a connection leak when cancelling a task in a fastapi + starlette + BaseHTTPMiddleware + SA + asyncpg setup and managed to reproduce it with the code below.
From my understanding, when cancelling an
anyiotaskgroup which contains a task that checked out a connection from the pool, SA tries to close the connection, but most (all?)awaitcalls within that code path bail withasyncio.CancelledError, leading to SA and asyncpg thinking that the connection was closed / released, but the socket is still alive and idle from postgres's point of view.This is related to MagicStack/asyncpg#1156 which doesn't currently have a resolution.
The solution proposed here might not be the right place to fix this issue, but it does fix my reproducer :)
The following code leaks a connection on every loop iteration:
Description
If terminate is called from an asyncio.CancelledError exception handler, under an anyio task group, await will bail with asyncio.CancelledError right away, leading to a connection leak.
Shielding the close() call allows the connection to be properly closed in those cases.
Tentatively removed the asyncio.CancelledError except clause since close() doesn't raise it itself so it shouldn't be possible to get it.
Fixes #12077