Opened 10 years ago
Closed 32 hours ago
#26630 closed Bug (duplicate)
Defered constraint checks flush after `post_delete` signal
| Reported by: | Alex Madjar | Owned by: | |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Annabelle Wiegart | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description
Repro steps:
- Use a database that supports
can_defer_constraint_checks(tested on postgres) - Have a model (A) with a
null=TrueForeignKey or OneToOneField (F) to another model (B) - Connect a function to listen to
post_deleteon (B) that pulls objects of type (A) from the db (using a = A.objects.get... etc ) - Try to access a.F for an object a that used to be associated with the deleted instance of B that was just deleted
Expectation: a.F_id is None
Actually: a.F_id is the id of the deleted instance and a.F raises an ObjectDoesNotExist exception
Note: doesn't repro on fields where null=False or on sqlite. My diagnosis is that A's field isn't being properly cleared (or A objects deleted in the case of on delete CASCADE) because the constraint checks aren't being flushed until _after_ the post_delete signal fires
Attachments (1)
Change History (13)
comment:1 by , 10 years ago
comment:2 by , 10 years ago
If you could provide a test case for Django's test suite, that would be helpful.
comment:3 by , 10 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
Seems legitimate. I attached a sample project I used to reproduce. The first test method shows the failure.
comment:5 by , 6 years ago
| Has patch: | set |
|---|
comment:6 by , 6 years ago
| Patch needs improvement: | set |
|---|
comment:7 by , 3 years ago
| Owner: | removed |
|---|
comment:8 by , 6 days ago
I'm unable to reproduce the described behavior with the attached sample project. The related Choice instance seems to be deleted immediately. The post_delete callback function produces the error t26630.models.Choice.DoesNotExist: Choice matching query does not exist. Is there still a bug that needs to be fixed?
comment:9 by , 6 days ago
Thanks, Annabelle, I'm seeing the same. However, I think this is a symptom of the test project needing to be updated. If you connect a post_delete signal on Choice as well, you can undo the effect of the optimization from #30856 and trigger the failing code path again.
I'm not sure exactly how to fix this, but we should probably examine the solution space a bit more before closing this one.
If you're curious how I dug around for this, I noticed the original report suggesting that one of the objects was not being cascade deleted, but as you saw, it was. So I set a breakpoint and noticed we were hitting a "fast delete" path, which led me to #30856 driving more traffic into that path.
comment:10 by , 42 hours ago
Thank you for your clarifications, Jacob. i looked at the DryORM example and a bit into the Collector class and the "fast delete" path.
I'm a bit unsure about the expected behavior though. The ticket description says "Expectation: a.F_id is None". Since the "fast delete" path leads to the deletion of a not being deferred, I suppose the expected behavior would be exactly this, even if a post_delete signal is attached to Choice as well?
comment:11 by , 35 hours ago
| Cc: | added |
|---|
comment:12 by , 32 hours ago
| Resolution: | → duplicate |
|---|---|
| Status: | assigned → closed |
I'm also a little unsure. I think the gist of the report is that doing database queries during a post_delete listener is problematic, because you can query Choice rows by question_id and get misleading results (as those rows are in the middle of being cascade deleted).
I'm reading the original report to suggest reordering the post_delete signal to run after constraints are flushed (and all cascade deletes have happened). I doubt that would be backward compatible.
In #30835 something similar was suggested, and Simon explained you can use on_commit() to defer execution of the callback (to a point when all cascade deletions have been made). I think I'd suggest rewriting the signal handler that way to avoid this problem with unstable Choice results.
I'm inclined to close this as duplicate of #30835. As mentioned there, if there's a proposal for a docs tweak in post_delete we could consider it. (In that case, do reopen #30835!)
Thanks for helping out with triage.
Only tried on CASCADE deletes, I think it blames to this code - https://github.com/django/django/blob/master/django/db/models/deletion.py#L15