multifilesystem_nolock.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. # This file is part of Radicale Server - Calendar 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 Deque, Dict, Iterator, Tuple
  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. self._cond.notify()
  39. else:
  40. self._writer = True
  41. try:
  42. yield
  43. finally:
  44. with self._cond:
  45. if mode == "r":
  46. self._readers -= 1
  47. self._writer = False
  48. self._cond.notify()
  49. class Collection(multifilesystem.Collection):
  50. _storage: "Storage"
  51. @types.contextmanager
  52. def _acquire_cache_lock(self, ns: str = "") -> Iterator[None]:
  53. if self._storage._lock.locked == "w":
  54. yield
  55. return
  56. key = (self.path, ns)
  57. with self._storage._cache_lock:
  58. waiters = self._storage._cache_locks.get(key)
  59. if waiters is None:
  60. self._storage._cache_locks[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._storage._cache_lock:
  71. removedWaiter = waiters.popleft()
  72. assert removedWaiter is waiter
  73. if waiters:
  74. waiters[0].release()
  75. else:
  76. removedWaiters = self._storage._cache_locks.pop(key)
  77. assert removedWaiters is waiters
  78. class Storage(multifilesystem.Storage):
  79. _collection_class = Collection
  80. _cache_lock: threading.Lock
  81. _cache_locks: Dict[Tuple[str, str], Deque[threading.Lock]]
  82. def __init__(self, configuration: config.Configuration) -> None:
  83. super().__init__(configuration)
  84. self._lock = RwLock()
  85. self._cache_lock = threading.Lock()
  86. self._cache_locks = {}