Locking the element on take_snapshot to avoid racing condition issues

remotes/origin/enhancement/email-actions
Alejandro Alonso 2015-02-04 10:15:06 +01:00 committed by David Barragán Merino
parent 985838cd1a
commit 50fd9b6161
2 changed files with 44 additions and 40 deletions

View File

@ -30,6 +30,7 @@ django-ipware==0.1.0
premailer==2.8.1 premailer==2.8.1
django-transactional-cleanup==0.1.13 django-transactional-cleanup==0.1.13
lxml==3.4.1 lxml==3.4.1
git+https://github.com/Xof/django-pglocks.git@dbb8d7375066859f897604132bd437832d2014ea
# Comment it if you are using python >= 3.4 # Comment it if you are using python >= 3.4
enum34==1.0 enum34==1.0

View File

@ -37,6 +37,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.paginator import Paginator, InvalidPage from django.core.paginator import Paginator, InvalidPage
from django.apps import apps from django.apps import apps
from django.db import transaction as tx from django.db import transaction as tx
from django_pglocks import advisory_lock
from taiga.mdrender.service import render as mdrender from taiga.mdrender.service import render as mdrender
from taiga.base.utils.db import get_typename_for_model_class from taiga.base.utils.db import get_typename_for_model_class
@ -269,6 +270,7 @@ def get_modified_fields(obj:object, last_modifications):
return modified_fields return modified_fields
@tx.atomic @tx.atomic
def take_snapshot(obj:object, *, comment:str="", user=None, delete:bool=False): def take_snapshot(obj:object, *, comment:str="", user=None, delete:bool=False):
""" """
@ -280,56 +282,57 @@ def take_snapshot(obj:object, *, comment:str="", user=None, delete:bool=False):
""" """
key = make_key_from_model_object(obj) key = make_key_from_model_object(obj)
typename = get_typename_for_model_class(obj.__class__) with advisory_lock(key) as acquired_key_lock:
typename = get_typename_for_model_class(obj.__class__)
new_fobj = freeze_model_instance(obj) new_fobj = freeze_model_instance(obj)
old_fobj, need_real_snapshot = get_last_snapshot_for_key(key) old_fobj, need_real_snapshot = get_last_snapshot_for_key(key)
entry_model = apps.get_model("history", "HistoryEntry") entry_model = apps.get_model("history", "HistoryEntry")
user_id = None if user is None else user.id user_id = None if user is None else user.id
user_name = "" if user is None else user.get_full_name() user_name = "" if user is None else user.get_full_name()
# Determine history type # Determine history type
if delete: if delete:
entry_type = HistoryType.delete entry_type = HistoryType.delete
elif new_fobj and not old_fobj: elif new_fobj and not old_fobj:
entry_type = HistoryType.create entry_type = HistoryType.create
elif new_fobj and old_fobj: elif new_fobj and old_fobj:
entry_type = HistoryType.change entry_type = HistoryType.change
else: else:
raise RuntimeError("Unexpected condition") raise RuntimeError("Unexpected condition")
fdiff = make_diff(old_fobj, new_fobj) fdiff = make_diff(old_fobj, new_fobj)
# If diff and comment are empty, do # If diff and comment are empty, do
# not create empty history entry # not create empty history entry
if (not fdiff.diff and not comment if (not fdiff.diff and not comment
and old_fobj is not None and old_fobj is not None
and entry_type != HistoryType.delete): and entry_type != HistoryType.delete):
return None return None
fvals = make_diff_values(typename, fdiff) fvals = make_diff_values(typename, fdiff)
if len(comment) > 0: if len(comment) > 0:
is_hidden = False is_hidden = False
else: else:
is_hidden = is_hidden_snapshot(fdiff) is_hidden = is_hidden_snapshot(fdiff)
kwargs = { kwargs = {
"user": {"pk": user_id, "name": user_name}, "user": {"pk": user_id, "name": user_name},
"key": key, "key": key,
"type": entry_type, "type": entry_type,
"snapshot": fdiff.snapshot if need_real_snapshot else None, "snapshot": fdiff.snapshot if need_real_snapshot else None,
"diff": fdiff.diff, "diff": fdiff.diff,
"values": fvals, "values": fvals,
"comment": comment, "comment": comment,
"comment_html": mdrender(obj.project, comment), "comment_html": mdrender(obj.project, comment),
"is_hidden": is_hidden, "is_hidden": is_hidden,
"is_snapshot": need_real_snapshot, "is_snapshot": need_real_snapshot,
} }
return entry_model.objects.create(**kwargs) return entry_model.objects.create(**kwargs)
# High level query api # High level query api