/tests/test_rwlock.py

https://github.com/aio-libs/aiorwlock · Python · 544 lines · 456 code · 5 blank · 83 comment · 0 complexity · 8667a65eec8a588b8798393d713a2191 MD5 · raw file

  1. import asyncio
  2. import pytest
  3. from aiorwlock import RWLock, _current_task
  4. class Bunch(object):
  5. """A bunch of Tasks. Port python threading tests"""
  6. def __init__(self, f, n, wait_before_exit=False):
  7. """
  8. Construct a bunch of `n` tasks running the same function `f`.
  9. If `wait_before_exit` is True, the tasks won't terminate until
  10. do_finish() is called.
  11. """
  12. self._loop = asyncio.get_event_loop()
  13. self.f = f
  14. self.n = n
  15. self.started = []
  16. self.finished = []
  17. self._can_exit = not wait_before_exit
  18. self._futures = []
  19. async def task():
  20. tid = _current_task()
  21. self.started.append(tid)
  22. try:
  23. await f()
  24. finally:
  25. self.finished.append(tid)
  26. while not self._can_exit:
  27. await asyncio.sleep(0.01)
  28. for _ in range(n):
  29. t = asyncio.Task(task())
  30. self._futures.append(t)
  31. async def wait_for_finished(self):
  32. await asyncio.gather(*self._futures)
  33. def do_finish(self):
  34. self._can_exit = True
  35. async def _wait():
  36. await asyncio.sleep(0.01)
  37. def test_ctor_deprecated_implicit_not_running(loop):
  38. with pytest.warns(DeprecationWarning):
  39. RWLock()
  40. def test_ctor_deprecated_explicit_non_running(loop):
  41. with pytest.warns(DeprecationWarning):
  42. RWLock(loop=loop)
  43. @pytest.mark.asyncio
  44. async def test_ctor_loop_deprecated_arg(loop):
  45. with pytest.warns(DeprecationWarning):
  46. RWLock(loop=loop)
  47. @pytest.mark.asyncio
  48. async def test_ctor_loop_reader(loop):
  49. rwlock = RWLock().reader_lock
  50. assert rwlock._lock._loop is loop
  51. @pytest.mark.asyncio
  52. async def test_ctor_noloop_reader(loop):
  53. asyncio.set_event_loop(loop)
  54. rwlock = RWLock().reader_lock
  55. assert rwlock._lock._loop is loop
  56. @pytest.mark.asyncio
  57. async def test_ctor_loop_writer(loop):
  58. rwlock = RWLock().writer_lock
  59. assert rwlock._lock._loop is loop
  60. @pytest.mark.asyncio
  61. async def test_ctor_noloop_writer(loop):
  62. asyncio.set_event_loop(loop)
  63. rwlock = RWLock().writer_lock
  64. assert rwlock._lock._loop is loop
  65. @pytest.mark.asyncio
  66. async def test_repr(loop):
  67. rwlock = RWLock()
  68. assert 'RWLock' in rwlock.__repr__()
  69. assert 'WriterLock: [unlocked' in rwlock.__repr__()
  70. assert 'ReaderLock: [unlocked' in rwlock.__repr__()
  71. # reader lock __repr__
  72. await rwlock.reader_lock.acquire()
  73. assert 'ReaderLock: [locked]' in rwlock.__repr__()
  74. rwlock.reader_lock.release()
  75. assert 'ReaderLock: [unlocked]' in rwlock.__repr__()
  76. # writer lock __repr__
  77. await rwlock.writer_lock.acquire()
  78. assert 'WriterLock: [locked]' in rwlock.__repr__()
  79. rwlock.writer_lock.release()
  80. assert 'WriterLock: [unlocked]' in rwlock.__repr__()
  81. @pytest.mark.asyncio
  82. async def test_release_unlocked(loop):
  83. rwlock = RWLock()
  84. with pytest.raises(RuntimeError):
  85. rwlock.reader_lock.release()
  86. @pytest.mark.asyncio
  87. async def test_many_readers(loop, fast_track):
  88. rwlock = RWLock(fast=fast_track)
  89. N = 5
  90. locked = []
  91. nlocked = []
  92. async def f():
  93. await rwlock.reader_lock.acquire()
  94. try:
  95. locked.append(1)
  96. await _wait()
  97. nlocked.append(len(locked))
  98. await _wait()
  99. locked.pop(-1)
  100. finally:
  101. rwlock.reader_lock.release()
  102. await Bunch(f, N).wait_for_finished()
  103. assert max(nlocked) > 1
  104. @pytest.mark.asyncio
  105. async def test_read_upgrade_write_release(loop):
  106. rwlock = RWLock()
  107. await rwlock.writer_lock.acquire()
  108. await rwlock.reader_lock.acquire()
  109. await rwlock.reader_lock.acquire()
  110. await rwlock.reader_lock.acquire()
  111. rwlock.reader_lock.release()
  112. assert rwlock.writer_lock.locked
  113. rwlock.writer_lock.release()
  114. assert not rwlock.writer_lock.locked
  115. assert rwlock.reader.locked
  116. with pytest.raises(RuntimeError):
  117. await rwlock.writer_lock.acquire()
  118. rwlock.reader_lock.release()
  119. rwlock.reader_lock.release()
  120. @pytest.mark.asyncio
  121. async def test_reader_recursion(loop, fast_track):
  122. rwlock = RWLock(fast=fast_track)
  123. N = 5
  124. locked = []
  125. nlocked = []
  126. async def f():
  127. await rwlock.reader_lock.acquire()
  128. try:
  129. await rwlock.reader_lock.acquire()
  130. try:
  131. locked.append(1)
  132. await _wait()
  133. nlocked.append(len(locked))
  134. await _wait()
  135. locked.pop(-1)
  136. finally:
  137. rwlock.reader_lock.release()
  138. finally:
  139. rwlock.reader_lock.release()
  140. await Bunch(f, N).wait_for_finished()
  141. assert max(nlocked) > 1
  142. @pytest.mark.asyncio
  143. async def test_writer_recursion(loop, fast_track):
  144. rwlock = RWLock(fast=fast_track)
  145. N = 5
  146. locked = []
  147. nlocked = []
  148. async def f():
  149. await rwlock.writer_lock.acquire()
  150. try:
  151. await rwlock.writer_lock.acquire()
  152. try:
  153. locked.append(1)
  154. await _wait()
  155. nlocked.append(len(locked))
  156. await _wait()
  157. locked.pop(-1)
  158. finally:
  159. rwlock.writer_lock.release()
  160. finally:
  161. rwlock.writer_lock.release()
  162. await Bunch(f, N).wait_for_finished()
  163. assert max(nlocked) == 1
  164. @pytest.mark.asyncio
  165. async def test_writer_then_reader_recursion(loop, fast_track):
  166. rwlock = RWLock(fast=fast_track)
  167. N = 5
  168. locked = []
  169. nlocked = []
  170. async def f():
  171. await rwlock.writer_lock.acquire()
  172. try:
  173. await rwlock.reader_lock.acquire()
  174. try:
  175. locked.append(1)
  176. await _wait()
  177. nlocked.append(len(locked))
  178. await _wait()
  179. locked.pop(-1)
  180. finally:
  181. rwlock.reader_lock.release()
  182. finally:
  183. rwlock.writer_lock.release()
  184. await Bunch(f, N).wait_for_finished()
  185. assert max(nlocked) == 1
  186. @pytest.mark.asyncio
  187. async def test_writer_recursion_fail(loop):
  188. rwlock = RWLock()
  189. N = 5
  190. locked = []
  191. async def f():
  192. await rwlock.reader_lock.acquire()
  193. try:
  194. with pytest.raises(RuntimeError):
  195. await rwlock.writer_lock.acquire()
  196. locked.append(1)
  197. finally:
  198. rwlock.reader_lock.release()
  199. await Bunch(f, N).wait_for_finished()
  200. assert len(locked) == N
  201. @pytest.mark.asyncio
  202. async def test_readers_writers(loop, fast_track):
  203. rwlock = RWLock(fast=fast_track)
  204. N = 5
  205. rlocked = []
  206. wlocked = []
  207. nlocked = []
  208. async def r():
  209. await rwlock.reader_lock.acquire()
  210. try:
  211. rlocked.append(1)
  212. await _wait()
  213. nlocked.append((len(rlocked), len(wlocked)))
  214. await _wait()
  215. rlocked.pop(-1)
  216. finally:
  217. rwlock.reader_lock.release()
  218. async def w():
  219. await rwlock.writer_lock.acquire()
  220. try:
  221. wlocked.append(1)
  222. await _wait()
  223. nlocked.append((len(rlocked), len(wlocked)))
  224. await _wait()
  225. wlocked.pop(-1)
  226. finally:
  227. rwlock.writer_lock.release()
  228. b1 = Bunch(r, N)
  229. b2 = Bunch(w, N)
  230. await asyncio.sleep(0.0001)
  231. await b1.wait_for_finished()
  232. await b2.wait_for_finished()
  233. (
  234. r,
  235. w,
  236. ) = zip(*nlocked)
  237. assert max(r) > 1
  238. assert max(w) == 1
  239. for r, w in nlocked:
  240. if w:
  241. assert r == 0
  242. if r:
  243. assert w == 0
  244. @pytest.mark.asyncio
  245. async def test_writer_success(loop):
  246. # Verify that a writer can get access
  247. rwlock = RWLock()
  248. N = 5
  249. reads = 0
  250. writes = 0
  251. async def r():
  252. # read until we achive write successes
  253. nonlocal reads, writes
  254. while writes < 2:
  255. # print("current pre-reads", reads)
  256. await rwlock.reader_lock.acquire()
  257. try:
  258. reads += 1
  259. # print("current reads", reads)
  260. finally:
  261. rwlock.reader_lock.release()
  262. async def w():
  263. nonlocal reads, writes
  264. while reads == 0:
  265. await _wait()
  266. for _ in range(2):
  267. await _wait()
  268. # print("current pre-writes", reads)
  269. await rwlock.writer_lock.acquire()
  270. try:
  271. writes += 1
  272. # print("current writes", reads)
  273. finally:
  274. rwlock.writer_lock.release()
  275. b1 = Bunch(r, N)
  276. b2 = Bunch(w, 1)
  277. await b1.wait_for_finished()
  278. await b2.wait_for_finished()
  279. assert writes == 2
  280. # uncomment this to view performance
  281. # print('>>>>>>>>>>>', writes, reads)
  282. @pytest.mark.asyncio
  283. async def test_writer_success_fast(loop):
  284. # Verify that a writer can get access
  285. rwlock = RWLock(fast=True)
  286. N = 5
  287. reads = 0
  288. writes = 0
  289. async def r():
  290. # read until we achive write successes
  291. nonlocal reads, writes
  292. while writes < 2:
  293. # print("current pre-reads", reads)
  294. await rwlock.reader_lock.acquire()
  295. try:
  296. reads += 1
  297. # print("current reads", reads)
  298. await asyncio.sleep(0)
  299. finally:
  300. rwlock.reader_lock.release()
  301. async def w():
  302. nonlocal reads, writes
  303. while reads == 0:
  304. await _wait()
  305. for _ in range(2):
  306. await _wait()
  307. # print("current pre-writes", reads)
  308. await rwlock.writer_lock.acquire()
  309. try:
  310. writes += 1
  311. # print("current writes", reads)
  312. finally:
  313. rwlock.writer_lock.release()
  314. b1 = Bunch(r, N)
  315. b2 = Bunch(w, 1)
  316. await b1.wait_for_finished()
  317. await b2.wait_for_finished()
  318. assert writes == 2
  319. # uncomment this to view performance
  320. # print('>>>>>>>>>>>', writes, reads)
  321. @pytest.mark.asyncio
  322. async def test_writer_success_with_statement(loop):
  323. # Verify that a writer can get access
  324. rwlock = RWLock()
  325. N = 5
  326. reads = 0
  327. writes = 0
  328. async def r():
  329. # read until we achive write successes
  330. nonlocal reads, writes
  331. while writes < 2:
  332. # print("current pre-reads", reads)
  333. async with rwlock.reader_lock:
  334. reads += 1
  335. # print("current reads", reads)
  336. async def w():
  337. nonlocal reads, writes
  338. while reads == 0:
  339. await _wait()
  340. for _ in range(2):
  341. await _wait()
  342. # print("current pre-writes", reads)
  343. async with rwlock.writer_lock:
  344. writes += 1
  345. b1 = Bunch(r, N)
  346. b2 = Bunch(w, 1)
  347. await b1.wait_for_finished()
  348. await b2.wait_for_finished()
  349. assert writes == 2
  350. # uncomment this to view performance
  351. # print('>>>>>>>>>>>', writes, reads)
  352. @pytest.mark.asyncio
  353. async def test_raise_error_on_with_for_reader_lock(loop, fast_track):
  354. rwlock = RWLock(fast=fast_track)
  355. with pytest.raises(RuntimeError):
  356. with rwlock.reader_lock:
  357. pass
  358. @pytest.mark.asyncio
  359. async def test_raise_error_on_with_for_writer_lock(loop, fast_track):
  360. rwlock = RWLock(fast=fast_track)
  361. with pytest.raises(RuntimeError):
  362. with rwlock.writer_lock:
  363. pass
  364. @pytest.mark.asyncio
  365. async def test_read_locked(loop, fast_track):
  366. rwlock = RWLock(fast=fast_track)
  367. assert not rwlock.reader_lock.locked
  368. async with rwlock.reader_lock:
  369. assert rwlock.reader_lock.locked
  370. @pytest.mark.asyncio
  371. async def test_write_locked(loop, fast_track):
  372. rwlock = RWLock(fast=fast_track)
  373. assert not rwlock.writer_lock.locked
  374. async with rwlock.writer_lock:
  375. assert rwlock.writer_lock.locked
  376. @pytest.mark.asyncio
  377. async def test_write_read_lock_multiple_tasks(loop, fast_track):
  378. rwlock = RWLock(fast=fast_track)
  379. rl = rwlock.reader
  380. wl = rwlock.writer
  381. async def coro():
  382. async with rl:
  383. assert not wl.locked
  384. assert rl.locked
  385. await asyncio.sleep(0.2, loop)
  386. async with wl:
  387. assert wl.locked
  388. assert not rl.locked
  389. task = asyncio.Task(coro())
  390. await asyncio.sleep(0.1, loop)
  391. await task
  392. assert not rl.locked
  393. assert not wl.locked
  394. def test_current_task(loop):
  395. with pytest.raises(RuntimeError):
  396. _current_task(loop)
  397. with pytest.raises(RuntimeError):
  398. _current_task()
  399. @pytest.mark.asyncio
  400. async def test_read_context_manager(loop, fast_track):
  401. rwlock = RWLock(fast=fast_track)
  402. reader = rwlock.reader_lock
  403. assert not reader.locked
  404. async with reader:
  405. assert reader.locked
  406. @pytest.mark.asyncio
  407. async def test_write_context_manager(loop, fast_track):
  408. rwlock = RWLock(fast=fast_track)
  409. writer = rwlock.writer_lock
  410. assert not writer.locked
  411. async with writer:
  412. assert writer.locked
  413. @pytest.mark.asyncio
  414. async def test_await_read_lock(loop, fast_track):
  415. rwlock = RWLock(fast=fast_track)
  416. reader = rwlock.reader_lock
  417. assert not reader.locked
  418. async with reader:
  419. assert reader.locked
  420. @pytest.mark.asyncio
  421. async def test_await_write_lock(loop, fast_track):
  422. rwlock = RWLock(fast=fast_track)
  423. writer = rwlock.writer_lock
  424. assert not writer.locked
  425. async with writer:
  426. assert writer.locked