diff --git a/HISTORY.rst b/HISTORY.rst index 241e0c685d..1a371d7e03 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -169,6 +169,9 @@ - Add document type change API endpoint. - Change OCR API submit URL from documents/{pk}/submit to documents/{pk}/ocr/submit. +- Add Redis based distributed lock backend. Requires one + argument: "redis_url". Example: redis://127.0.0.1:6379/0 +- Add the setting LOCK_MANAGER_BACKEND_ARGUMENTS. 3.3.11 (2019-XX-XX) =================== diff --git a/mayan/apps/lock_manager/backends/redis_lock.py b/mayan/apps/lock_manager/backends/redis_lock.py new file mode 100644 index 0000000000..2527abf1c7 --- /dev/null +++ b/mayan/apps/lock_manager/backends/redis_lock.py @@ -0,0 +1,43 @@ +from __future__ import unicode_literals + +import redis + +from ..exceptions import LockError +from ..settings import setting_backend_arguments + +from .base import LockingBackend + + +class RedisLock(LockingBackend): + @classmethod + def acquire_lock(cls, name, timeout=None): + super(RedisLock, cls).acquire_lock(name=name, timeout=timeout) + return RedisLock(name=name, timeout=timeout) + + @classmethod + def get_redis_connection(cls): + redis_url = setting_backend_arguments.value.get('redis_url', None) + server = redis.from_url(url=redis_url) + server.client_id() + return server + + @classmethod + def purge_locks(cls): + super(RedisLock, cls).purge_locks() + + def __init__(self, name, timeout): + self.name = name + redis_lock_instance = self.get_redis_connection().lock( + name=name, timeout=timeout, sleep=0.1, blocking_timeout=0.1 + ) + if redis_lock_instance.acquire(): + self.redis_lock_instance = redis_lock_instance + else: + raise LockError + + def release(self): + super(RedisLock, self).release() + try: + self.redis_lock_instance.release() + except redis.exceptions.LockNotOwnedError: + return diff --git a/mayan/apps/lock_manager/settings.py b/mayan/apps/lock_manager/settings.py index 30448fa1fb..78e1cf6261 100644 --- a/mayan/apps/lock_manager/settings.py +++ b/mayan/apps/lock_manager/settings.py @@ -15,6 +15,10 @@ setting_backend = namespace.add_setting( 'resource locks.' ) ) +setting_backend_arguments = namespace.add_setting( + global_name='LOCK_MANAGER_BACKEND_ARGUMENTS', + default={}, help_text=_('Arguments to pass to the LOCK_MANAGER_BACKEND.') +) setting_default_lock_timeout = namespace.add_setting( default=DEFAULT_LOCK_TIMEOUT_VALUE, diff --git a/mayan/apps/lock_manager/tests/test_backends.py b/mayan/apps/lock_manager/tests/test_backends.py index 97844c1207..24f07fa13c 100644 --- a/mayan/apps/lock_manager/tests/test_backends.py +++ b/mayan/apps/lock_manager/tests/test_backends.py @@ -2,18 +2,21 @@ from __future__ import unicode_literals import time -from django.test import TestCase +from django.test import override_settings from django.utils.module_loading import import_string +from mayan.apps.common.tests.base import BaseTestCase + from ..exceptions import LockError TEST_LOCK_1 = 'test lock 1' -class FileLockTestCase(TestCase): +class FileLockTestCase(BaseTestCase): backend_string = 'mayan.apps.lock_manager.backends.file_lock.FileLock' def setUp(self): + super(FileLockTestCase, self).setUp() self.locking_backend = import_string(self.backend_string) def test_exclusive(self): @@ -72,3 +75,10 @@ class FileLockTestCase(TestCase): class ModelLockTestCase(FileLockTestCase): backend_string = 'mayan.apps.lock_manager.backends.model_lock.ModelLock' + + +@override_settings( + LOCK_MANAGER_BACKEND_ARGUMENTS={'redis_url': 'redis://127.0.0.1:6379/0'} +) +class RedisLockTestCase(FileLockTestCase): + backend_string = 'mayan.apps.lock_manager.backends.redis_lock.RedisLock'