Add multifilesystem_nolock storage
This commit is contained in:
parent
e629e9a2e1
commit
f14e1de071
@ -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
|
||||
|
2
config
2
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
|
||||
|
@ -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
radicale/storage/multifilesystem_nolock.py
Normal file
103
radicale/storage/multifilesystem_nolock.py
Normal file
@ -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 = {}
|
@ -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."""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user