Add multifilesystem_nolock storage
This commit is contained in:
parent
e629e9a2e1
commit
f14e1de071
@ -743,6 +743,9 @@ Available backends:
|
|||||||
`multifilesystem`
|
`multifilesystem`
|
||||||
: Stores the data in the filesystem.
|
: 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`
|
Default: `multifilesystem`
|
||||||
|
|
||||||
#### filesystem_folder
|
#### filesystem_folder
|
||||||
|
2
config
2
config
@ -83,7 +83,7 @@
|
|||||||
[storage]
|
[storage]
|
||||||
|
|
||||||
# Storage backend
|
# Storage backend
|
||||||
# Value: multifilesystem
|
# Value: multifilesystem | multifilesystem_nolock
|
||||||
#type = multifilesystem
|
#type = multifilesystem
|
||||||
|
|
||||||
# Folder for storing local collections, created if not present
|
# 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 import types, utils
|
||||||
from radicale.item import filter as radicale_filter
|
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_DEPS: Sequence[str] = ("radicale", "vobject", "python-dateutil",)
|
||||||
CACHE_VERSION: bytes = "".join(
|
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
|
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):
|
class TestCustomStorageSystem(BaseFileSystemTest):
|
||||||
"""Test custom backend loading."""
|
"""Test custom backend loading."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user