summaryrefslogtreecommitdiff
path: root/tests/get_or_create
diff options
context:
space:
mode:
authorJensen Cochran <sarianrogue@gmail.com>2016-06-24 12:08:42 -0500
committerTim Graham <timograham@gmail.com>2016-07-14 12:10:19 -0400
commitd44afd889275473c97474cca19467d1509e0fcc1 (patch)
tree9264abf2d46397ec806e448b7bc941982d4cf778 /tests/get_or_create
parent76e19da5b0385d4f8afda509e0b60f77f7ffc4c2 (diff)
downloaddjango-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.py53
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')