mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
Fixed and improved locking implementations
This commit is contained in:
20
src/pretix/base/migrations/0018_eventlock_token.py
Normal file
20
src/pretix/base/migrations/0018_eventlock_token.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0017_order_guest_locale'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='eventlock',
|
||||||
|
name='token',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -366,7 +366,6 @@ class Event(Versionable):
|
|||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
verbose_name=_("Plugins"),
|
verbose_name=_("Plugins"),
|
||||||
)
|
)
|
||||||
locked_here = False
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Event")
|
verbose_name = _("Event")
|
||||||
@@ -1822,6 +1821,10 @@ class OrganizerSetting(Versionable):
|
|||||||
class EventLock(models.Model):
|
class EventLock(models.Model):
|
||||||
event = models.CharField(max_length=36, primary_key=True)
|
event = models.CharField(max_length=36, primary_key=True)
|
||||||
date = models.DateTimeField(auto_now=True)
|
date = models.DateTimeField(auto_now=True)
|
||||||
|
token = models.UUIDField(default=uuid.uuid4)
|
||||||
|
|
||||||
class LockTimeoutException(Exception):
|
class LockTimeoutException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class LockReleaseException(Exception):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@@ -9,6 +10,7 @@ from django.utils.timezone import now
|
|||||||
from pretix.base.models import EventLock
|
from pretix.base.models import EventLock
|
||||||
|
|
||||||
logger = logging.getLogger('pretix.base.locking')
|
logger = logging.getLogger('pretix.base.locking')
|
||||||
|
LOCK_TIMEOUT = 120
|
||||||
|
|
||||||
|
|
||||||
class LockManager:
|
class LockManager:
|
||||||
@@ -32,22 +34,25 @@ def lock_event(event):
|
|||||||
:raises EventLock.LockTimeoutException: if the event is locked every time we try
|
:raises EventLock.LockTimeoutException: if the event is locked every time we try
|
||||||
to obtain the lock
|
to obtain the lock
|
||||||
"""
|
"""
|
||||||
if event.locked_here:
|
if hasattr(event, '_lock') and event._lock:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if settings.HAS_REDIS:
|
if settings.HAS_REDIS:
|
||||||
return lock_event_redis(event)
|
return lock_event_redis(event)
|
||||||
else:
|
else:
|
||||||
return lock_event_db(event)
|
return lock_event_db(event)
|
||||||
|
|
||||||
|
|
||||||
def release_event(event, force=False):
|
def release_event(event):
|
||||||
"""
|
"""
|
||||||
Release a lock placed by :py:meth:`lock()`. If the parameter force is not set to ``True``,
|
Release a lock placed by :py:meth:`lock()`. If the parameter force is not set to ``True``,
|
||||||
the lock will only be released if it was issued in _this_ python
|
the lock will only be released if it was issued in _this_ python
|
||||||
representation of the database object.
|
representation of the database object.
|
||||||
|
|
||||||
|
:raises EventLock.LockReleaseException: if we do not own the lock
|
||||||
"""
|
"""
|
||||||
if not event.locked_here and not force:
|
if not hasattr(event, '_lock') or not event._lock:
|
||||||
return False
|
raise EventLock.LockReleaseException('')
|
||||||
if settings.HAS_REDIS:
|
if settings.HAS_REDIS:
|
||||||
return release_event_redis(event)
|
return release_event_redis(event)
|
||||||
else:
|
else:
|
||||||
@@ -61,31 +66,39 @@ def lock_event_db(event):
|
|||||||
dt = now()
|
dt = now()
|
||||||
l, created = EventLock.objects.get_or_create(event=event.identity)
|
l, created = EventLock.objects.get_or_create(event=event.identity)
|
||||||
if created:
|
if created:
|
||||||
event.locked_here = dt
|
event._lock = l
|
||||||
return True
|
return True
|
||||||
elif l.date < now() - timedelta(seconds=120):
|
elif l.date < now() - timedelta(seconds=LOCK_TIMEOUT):
|
||||||
updated = EventLock.objects.filter(event=event.identity, date=l.date).update(date=dt)
|
newtoken = uuid.uuid4()
|
||||||
|
updated = EventLock.objects.filter(event=event.identity, token=l.token).update(date=dt, token=newtoken)
|
||||||
if updated:
|
if updated:
|
||||||
event.locked_here = dt
|
l.token = newtoken
|
||||||
|
event._lock = l
|
||||||
return True
|
return True
|
||||||
time.sleep(2 ** i / 100)
|
time.sleep(2 ** i / 100)
|
||||||
raise EventLock.LockTimeoutException()
|
raise EventLock.LockTimeoutException()
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
def release_event_db(event):
|
def release_event_db(event):
|
||||||
deleted = EventLock.objects.filter(event=event.identity).delete()
|
if not hasattr(event, '_lock') or not event._lock:
|
||||||
event.locked_here = None
|
raise EventLock.LockReleaseException('Lock is not owned by this thread')
|
||||||
return deleted
|
try:
|
||||||
|
lock = EventLock.objects.get(event=event.identity, token=event._lock.token)
|
||||||
|
lock.delete()
|
||||||
|
event._lock = None
|
||||||
|
except EventLock.DoesNotExist:
|
||||||
|
raise EventLock.LockReleaseException('Lock is no longer owned by this thread')
|
||||||
|
|
||||||
|
|
||||||
def redis_lock_from_event(event):
|
def redis_lock_from_event(event):
|
||||||
from django_redis import get_redis_connection
|
from django_redis import get_redis_connection
|
||||||
from redis.lock import Lock
|
from redis.lock import Lock
|
||||||
|
|
||||||
if not hasattr(event, '_redis_lock'):
|
if not hasattr(event, '_lock') or not event._lock:
|
||||||
rc = get_redis_connection("redis")
|
rc = get_redis_connection("redis")
|
||||||
event._redis_lock = Lock(redis=rc, name='pretix_event_%s' % event.identity, timeout=120)
|
event._lock = Lock(redis=rc, name='pretix_event_%s' % event.identity, timeout=LOCK_TIMEOUT)
|
||||||
return event._redis_lock
|
return event._lock
|
||||||
|
|
||||||
|
|
||||||
def lock_event_redis(event):
|
def lock_event_redis(event):
|
||||||
@@ -94,10 +107,8 @@ def lock_event_redis(event):
|
|||||||
lock = redis_lock_from_event(event)
|
lock = redis_lock_from_event(event)
|
||||||
retries = 5
|
retries = 5
|
||||||
for i in range(retries):
|
for i in range(retries):
|
||||||
dt = now()
|
|
||||||
try:
|
try:
|
||||||
if lock.acquire(False):
|
if lock.acquire(False):
|
||||||
event.locked_here = dt
|
|
||||||
return True
|
return True
|
||||||
except RedisError:
|
except RedisError:
|
||||||
logger.exception('Error locking an event')
|
logger.exception('Error locking an event')
|
||||||
@@ -115,5 +126,4 @@ def release_event_redis(event):
|
|||||||
except RedisError:
|
except RedisError:
|
||||||
logger.exception('Error releasing an event lock')
|
logger.exception('Error releasing an event lock')
|
||||||
raise EventLock.LockTimeoutException()
|
raise EventLock.LockTimeoutException()
|
||||||
event.locked_here = None
|
event._lock = None
|
||||||
return True
|
|
||||||
|
|||||||
Reference in New Issue
Block a user