-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
Run transactional callbacks on instances most likely to match DB state #45280
Run transactional callbacks on instances most likely to match DB state #45280
Conversation
activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. Let's add the flag to allow people to opt-in to it, but still makes this a framework default
activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
Show resolved
Hide resolved
@rafaelfranca I added the configuration flag, updated the framework defaults, adding documentation for the new flag, and added a changelog entry. I considered whether a deprecation warning was necessary but based on the example of other such framework defaults I decided not to. We aren't really deprecating the old behavior if there's a configuration flag that lets an app developer opt in or out as they please. Let me know what you think. CI is red but as far as I can tell it appears just to be flakiness. |
activerecord/CHANGELOG.md
Outdated
- If the record is created within the transaction, then | ||
`after_create_commit` callbacks will be fired on the last instance to | ||
save, even if that instance performed an update. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an exception? It is running the callback on the last instance to save.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exceptional behavior is that we're running after_create_commit
, not after_update_commit
on that instance, even though from its perspective it just issued an update
If you don't feel like that detail needs to be called out, then I'm happy to remove or reword.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh, got it, that is not clear in the message. Can we update the message to show that difference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I updated it. Is that more clear?
...ib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_1.rb.tt
Outdated
Show resolved
Hide resolved
e296c89
to
a7ae66f
Compare
a7ae66f
to
936a862
Compare
Follow up rails#45280 There are couple of pull requests that remove these trailing spaces. Changing the trailing whitespaces is not related to them.
@cbothner @rafaelfranca this change silently breaks Depending on what else is happening in the transaction they'll either work as expected or will be completely ignored since the last object won't have the expected changes. A nightmare to debug and the only way to fix is to disable this behavior. |
@kshnurov Anything that relies on instance state is unreliable when two instances are being used to write to the same row in the same transaction. No matter which instance we choose for callbacks, we'll miss something. Between the two options, I believe this is the better choice. Missing dirty state from the first instance to save does break callbacks that are conditional on Really I think we should all try not to get into situations where we have multiple instances representing the same row. Passing around one instance will ensure no state is lost, and as a bonus it will save unnecessary |
@cbothner well, both are bad, but the stale state is an expected behaviour described in the docs. This PR didn't update them, which makes things even worse - people will expect it to work as before. This one is a breaking change in Rails 7.1 that leads to silent hard-to-find bugs. |
@kshnurov Sorry to dismiss your comments here, but people make mistakes and responding to a PR that was merged over 18 months ago probably won't get the feedback you're looking for. I suggest if you can create a reproduction to open a new issue. 🙏 |
When multiple instances of the same ActiveRecord model are used to persist changes within a transaction, Rails always runs
after_commit
callbacks on the first to save. This results in very unintuitive behavior:This PR implements a change to instead fire transactional callbacks on instances with internal state most likely to match the committed database state.
after_create_commit
callbacks will be run on the second instance. This is instead of theafter_update_commit
callbacks that would naively be run based on that instance’s state.after_destroy_commit
callbacks will be fired on the last destroyed instance, even if a stale instance subsequently performed an update (which will have affected 0 rows).Please see the added test cases for a more detailed statement of behavior.