-
Notifications
You must be signed in to change notification settings - Fork 21.6k
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
Explicit transactions not rolling back when validation fails on nested attributes #14698
Comments
Ah. Thanks for looking into this @lcreid. I dug a bit deeper and found the documentation I was looking for the other day. It turns out that this is indeed expected and documented behaviour (see the example in the docs I linked). And it also turns out that I have been bitten by this more than once (see #12944, also #4566), so clearly this is super confusing 😅 I'll look into the reason behind this (performance maybe?) and see if anything can be changed here in Rails 5.0, either make Closing for now, but thanks again for looking into this with me! |
I'm actually a bit suspicious of the correctness of the documentation I linked, I think this might have been valid – I'll try to get a more definitive answer |
@chancancode FWIW, I agree... that sounds more like it's documenting existing behaviour, rather than intended. |
So it all started with this docrails commit – 53bbbcc, which the documented (but perhaps buggy) behaviour at that time to what we have now. @evtuhovich, I know this is a long time ago, but do you remember the back story for this change? Have you confirmed that the behaviour you documented is intended? Thanks in advance 😀 |
I remember this bug, i spent one day to find a solution. This was default behaviour for nested transaction, because not all databases support it. I only found this and document. So, if you want consistent nested transaction, always use |
I believe the intention was that if you don't use |
I also was sure, that rollback should rollback outremost block. But this is wrong. When you rollback inner transaction nothing happens, outer transaction don't get rollback and continue its execution. And this is why a spent a day to understand that problem in my project. |
Right, if I understand you correctly, you are saying the documentation (at the time) didn't agree with the actual behaviour of the code (at the time), correct? But in that case, it could be either the code or the documentation that is wrong. In this case I suspect it's the former – which is why I asked if you have confirmed this behaviour is desirable (for example, filed a bug on this and was told by a core team member that the docs are wrong). I'm trying to find those back stories to support this doc change, because it appears otherwise that the code should have been fixed instead. |
I don't think, this is desireble behaviour, i've just documented the state of code at that time, and i hope, this helps other people. And i sure the code should be fixed, of course. |
Thanks for the update! 😄 For the future, please file a bug report for these kind of things – the documentation should reflect how the code should behave, so we shouldn't change the documentation when the code misbehave. This will cause other people to make incorrect inference about the code and result in things like issues incorrectly being closed, or worse – more code being changed to align itself with the buggy behaviour. |
cc @fxn |
Previously, `ActiveRecord::Rollback` are silently swallowed by all transaction blocks. The intention behind this is that these special "exceptions" are only used to signal a rollback request to Active Record, thus they should be discarded after the rollback request is handled. However, this is incorrect for nested transactions. By default, nested transaction blocks are effectively a no-op – they simply become part of the parent transaction†. To achieve this, the correct behaviour is to "bubble up" the `ActiveRecord::Rollback` "exception", which allow the first parent transaction block with a real database transaction to correctly handle the rollback. The documentation has been changed to reflect this. This effectively rolls back 53bbbcc. Fixes rails#3455, rails#14698. † The reason that nested transactions defaults to no-op is that savepoints are not supported in all databases. *Godfrey Chan*
This issue has been automatically marked as stale because it has not been commented on for at least The resources of the Rails team are limited, and so we are asking for your help. If you can still reproduce this error on the Thank you for all your contributions. |
Previously, `ActiveRecord::Rollback` are silently swallowed by all transaction blocks. The intention behind this is that these special "exceptions" are only used to signal a rollback request to Active Record, thus they should be discarded after the rollback request is handled. However, this is incorrect for nested transactions. By default, nested transaction blocks are effectively a no-op – they simply become part of the parent transaction†. To achieve this, the correct behaviour is to "bubble up" the `ActiveRecord::Rollback` "exception", which allow the first parent transaction block with a real database transaction to correctly handle the rollback. The documentation has been changed to reflect this. This effectively rolls back 53bbbcc. Fixes #3455, #14698. † The reason that nested transactions defaults to no-op is that savepoints are not supported in all databases. *Godfrey Chan* Conflicts: activerecord/CHANGELOG.md activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@rafaelfranca @fxn @chancancode I think this issue can be closed as it was addressed in aa59230 (not sure why it didn't autoclose) |
@robzolkos The commit you mentioned was not merged. See #15017. |
Given this has been around for nearly a decade, perhaps it's safe to close |
The issue is still here. We just didn't have time to fix yet. If someone wants to fix this please go ahead. |
A blast from the past. I'll take a look at this, perhaps this weekend. |
Just to be clear what the desired behaviour is: When an object has nested attributes, a failure to save the object or any of the objects affected by the nested attributes should leave the database in the state it was before the attempt to save the object. Said another way, the logical transaction should start on entering the save, and should be rolled back to that point on any failure to save. |
The above should actually be true for any autosaved associations, not just those that are implied by |
@irvingreid and I discussed this issue for a couple of hours. We see three approaches, not exclusive:
We're going to continue to investigate this issue and try to come up with some more concrete solutions. |
In certain situations, when a validation fails on a model referenced in nested attributes, when the update attributes is executed inside an explicit transaction, the transaction is not rolled back, and the database is left in a state that's different than it was before the transaction.
Example:
In the controller:
Note that
user.update!
does not cause the issue.The models:
A complete file that shows a case that causes, and some cases that are similar but don't cause the issue:
If the above is in a file called
repro.rb
, then:should produce output ending in:
The issues has been observed in 4.1 master and 4.0 master. I haven't yet checked how far back it might go.
The issue was shown to my by @chancancode . I'm documenting the issue here without much further research yet because I didn't want it to get dropped.
The text was updated successfully, but these errors were encountered: