Opened 11 years ago
Closed 11 years ago
#22967 closed Bug (fixed)
Model._do_update not always consistent with the precise meaning of returned value
| Reported by: | Jozef | Owned by: | Jozef |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | select_on_save concurrency race-condition |
| Cc: | Jozef | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
This is a bit obscure corner-case bug which might seem unimportant, but the solution is easy and actually only makes the code simpler (removes one nested if) and more consistent.
When select_on_save is set, Model._do_update() ignores the result of Model._update() if it learnt that the object exists in a previous DB call. That last thing is crucial, as the object could have already been deleted by concurrent worker just in-between these two ORM calls (exists and _update). In such case, the _update() call will fail, resulting in nothing being actually updated. However, _do_update() method will currently return True regardless of _update() result, which is inconsistent with the common (and expected) behavior, and also in contrast with the method's docs (saying: If the model was updated (in the sense that an update query was done and a matching row was found from the DB) the method will return True.)
As a consequence, object will not be saved into the database, because _save_table() won't call _do_insert(), being misled by _do_update(). Now you may oppose that this does not really matter that much, since it has been caused by concurrent modifications of the same data, which is considered non-deterministic anyway (so noone has the right to complain that the object is not there at the end). However, here's one example that vouches for this bug to be fixed: if you happen to have a trigger in your DB counting the number of times an object was changed (updated/inserted), you'd be missing one (which is no longer justifiable by concurrent save() and delete() operations).
As it sometimes happens, it is IMHO impossible to create a unit test for this bug, as it is too low level thing, exploitable only in a concurrent environment, and with some (bad) luck. I only found the bug when studying the code. So that's why I tried to provide a solid explanation, believing the fix I'm about to make should be obvious and mergable without worries.
Change History (8)
comment:1 by , 11 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
comment:2 by , 11 years ago
comment:3 by , 11 years ago
| Has patch: | set |
|---|
comment:4 by , 11 years ago
I've modified the fix to count with the rare DB behavior when a successful UPDATE returns zero (as discussed in #20988).
More info in Github discussion.
comment:5 by , 11 years ago
| Keywords: | select_on_save concurrency race-condition added |
|---|---|
| Version: | 1.6 → master |
comment:6 by , 11 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
On the PR Anssi said at least docs and comments should be updated.
comment:8 by , 11 years ago
| Resolution: | → fixed |
|---|---|
| Status: | assigned → closed |
My fix is available in this Github branch