Skip to content
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

Prevent returning connections to pool with a positive transaction depth #1283

Merged

Conversation

as-cii
Copy link
Contributor

@as-cii as-cii commented Dec 5, 2022

Mark transaction as closed only after commit/rollback succeeds.

Previously, open on the transaction would be set to false prior to attempting to commit or rollback the transaction. When the operation failed, for example, due to a serialization failure with a serializable isolation level, this would leave the transaction in an inconsistent state, where it thought it was closed but really it was still open. The connection would then be returned to the connection pool with a transaction depth of 1, causing a savepoint to be erroneously created the next time a transaction was created for the connection.

By waiting to set open to false until the commit/rollback succeeds, a failure to do either will result in us correctly rolling back the transaction when dropping it, ensuring that the connection is returned to the pool with a transaction depth of 0. Note that this is consistent with how sqlx handles transactions.

We attempted to write a test, but had a very difficult time forcing postgres to fail to commit a transaction. We found that it would block our requests instead when creating conflicting updates, and we couldn't find any information about when it blocks vs when transaction commits fail.

Mark transaction as closed *only* after commit/rollback succeeds.

Previously, `open` on the transaction would be set to `false` prior to attempting
to commit or rollback the transaction. When the operation failed, for example, due
to a serialization failure with a serializable isolation level, this would leave
the transaction in an inconsistent state, where it thought it was closed but really
it was still open. The connection would then be returned to the connection pool with
a transaction depth of 1, causing a savepoint to be erroneously created the next time
a transaction was created for the connection.

By waiting to set `open` to `false` until the commit/rollback succeeds, a failure
to do either will result in us correctly rolling back the transaction when dropping
it, ensuring that the connection is returned to the pool with a transaction depth
of 0. Note that this is consistent with how `sqlx` handles transactions.

We attempted to write a test, but had a very difficult time forcing postgres to fail
to commit a transaction. We found that it would block our requests instead when creating
conflicting updates, and we couldn't find any information about when it blocks vs when
transaction commits fail.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Copy link
Member

@billy1624 billy1624 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @as-cii @nathansobo sorry for the delay. Thanks for the investigation!!

I'll fix the clippy warning :)

@billy1624 billy1624 merged commit df2dcda into SeaQL:master Dec 16, 2022
Copy link
Member

@tyt2y3 tyt2y3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be a race condition worthy of a second thought. @nappa85 will there be other potential issues?

@nappa85
Copy link
Contributor

nappa85 commented Dec 21, 2022

Sorry for the late reply...
No, the edit is absolutely correct, my bad for the original bug

@tyt2y3
Copy link
Member

tyt2y3 commented Dec 22, 2022

Awesome! Thank you everyone.

billy1624 pushed a commit that referenced this pull request Dec 23, 2022
…th (#1283)

Mark transaction as closed *only* after commit/rollback succeeds.

Previously, `open` on the transaction would be set to `false` prior to attempting
to commit or rollback the transaction. When the operation failed, for example, due
to a serialization failure with a serializable isolation level, this would leave
the transaction in an inconsistent state, where it thought it was closed but really
it was still open. The connection would then be returned to the connection pool with
a transaction depth of 1, causing a savepoint to be erroneously created the next time
a transaction was created for the connection.

By waiting to set `open` to `false` until the commit/rollback succeeds, a failure
to do either will result in us correctly rolling back the transaction when dropping
it, ensuring that the connection is returned to the pool with a transaction depth
of 0. Note that this is consistent with how `sqlx` handles transactions.

We attempted to write a test, but had a very difficult time forcing postgres to fail
to commit a transaction. We found that it would block our requests instead when creating
conflicting updates, and we couldn't find any information about when it blocks vs when
transaction commits fail.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Co-authored-by: Nathan Sobo <nathan@zed.dev>
@nathansobo
Copy link
Contributor

Appreciate your merge and hard work on this project. It's been very helpful to have the abstraction so we can talk to Postgres in production and SQLite in tests.

@billy1624 billy1624 added this to the 0.11.x milestone Jan 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants