-
Notifications
You must be signed in to change notification settings - Fork 300
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
System.Data.SqlClient.SqlConnection does not roll back open transaction on dispose #84
Comments
I've been studying the problem, and even though I can intercept all instances of SqlConnection.Dispose() I'm not sure of the workaround. The connection object knows internally whether or not it can run commands but does not expose it, and even if it did, it's really hard to convince it to not go back into the connection pool. |
@jhudsoncedaron What is the OS on which the Client code is running? |
Windows 10. Incidentally this can be worked around if the ConnectionState property is made to work in all cases. (The documentation says states Executing and Fetching aren't implemented "This value is reserved for future versions of the product.".), in which case I can implement my own connection pool. |
What's the status on this? |
I was able to repro the timeout exception with this code in Windows 10.
I tested with following SQL queries:
Is there any particular reason to leave |
The why behind this code is hard to understand due to reduction. The actual problem is the second connection got sent back to the connection pool with an open transaction. In the real code, there was a thrown exception after the Exec("UPDATE ..."); line, and another thread picked up the connection with an open transaction. The reason there's no rollback in a finally block is because if a data reader were open, trying to execute the rollback would itself throw. |
This issue happens because unmanaged transaction is not rolled back before the connection moves back the pool. That means this error only happens when user initiates unmanaged transaction by executing Recommended (managed) way of initiating transaction is using
In this case, incomplete transaction is rolled back automatically when But if user initiates transaction by executing SQL command
In this case, the transaction is never rolled back when the connection moves back to the pool because we do not have control for transaction in runtime since the transaction runs without One possible way to resolve this issue is putting following line in
This line resolves the issue because rollback occurs when
|
So how do you write a function that does not care if it is in a transaction or not to create a command? This is the reason we don't use the managed transaction API. |
@jhudsoncedaron Could you provide a little more information about? :
In our understanding, if there is a function that creates a command without knowing if it is in a transaction or not, then the caller of the function should manage the transaction flow. |
We discovered that Connection.CreateCommand() returns a command that doesn't work if a transaction is open. It's easy enough to fix if you know you need to do this, but the fix code crashes if there is no transaction open. We have tons of helper functions that read stuff from the database that literally do not care if the caller opened a transaction or not. The connection pool is kind of broken anyway due to inheriting the isolation level of the previous transaction (which would be immediately fixed on opening a new one, but many read-only paths don't) and I'd actually rather you fix ConnectionState to let me know if the connection can accept commands or not and I'll implement my own pool. |
I have discovered that an actual fix is easy and doesn't depend on adding any more round trips. Instead of "EXEC sp_resetconnection;" on return to pool do "BEGIN TRANSACTION;ROLLBACK;EXEC sp_resetconnection;". Because BEGIN TRANSACTION/COMMIT stack and ROLLBACK doesn't, any pending transaction is rolled back. They tell me that sp_resetconnection is ran from Open() rather than Close() but from the number of bad connections I've gotten out of the pool in a row this is not the case. Also, the client is going to have to do set the transaction isolation level immediately on receiving the connection from the pool anyway. This is well known to the internet if you look for it but not documented in a way that is discoverable if you don't already know. (The connection object inherits its previous isolation level from the pool, which is likely random in any large application.) |
As recently announced in the .NET Blog, focus on new SqlClient features an improvements is moving to the new Microsoft.Data.SqlClient package. For this reason, we are moving this issue to the new repo at https://github.com/dotnet/SqlClient. We will still use https://github.com/dotnet/corefx to track issues on other providers like System.Data.Odbc and System.Data.OleDB, and general ADO.NET and .NET data access issues. |
If I'm following, you're saying, in essence, that using the managed API for transactions, which would solve this problem, doesn't work because of how Would the proposal in #28 resolve/bypass this issue? I think it would fix the |
@bgribaudo : Yeah that would; but I call via |
Thanks, @jhudsoncedaron! Maybe the fact that there are two of us who have an interest in #28 will get a little more traction under it. :-) |
@bgribaudo : Don't hope too much. I've a pull request up for fixing a data loss bug that's probably going to be declined just because hitting the race condition is rare. |
Agreed! The challenge is puling this off when Let's say I have a caller which manages transactions (as you describe). At certain points, this caller invokes other, low-level functions, passing them a reference to the db connection. These functions may choose to execute commands on that connection, creating those commands using The catch is that it doesn't work at the technical level with managed transactions. :-( If the caller uses managed transactions, command created by those other functions using Something like the proposal in #28 would resolve this. |
As explained in #84 (comment) by @geleems this is expected behavior if SqlClient is not managing transactions in pooled connection scenario. Closing this issue as not a bug in driver. |
SQL Server version is 2014 if it matters. Trivial reproduction causes timeout unless ;pooling=false is appended to the connection string.
We got here through a much larger chunk of code handling thrown exceptions. We have no idea what the connection state is and it might have an executing data reader on it so we have no good way of passing the rollback command ourselves.
The text was updated successfully, but these errors were encountered: