diff options
author | Jensen Cochran <sarianrogue@gmail.com> | 2016-06-24 12:08:42 -0500 |
---|---|---|
committer | Tim Graham <timograham@gmail.com> | 2016-07-14 12:10:19 -0400 |
commit | d44afd889275473c97474cca19467d1509e0fcc1 (patch) | |
tree | 9264abf2d46397ec806e448b7bc941982d4cf778 /tests/get_or_create | |
parent | 76e19da5b0385d4f8afda509e0b60f77f7ffc4c2 (diff) | |
download | django-d44afd889275473c97474cca19467d1509e0fcc1.tar.gz |
Fixed #26804 -- Fixed a race condition in QuerySet.update_or_create().
Diffstat (limited to 'tests/get_or_create')
-rw-r--r-- | tests/get_or_create/tests.py | 53 |
1 files changed, 51 insertions, 2 deletions
diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index 0cbb9a1fff..0a774eff77 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -1,10 +1,14 @@ from __future__ import unicode_literals +import time import traceback -from datetime import date +from datetime import date, datetime, timedelta +from threading import Thread from django.db import DatabaseError, IntegrityError -from django.test import TestCase, TransactionTestCase, ignore_warnings +from django.test import ( + TestCase, TransactionTestCase, ignore_warnings, skipUnlessDBFeature, +) from django.utils.encoding import DjangoUnicodeDecodeError from .models import ( @@ -422,3 +426,48 @@ class UpdateOrCreateTests(TestCase): ) self.assertIs(created, False) self.assertEqual(obj.last_name, 'NotHarrison') + + +class UpdateOrCreateTransactionTests(TransactionTestCase): + available_apps = ['get_or_create'] + + @skipUnlessDBFeature('has_select_for_update') + @skipUnlessDBFeature('supports_transactions') + def test_updates_in_transaction(self): + """ + Objects are selected and updated in a transaction to avoid race + conditions. This test forces update_or_create() to hold the lock + in another thread for a relatively long time so that it can update + while it holds the lock. The updated field isn't a field in 'defaults', + so update_or_create() shouldn't have an effect on it. + """ + def birthday_sleep(): + time.sleep(0.3) + return date(1940, 10, 10) + + def update_birthday_slowly(): + Person.objects.update_or_create( + first_name='John', defaults={'birthday': birthday_sleep} + ) + + Person.objects.create(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)) + + # update_or_create in a separate thread + t = Thread(target=update_birthday_slowly) + before_start = datetime.now() + t.start() + + # Wait for lock to begin + time.sleep(0.05) + + # Update during lock + Person.objects.filter(first_name='John').update(last_name='NotLennon') + after_update = datetime.now() + + # Wait for thread to finish + t.join() + + # The update remains and it blocked. + updated_person = Person.objects.get(first_name='John') + self.assertGreater(after_update - before_start, timedelta(seconds=0.3)) + self.assertEqual(updated_person.last_name, 'NotLennon') |