🐛(backend) race condition create doc
When 2 docs are created almost at the same time,
the second one will fail because the first one.
We get a unicity error on the path key already
used ("impress_document_path_key").
To fix this issue, we will lock the table the
time to create the document, the next query will
wait for the lock to be released.
This commit is contained in:
committed by
Manuel Raynaud
parent
15dc1e3012
commit
f28081a72a
@@ -15,6 +15,10 @@ and this project adheres to
|
||||
- ♻️Stop setting a default title on doc creation #634
|
||||
|
||||
|
||||
## Fixed
|
||||
|
||||
- 🐛(backend) race condition create doc #633
|
||||
|
||||
## [2.2.0] - 2025-02-10
|
||||
|
||||
## Added
|
||||
|
||||
@@ -12,8 +12,8 @@ from django.contrib.postgres.fields import ArrayField
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.storage import default_storage
|
||||
from django.db import connection, transaction
|
||||
from django.db import models as db
|
||||
from django.db import transaction
|
||||
from django.db.models.expressions import RawSQL
|
||||
from django.db.models.functions import Left, Length
|
||||
from django.http import Http404
|
||||
@@ -573,6 +573,12 @@ class DocumentViewSet(
|
||||
@transaction.atomic
|
||||
def perform_create(self, serializer):
|
||||
"""Set the current user as creator and owner of the newly created object."""
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
f'LOCK TABLE "{models.Document._meta.db_table}" ' # noqa: SLF001
|
||||
"IN SHARE ROW EXCLUSIVE MODE;"
|
||||
)
|
||||
|
||||
obj = models.Document.add_root(
|
||||
creator=self.request.user,
|
||||
**serializer.validated_data,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Tests for Documents API endpoint in impress's core app: create
|
||||
"""
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
@@ -51,6 +52,35 @@ def test_api_documents_create_authenticated_success():
|
||||
assert document.accesses.filter(role="owner", user=user).exists()
|
||||
|
||||
|
||||
def test_api_documents_create_document_race_condition():
|
||||
"""
|
||||
It should be possible to create several documents at the same time
|
||||
without causing any race conditions or data integrity issues.
|
||||
"""
|
||||
|
||||
def create_document(title):
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
return client.post(
|
||||
"/api/v1.0/documents/",
|
||||
{
|
||||
"title": title,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
future1 = executor.submit(create_document, "my document 1")
|
||||
future2 = executor.submit(create_document, "my document 2")
|
||||
|
||||
response1 = future1.result()
|
||||
response2 = future2.result()
|
||||
|
||||
assert response1.status_code == 201
|
||||
assert response2.status_code == 201
|
||||
|
||||
|
||||
def test_api_documents_create_authenticated_title_null():
|
||||
"""It should be possible to create several documents with a null title."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
Reference in New Issue
Block a user