And, of course, immediately after posting this, I find
http://code.djangoproject.com/ticket/13906, which seems to cover much of the
same area.

Ian


On Fri, Sep 2, 2011 at 11:04 AM, Ian Clelland <clell...@gmail.com> wrote:

> I'm seeing errors which I believe are due to a race condition in
> django.db.models.query.get_or_create, on a fairly high traffic site. Our
> production servers are running Django 1.2.5, but I don't see any changes in
> the code in trunk that would affect this. (I'm totally willing to construct
> a test case against trunk, but I'm posting this here in case it's already a
> recognized bug, or an error on my part)
>
> If two requests make the same call to get_or_create(), at roughly the same
> time, with a database server in REPEATABLE_READ isolation level, then I
> believe that it's possible for the following sequence of events to occur:
>
> 1. Process 1 enters into a transaction as part of the default view
> middleware.
> 2. Process 1 calls QuerySet.get(**lookup), no result is returned.
> (DoesNotExist is raised)
> ----
> 3. Process 2 enters into a transaction as part of the default view
> middleware.
> 4. Process 2 calls QuerySet.get(**lookup), no result is
> returned. (DoesNotExist is raised)
> 5. Process 2 calls transaction.savepoint
> 6. Process 2 saves a new object
> 7. Process 2 commits and returns the object
> ----
> 8. Process 1 calls transaction.savepoint
> 9. Process 1 tries to save a new object; this locks before #7, above, and
> fails after #7, with an IntegrityError
> 10. Process 1 rolls back to the savepoint, *but does not leave the outer
> transaction*
> 11. Process 1 calls QuerySet.get(**lookup), again, *but because we're still
> in the outer transaction, this returns nothing*
> 12. Process 1 Raises an integrity error, rather than getting the new
> object.
>
> Process 1 fails, because it performed the initial read inside of a
> transaction, but before the save point. In fact, inside of the same
> transaction, I believe it is impossible for the initial self.get() and the
> self.get() in the exception handler to return different results.
>
> Some SQL-shell testing shows that it's possible for this to work, as long
> as we set the savepoint before the initial read. That way, when we catch an
> IntegrityError and roll back to the savepoint, the lock is released, and
> Process 1 can actually see the object committed by Process 2.
>
> I expect to open up a ticket for this, unless someone can tell me "you're
> doing it wrong", or point me to another ticket (I've scanned the trac
> database, but didn't see anything identical. 15507 touches this, but won't
> actually do anything to solve it.)
>
> --
> Regards,
> Ian Clelland
> <clell...@gmail.com>
>



-- 
Regards,
Ian Clelland
<clell...@gmail.com>

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.

Reply via email to