-
-
Notifications
You must be signed in to change notification settings - Fork 32.8k
Fixed get_or_create() to re-raise IntegrityError when object still does not exist. Refs #36489 #19676
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
base: main
Are you sure you want to change the base?
Conversation
…omatic zoom issue in mobile Safari.
20ed5e1
to
f522c80
Compare
a3c5faf
to
615c54c
Compare
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.
Thank you for the PR
.gitignore
Outdated
@@ -17,3 +17,4 @@ tests/.coverage* | |||
build/ | |||
tests/report/ | |||
tests/screenshots/ | |||
test_postgres.py |
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.
test_postgres.py |
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.
removed.
django/db/models/query.py
Outdated
@@ -979,17 +979,17 @@ def get_or_create(self, defaults=None, **kwargs): | |||
return self.get(**kwargs), False | |||
except self.model.DoesNotExist: | |||
params = self._extract_model_params(defaults, **kwargs) | |||
# Try to create an object using passed params. |
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.
I think we can keep this comment
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.
comment has been put back.
tests/get_or_create_race/models.py
Outdated
@@ -0,0 +1,12 @@ | |||
from django.db import models |
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.
We don't need to create a new test folder for this fix and can use get_or_create
for example
We should try to reuse existing models where we can
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.
tests moved, removed my postgres test.
tests/get_or_create_race/tests.py
Outdated
|
||
|
||
@unittest.skipUnless( | ||
connection.vendor == "postgresql", "This test only runs on PostgreSQL" |
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.
Not sure why this is only being run only on postgres? I don't see anything in the ticket discussion to suggest this is a postgres specific issue
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.
I use PostgreSQL because this bug revolves around a race condition during concurrent get_or_create() calls involving a OneToOneField. So to reliably reproduce that the database needs to support concurrency.
SQLite doesn't support concurrent writes. It locks the entire database file during a write.
MySQL was failing with errors like Lock wait timeout exceeded.
PostgreSQL was the third one i tried and it allowed me to reproduce the race condition.
raise | ||
# Re-raise if the object still doesn't exist. | ||
# this wasn't a race condition but an integrity error. | ||
raise e |
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.
I don't think this is the correct solution. Note the test in the original ticket is still failing for me and based off the ticket discussion, I expect the solution to be more involved
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.
solution updated, and now passes test (modified to use models in get_or_create) from original ticket description.
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.
Have you given any thoughts on how systematically clearing one-to-one relationship cache will break select_related("parent").get_or_create(parent=parent)
, how this relates to update_or_create
and if something similar should be done with defaults
and create_defaults
as described in the ticket?
I appreciate that the test are passing but it seems like there are still a few open questions about which approach should be taken.
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.
Regarding that pruned comment, I was having git issues. Fixed that.
So instead of systematically clearing the one-to-one relationship cache, it should be done conditionally. Only if the cache looks stale. I can make that change real quick. I'm still getting used to how everything works.
I appreciate the guidance. I'll get that changed pushed shortly.
Those other two issues, update_or_create() and defaults / create_defaults, should those be dealt with here? I can take a closer look at both.
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.
It looks like update_or_create uses get_or_create internally:
obj, created = self.select_for_update().get_or_create(
create_defaults, **kwargs
)
The cache fix will apply here also.
9ad93a5
to
59b52ab
Compare
django/db/models/query.py
Outdated
# The get() needs to be targeted at the write database in order | ||
# to avoid potential transaction consistency problems. |
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.
Why did you prune this comment?
59b52ab
to
7c81ccc
Compare
7c81ccc
to
7279535
Compare
Trac ticket number
Refs #36489
Branch description
Previously,
QuerySet.get_or_create()
would suprress allIntegrityError
exceptions that occurred during object creation, under the assumption that they were due to race conditions. However, this also masked real issues like missing required fields, unique constraint violations, or invalid foreign keys.This change modifies the behavior to re-raise the
IntegrityError
if a subsequent.get()
still fails after.create()
-- ensuring only true race conditions are handled silently.Checklist
main
branch.Notes
get_or_create_race
) usingThreadPoolExecutor
to simulate concurrent access.