Просмотр исходного кода

Add multifilesystem_nolock storage

Unrud 4 лет назад
Родитель
Сommit
f14e1de071
5 измененных файлов с 117 добавлено и 2 удалено
  1. 3 0
      DOCUMENTATION.md
  2. 1 1
      config
  3. 1 1
      radicale/storage/__init__.py
  4. 103 0
      radicale/storage/multifilesystem_nolock.py
  5. 9 0
      radicale/tests/test_base.py

+ 3 - 0
DOCUMENTATION.md

@@ -743,6 +743,9 @@ Available backends:
 `multifilesystem`
 : Stores the data in the filesystem.
 
+`multifilesystem_nolock`
+: The `multifilesystem` backend without file-based locking. Must only be used with a single process.
+
 Default: `multifilesystem`
 
 #### filesystem_folder

+ 1 - 1
config

@@ -83,7 +83,7 @@
 [storage]
 
 # Storage backend
-# Value: multifilesystem
+# Value: multifilesystem | multifilesystem_nolock
 #type = multifilesystem
 
 # Folder for storing local collections, created if not present

+ 1 - 1
radicale/storage/__init__.py

@@ -37,7 +37,7 @@ from radicale import item as radicale_item
 from radicale import types, utils
 from radicale.item import filter as radicale_filter
 
-INTERNAL_TYPES: Sequence[str] = ("multifilesystem",)
+INTERNAL_TYPES: Sequence[str] = ("multifilesystem", "multifilesystem_nolock",)
 
 CACHE_DEPS: Sequence[str] = ("radicale", "vobject", "python-dateutil",)
 CACHE_VERSION: bytes = "".join(

+ 103 - 0
radicale/storage/multifilesystem_nolock.py

@@ -0,0 +1,103 @@
+# This file is part of Radicale Server - Calendar Server
+# Copyright © 2021 Unrud <unrud@outlook.com>
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+The multifilesystem backend without file-based locking.
+"""
+
+import threading
+from collections import deque
+from typing import Deque, Dict, Iterator, Tuple
+
+from radicale import config, pathutils, types
+from radicale.storage import multifilesystem
+
+
+class RwLock(pathutils.RwLock):
+
+    _cond: threading.Condition
+
+    def __init__(self) -> None:
+        super().__init__("")
+        self._cond = threading.Condition(self._lock)
+
+    @types.contextmanager
+    def acquire(self, mode: str, user: str = "") -> Iterator[None]:
+        if mode not in "rw":
+            raise ValueError("Invalid mode: %r" % mode)
+        with self._cond:
+            self._cond.wait_for(lambda: not self._writer and (
+                                    mode == "r" or self._readers == 0))
+            if mode == "r":
+                self._readers += 1
+                self._cond.notify()
+            else:
+                self._writer = True
+        try:
+            yield
+        finally:
+            with self._cond:
+                if mode == "r":
+                    self._readers -= 1
+                self._writer = False
+                self._cond.notify()
+
+
+class Collection(multifilesystem.Collection):
+
+    _storage: "Storage"
+
+    @types.contextmanager
+    def _acquire_cache_lock(self, ns: str = "") -> Iterator[None]:
+        if self._storage._lock.locked == "w":
+            yield
+            return
+        key = (self.path, ns)
+        with self._storage._cache_lock:
+            waiters = self._storage._cache_locks.get(key)
+            if waiters is None:
+                self._storage._cache_locks[key] = waiters = deque()
+            wait = bool(waiters)
+            waiter = threading.Lock()
+            waiter.acquire()
+            waiters.append(waiter)
+        if wait:
+            waiter.acquire()
+        try:
+            yield
+        finally:
+            with self._storage._cache_lock:
+                removedWaiter = waiters.popleft()
+                assert removedWaiter is waiter
+                if waiters:
+                    waiters[0].release()
+                else:
+                    removedWaiters = self._storage._cache_locks.pop(key)
+                    assert removedWaiters is waiters
+
+
+class Storage(multifilesystem.Storage):
+
+    _collection_class = Collection
+
+    _cache_lock: threading.Lock
+    _cache_locks: Dict[Tuple[str, str], Deque[threading.Lock]]
+
+    def __init__(self, configuration: config.Configuration) -> None:
+        super().__init__(configuration)
+        self._lock = RwLock()
+        self._cache_lock = threading.Lock()
+        self._cache_locks = {}

+ 9 - 0
radicale/tests/test_base.py

@@ -1751,6 +1751,15 @@ class TestMultiFileSystem(BaseFileSystemTest, BaseRequestsMixIn):
             assert "\r\nUID:%s\r\n" % uid in answer
 
 
+class TestMultiFileSystemNoLock(BaseFileSystemTest):
+    """Test BaseRequests on multifilesystem_nolock."""
+
+    storage_type: ClassVar[StorageType] = "multifilesystem_nolock"
+
+    test_add_event = BaseRequestsMixIn.test_add_event
+    test_item_cache_rebuild = TestMultiFileSystem.test_item_cache_rebuild
+
+
 class TestCustomStorageSystem(BaseFileSystemTest):
     """Test custom backend loading."""