New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Connections not reset properly if connection is opened/closed within a transactionscope. #4451
Comments
As you wrote, it isn't possible to execute DISCARD ALL within a transaction (PG returns an error). To give more context, opening multiple connections within the same TransactionScope is typically the trigger to start a distributed transaction (currently unsupported in .NET Core/5+, see dotnet/runtime#715). However, there's a special optimization where connections are done to the same database, and not at the same time - as in your code. In that case, since the connection being closed is still enlisted in an uncommitted TransactionScope, it is returned to a special place in the pool, and if another connection is opened to the same database within that TransactionScope, that connection is handed out again. This prevents escalation to a distributed transaction, which is a much more costly mechanism. Technically, we could maybe issue the various reset commands that DISCARD ALL performs (see docs). However, the current transaction may be relying on e.g. temporary tables, and resetting those could interfere with the proper functioning of the transaction. So I'm simply not sure what else we could do here - but am open to suggestions. |
Could the .Reset() be called when returning the connection back after the Transaction has been committed in the VolatileResourceManager.cs:Dispose()? My initial test seemed to pass for the simple non-distributed test case. Seems like a lot of work to do in Dispose() function though.
|
I'm no export :) so the the only input I can give is that the code should probably catch, log and eat any exceptions that could arise due to the Reset() command being issued. Dispose() is not supposed to throw based on my understanding, can potentially hide the real exception. However if this can't be solved then perhaps the docs should be updated to reflect the issue? (https://www.npgsql.org/doc/basic-usage.html#systemtransactions-and-distributed-transactions).
|
Steps to reproduce
Before running the test You will have to create the db test_db and role test_db_role manually.
The issue
I'm not sure if it really is a bug or not but here it goes :).
It looks like the connection from the connection pool is not reset correctly if NpgsqlConnection.Open/Close is called within a TransactionScope. In the test code above the first execution of the query works fine. The seconds time we (probably) get the same connection from the connection pool back but it is not being reset properly (no DISCARD ALL). On the second call the SET ROLE command is still in effect (current_user = 'test_db_role') which I assume can have serious consequences in some cases (for example sql cmd executing as a role with higher privileges than intended?). This is of course not limited to just the SET ROLE command. Temp tables etc are also not discarded.
I kind of understand why since its not allowed to issue a DISCARD ALL within a transaction. However from what I've seen in the wild its quite common to create a transactionscope and then open/close connections inside it to do DB operations.
Further technical details
Npgsql version: 6.0.4
PostgreSQL version: 14
Operating system: Windows Server 2012 R2
Runtime: NET 6.
The text was updated successfully, but these errors were encountered: