Какой самый простой способ заблокировать объект в Django
Я хочу вызвать ошибку, когда пользователь пытается удалить объект, когда некоторые другие пользователи активны в представлении update_object. Я чувствую, что для этого нужен какой-то мьютекс-подобный механизм блокировки. У вас есть какие-нибудь предложения?
6 ответов
Итак, есть несколько способов сделать то, что вы просите. Но многие из них не будут независимы от реализации: вы можете использовать блокировки или блокировки, но они действительно будут работать только на 100% -ных многопоточных серверах и, вероятно, вообще не будут реализованы в реализации fork / pre-fork.
Это более или менее означает, что реализация блокировки будет зависеть от вас. Две идеи:
.lock
файл в вашей файловой системеlocked
недвижимость в вашем модельном классе
В обоих случаях вы должны вручную установить объект блокировки при обновлении и проверить его при удалении. Попробуйте что-то вроде:
def safe_update(request,model,id):
obj = model.objects.get(id)
if obj.locked:
raise SimultaneousUpdateError #Define this somewhere
else:
obj.lock()
return update_object(request,model,id)
# In models file
class SomeModel(models.Model):
locked = models.BooleanField(default = False)
def lock(self):
self.locked = True
super(models.Model,self).save()
def save(self):
# overriding save because you want to use generic views
# probably not the best idea to rework model code to accomodate view shortcuts
# but I like to give examples.
self.locked = False
# THIS CREATES A DIFFERENT CRITICAL REGION!
super(models.Model,self).save()
Это действительно неуклюжая реализация, которую вам придется очистить. Возможно, вас не устраивает тот факт, что была создана другая критическая область, но я не понимаю, как вы будете работать намного лучше, если будете использовать базу данных в качестве реализации, не усложняя реализацию. (Одним из вариантов будет сделать блокировки полностью отдельными объектами. Затем вы можете обновить их после вызова метода save(). Но я не чувствую необходимости кодировать это.) Если вы действительно хотите использовать блокировку на основе файлов система, которая также решит проблему. Если вы параноик по базе данных, это может быть для вас. Что-то вроде:
class FileLock(object):
def __get__(self,obj):
return os.access(obj.__class__+"_"+obj.id+".lock",os.F_OK)
def __set__(self,obj,value):
if not isinstance(value,bool):
raise AttributeError
if value:
f = open(obj.__class__+"_"+obj.id+".lock")
f.close()
else:
os.remove(obj.__class__+"_"+obj.id+".lock")
def __delete__(self,obj):
raise AttributeError
class SomeModel(models.Model):
locked = FileLock()
def save(self):
super(models.Model,self).save()
self.locked = False
В любом случае, может быть, есть какой-то способ смешать и сопоставить эти предложения на свой вкус?
После добавления select_for_update существует простой способ получить блокировку для объекта, если ваша база данных поддерживает его. postgresql, oracle и mysql, по крайней мере, поддерживают его, согласно документации Django.
Пример кода:
import time
from django.contrib.auth import get_user_model
from django.db import transaction
User = get_user_model()
target_user_pk = User.objects.all()[0].pk
with transaction.atomic():
print "Acquiring lock..."
to_lock = User.objects.filter(pk=target_user_pk).select_for_update()
# Important! Queryset evaluation required to actually acquire the lock.
locked = to_lock[0]
print locked
while True:
print "sleeping {}".format(time.time())
time.sleep(5)
Поскольку ваша область ограничена удалениями, а не обновлениями, одним из вариантов будет переосмысление идеи "удалить" как действия "отменить публикацию". Например, возьмите следующую модель:
class MyManager(models.Manager):
def get_query_set(self):
super(MyManager, self).get_query_set().filter(published=True)
class MyModel(models.Model):
objects = MyManager()
published = models.BooleanField(default=True)
... your fields ...
def my_delete(self):
self.published = False
super(MyModel, self).save()
def save(self):
self.published = True
super(MyModel, self).save()
Таким образом, всякий раз, когда редактирование фиксируется, оно видимо всем пользователям... но другие по-прежнему могут удалять элементы. Одним из преимуществ этой техники является то, что вам не нужно иметь никакой дополнительной логики для блокировки элементов и представления пользователю другого пользовательского интерфейса. Недостатками являются дополнительное пространство, используемое в таблице базы данных, и редкие обстоятельства, когда удаленный элемент "волшебным образом" появляется снова.
(Это, вероятно, только отправная точка. Если вы выбрали этот путь, вы, вероятно, захотите внести изменения в эту идею в зависимости от вашего варианта использования.)
Я бы предложил простую блокировку чтения-записи, поскольку вы не хотите блокировать одновременный доступ пользователей к объекту (только от редактирования).
Общий подход к этому состоит в создании функции, которая поддерживает количество активных читателей. Когда вам нужно написать в этот объект, вы создадите другую функцию, которая будет препятствовать доступу новых читателей (например, страницы обслуживания) и, возможно, перенаправит существующих читателей прочь. Когда читателей больше не осталось, вы завершите запись, а затем разблокируете объект.
Во-первых, вам нужно знать, что есть два механизма блокировки: оптимистическая и пессимистическая блокировка. Чем они отличаются, объясняется здесь. Насколько я знаю, нет пакета, реализующего пессимистическую блокировку для Django. Для оптимистичной блокировки наиболее полнофункциональной библиотекой является django-concurrency.
В принятом ответе между проверками все еще могут быть условия гонки.obj.locked
и установим для него значение true. Это можно исправить, используя отдельную модель.
Существует также вероятность взаимоблокировок (если объект никогда не сохраняется). Это можно исправить с помощью контекстного менеджера.
Поэтому я считаю, что эта версия имеет некоторые преимущества:
class SomeModel(models.Model):
@contextlib.contextmanager
def lock(self):
# raises IntegrityError if already locked
lock = SomeLock.objects.create(obj=self)
try:
yield
finally:
lock.delete()
class SomeLock(models.Model):
obj = models.ForeignKey(SomeModel, on_delete=models.CASCADE, unique=true)