multifilesystem_nolock.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. # This file is part of Radicale - CalDAV and CardDAV server
  2. # Copyright © 2021 Unrud <unrud@outlook.com>
  3. #
  4. # This library is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This library is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. The multifilesystem backend without file-based locking.
  18. """
  19. import threading
  20. from collections import deque
  21. from typing import ClassVar, Deque, Dict, Hashable, Iterator, Type
  22. from radicale import config, pathutils, types
  23. from radicale.storage import multifilesystem
  24. class RwLock(pathutils.RwLock):
  25. _cond: threading.Condition
  26. def __init__(self) -> None:
  27. super().__init__("")
  28. self._cond = threading.Condition(self._lock)
  29. @types.contextmanager
  30. def acquire(self, mode: str, user: str = "") -> Iterator[None]:
  31. if mode not in "rw":
  32. raise ValueError("Invalid mode: %r" % mode)
  33. with self._cond:
  34. self._cond.wait_for(lambda: not self._writer and (
  35. mode == "r" or self._readers == 0))
  36. if mode == "r":
  37. self._readers += 1
  38. else:
  39. self._writer = True
  40. try:
  41. yield
  42. finally:
  43. with self._cond:
  44. if mode == "r":
  45. self._readers -= 1
  46. self._writer = False
  47. if self._readers == 0:
  48. self._cond.notify_all()
  49. class LockDict:
  50. _lock: threading.Lock
  51. _dict: Dict[Hashable, Deque[threading.Lock]]
  52. def __init__(self) -> None:
  53. self._lock = threading.Lock()
  54. self._dict = {}
  55. @types.contextmanager
  56. def acquire(self, key: Hashable) -> Iterator[None]:
  57. with self._lock:
  58. waiters = self._dict.get(key)
  59. if waiters is None:
  60. self._dict[key] = waiters = deque()
  61. wait = bool(waiters)
  62. waiter = threading.Lock()
  63. waiter.acquire()
  64. waiters.append(waiter)
  65. if wait:
  66. waiter.acquire()
  67. try:
  68. yield
  69. finally:
  70. with self._lock:
  71. assert waiters[0] is waiter and self._dict[key] is waiters
  72. del waiters[0]
  73. if waiters:
  74. waiters[0].release()
  75. else:
  76. del self._dict[key]
  77. class Collection(multifilesystem.Collection):
  78. _storage: "Storage"
  79. @types.contextmanager
  80. def _acquire_cache_lock(self, ns: str = "") -> Iterator[None]:
  81. if self._storage._lock.locked == "w":
  82. yield
  83. return
  84. with self._storage._cache_lock.acquire((self.path, ns)):
  85. yield
  86. class Storage(multifilesystem.Storage):
  87. _collection_class: ClassVar[Type[Collection]] = Collection
  88. _cache_lock: LockDict
  89. def __init__(self, configuration: config.Configuration) -> None:
  90. super().__init__(configuration)
  91. self._lock = RwLock()
  92. self._cache_lock = LockDict()