88import datetime
99from collections .abc import AsyncIterator , Callable
1010from contextlib import AbstractAsyncContextManager
11- from typing import Final
1211
1312import pytest
1413from faker import Faker
1514from pytest_mock import MockerFixture
1615from redis .exceptions import LockError , LockNotOwnedError
17- from servicelib .redis import (
18- CouldNotAcquireLockError ,
19- RedisClientSDK ,
20- RedisClientsManager ,
21- RedisManagerDBConfig ,
22- )
16+ from servicelib .redis import RedisClientSDK , RedisClientsManager , RedisManagerDBConfig
2317from servicelib .redis import _constants as redis_constants
24- from servicelib .utils import limited_gather
2518from settings_library .redis import RedisDatabase , RedisSettings
2619from tenacity import (
2720 AsyncRetrying ,
3932]
4033
4134
42- async def _is_locked (redis_client_sdk : RedisClientSDK , lock_name : str ) -> bool :
43- lock = redis_client_sdk .redis .lock (lock_name )
44- return await lock .locked ()
45-
46-
4735@pytest .fixture
4836async def redis_client_sdk (
4937 get_redis_client_sdk : Callable [
@@ -66,34 +54,28 @@ def with_short_default_redis_lock_ttl(mocker: MockerFixture) -> None:
6654 )
6755
6856
69- async def test_redis_key_encode_decode (redis_client_sdk : RedisClientSDK , faker : Faker ):
70- key = faker .pystr ()
71- value = faker .pystr ()
72- await redis_client_sdk .redis .set (key , value )
73- val = await redis_client_sdk .redis .get (key )
74- assert val == value
75- await redis_client_sdk .redis .delete (key )
57+ @pytest .fixture
58+ def lock_name (faker : Faker ) -> str :
59+ return faker .pystr ()
7660
7761
78- async def test_redis_lock_acquisition (redis_client_sdk : RedisClientSDK , faker : Faker ):
79- lock_name = faker .pystr ()
80- lock = redis_client_sdk .redis .lock (lock_name )
62+ async def test_redis_lock_no_ttl (redis_client_sdk : RedisClientSDK , lock_name : str ):
63+ lock = redis_client_sdk .create_lock (lock_name , ttl = None )
8164 assert await lock .locked () is False
8265
83- # Try to acquire the lock:
8466 lock_acquired = await lock .acquire (blocking = False )
8567 assert lock_acquired is True
8668 assert await lock .locked () is True
8769 assert await lock .owned () is True
8870 with pytest .raises (LockError ):
89- # a lock with no timeout cannot be reacquired
71+ # a lock with no ttl cannot be reacquired
9072 await lock .reacquire ()
9173 with pytest .raises (LockError ):
92- # a lock with no timeout cannot be extended
74+ # a lock with no ttl cannot be extended
9375 await lock .extend (2 )
9476
9577 # try to acquire the lock a second time
96- same_lock = redis_client_sdk .redis . lock (lock_name )
78+ same_lock = redis_client_sdk .create_lock (lock_name , ttl = None )
9779 assert await same_lock .locked () is True
9880 assert await same_lock .owned () is False
9981 assert await same_lock .acquire (blocking = False ) is False
@@ -104,11 +86,10 @@ async def test_redis_lock_acquisition(redis_client_sdk: RedisClientSDK, faker: F
10486 assert not await lock .owned ()
10587
10688
107- async def test_redis_lock_context_manager (
108- redis_client_sdk : RedisClientSDK , faker : Faker
89+ async def test_redis_lock_context_manager_no_ttl (
90+ redis_client_sdk : RedisClientSDK , lock_name : str
10991):
110- lock_name = faker .pystr ()
111- lock = redis_client_sdk .redis .lock (lock_name )
92+ lock = redis_client_sdk .create_lock (lock_name , ttl = None )
11293 assert not await lock .locked ()
11394
11495 async with lock :
@@ -123,7 +104,7 @@ async def test_redis_lock_context_manager(
123104 await lock .extend (2 )
124105
125106 # try to acquire the lock a second time
126- same_lock = redis_client_sdk .redis . lock (lock_name , blocking_timeout = 1 )
107+ same_lock = redis_client_sdk .create_lock (lock_name , ttl = None )
127108 assert await same_lock .locked ()
128109 assert not await same_lock .owned ()
129110 assert await same_lock .acquire () is False
@@ -134,11 +115,9 @@ async def test_redis_lock_context_manager(
134115
135116
136117async def test_redis_lock_with_ttl (
137- redis_client_sdk : RedisClientSDK , faker : Faker , redis_lock_ttl : datetime .timedelta
118+ redis_client_sdk : RedisClientSDK , lock_name : str , redis_lock_ttl : datetime .timedelta
138119):
139- ttl_lock = redis_client_sdk .redis .lock (
140- faker .pystr (), timeout = redis_lock_ttl .total_seconds ()
141- )
120+ ttl_lock = redis_client_sdk .create_lock (lock_name , ttl = redis_lock_ttl )
142121 assert not await ttl_lock .locked ()
143122
144123 with pytest .raises (LockNotOwnedError ): # noqa: PT012
@@ -150,104 +129,104 @@ async def test_redis_lock_with_ttl(
150129 assert not await ttl_lock .locked ()
151130
152131
153- async def test_lock_context_with_already_locked_lock_raises (
154- redis_client_sdk : RedisClientSDK , faker : Faker
155- ):
156- lock_name = faker .pystr ()
157- assert await _is_locked (redis_client_sdk , lock_name ) is False
158- async with redis_client_sdk .lock_context (lock_name ) as lock :
159- assert await _is_locked (redis_client_sdk , lock_name ) is True
160-
161- assert isinstance (lock .name , str )
162-
163- # case where gives up immediately to acquire lock without waiting
164- with pytest .raises (CouldNotAcquireLockError ):
165- async with redis_client_sdk .lock_context (lock .name , blocking = False ):
166- ...
167-
168- # case when lock waits up to blocking_timeout_s before giving up on
169- # lock acquisition
170- with pytest .raises (CouldNotAcquireLockError ):
171- async with redis_client_sdk .lock_context (
172- lock .name , blocking = True , blocking_timeout_s = 0.1
173- ):
174- ...
175-
176- assert await lock .locked () is True
177- assert await _is_locked (redis_client_sdk , lock_name ) is False
178-
179-
180- async def test_lock_context_with_data (redis_client_sdk : RedisClientSDK , faker : Faker ):
181- lock_data = faker .text ()
182- lock_name = faker .pystr ()
183- assert await _is_locked (redis_client_sdk , lock_name ) is False
184- assert await redis_client_sdk .lock_value (lock_name ) is None
185- async with redis_client_sdk .lock_context (lock_name , lock_value = lock_data ):
186- assert await _is_locked (redis_client_sdk , lock_name ) is True
187- assert await redis_client_sdk .lock_value (lock_name ) == lock_data
188- assert await _is_locked (redis_client_sdk , lock_name ) is False
189- assert await redis_client_sdk .lock_value (lock_name ) is None
190-
191-
192- async def test_lock_context_released_after_error (
193- redis_client_sdk : RedisClientSDK , faker : Faker
194- ):
195- lock_name = faker .pystr ()
196-
197- assert await redis_client_sdk .lock_value (lock_name ) is None
198-
199- with pytest .raises (RuntimeError ): # noqa: PT012
200- async with redis_client_sdk .lock_context (lock_name ):
201- assert await redis_client_sdk .redis .get (lock_name ) is not None
202- msg = "Expected error"
203- raise RuntimeError (msg )
204-
205- assert await redis_client_sdk .lock_value (lock_name ) is None
206-
207-
208- async def test_lock_acquired_in_parallel_to_update_same_resource (
209- with_short_default_redis_lock_ttl : None ,
210- get_redis_client_sdk : Callable [
211- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
212- ],
213- faker : Faker ,
214- ):
215- INCREASE_OPERATIONS : Final [int ] = 250
216- INCREASE_BY : Final [int ] = 10
217-
218- class RaceConditionCounter :
219- def __init__ (self ):
220- self .value : int = 0
221-
222- async def race_condition_increase (self , by : int ) -> None :
223- current_value = self .value
224- current_value += by
225- # most likely situation which creates issues
226- await asyncio .sleep (redis_constants .DEFAULT_LOCK_TTL .total_seconds () / 2 )
227- self .value = current_value
228-
229- counter = RaceConditionCounter ()
230- lock_name : str = faker .pystr ()
231- # ensures it does nto time out before acquiring the lock
232- time_for_all_inc_counter_calls_to_finish_s : float = (
233- redis_constants .DEFAULT_LOCK_TTL .total_seconds () * INCREASE_OPERATIONS * 10
234- )
235-
236- async def _inc_counter () -> None :
237- async with get_redis_client_sdk ( # noqa: SIM117
238- RedisDatabase .RESOURCES
239- ) as redis_client_sdk :
240- async with redis_client_sdk .lock_context (
241- lock_key = lock_name ,
242- blocking = True ,
243- blocking_timeout_s = time_for_all_inc_counter_calls_to_finish_s ,
244- ):
245- await counter .race_condition_increase (INCREASE_BY )
246-
247- await limited_gather (
248- * (_inc_counter () for _ in range (INCREASE_OPERATIONS )), limit = 15
249- )
250- assert counter .value == INCREASE_BY * INCREASE_OPERATIONS
132+ # async def test_lock_context_with_already_locked_lock_raises(
133+ # redis_client_sdk: RedisClientSDK, faker: Faker
134+ # ):
135+ # lock_name = faker.pystr()
136+ # assert await _is_locked(redis_client_sdk, lock_name) is False
137+ # async with redis_client_sdk.lock_context(lock_name) as lock:
138+ # assert await _is_locked(redis_client_sdk, lock_name) is True
139+
140+ # assert isinstance(lock.name, str)
141+
142+ # # case where gives up immediately to acquire lock without waiting
143+ # with pytest.raises(CouldNotAcquireLockError):
144+ # async with redis_client_sdk.lock_context(lock.name, blocking=False):
145+ # ...
146+
147+ # # case when lock waits up to blocking_timeout_s before giving up on
148+ # # lock acquisition
149+ # with pytest.raises(CouldNotAcquireLockError):
150+ # async with redis_client_sdk.lock_context(
151+ # lock.name, blocking=True, blocking_timeout_s=0.1
152+ # ):
153+ # ...
154+
155+ # assert await lock.locked() is True
156+ # assert await _is_locked(redis_client_sdk, lock_name) is False
157+
158+
159+ # async def test_lock_context_with_data(redis_client_sdk: RedisClientSDK, faker: Faker):
160+ # lock_data = faker.text()
161+ # lock_name = faker.pystr()
162+ # assert await _is_locked(redis_client_sdk, lock_name) is False
163+ # assert await redis_client_sdk.lock_value(lock_name) is None
164+ # async with redis_client_sdk.lock_context(lock_name, lock_value=lock_data):
165+ # assert await _is_locked(redis_client_sdk, lock_name) is True
166+ # assert await redis_client_sdk.lock_value(lock_name) == lock_data
167+ # assert await _is_locked(redis_client_sdk, lock_name) is False
168+ # assert await redis_client_sdk.lock_value(lock_name) is None
169+
170+
171+ # async def test_lock_context_released_after_error(
172+ # redis_client_sdk: RedisClientSDK, faker: Faker
173+ # ):
174+ # lock_name = faker.pystr()
175+
176+ # assert await redis_client_sdk.lock_value(lock_name) is None
177+
178+ # with pytest.raises(RuntimeError): # noqa: PT012
179+ # async with redis_client_sdk.lock_context(lock_name):
180+ # assert await redis_client_sdk.redis.get(lock_name) is not None
181+ # msg = "Expected error"
182+ # raise RuntimeError(msg)
183+
184+ # assert await redis_client_sdk.lock_value(lock_name) is None
185+
186+
187+ # async def test_lock_acquired_in_parallel_to_update_same_resource(
188+ # with_short_default_redis_lock_ttl: None,
189+ # get_redis_client_sdk: Callable[
190+ # [RedisDatabase], AbstractAsyncContextManager[RedisClientSDK]
191+ # ],
192+ # faker: Faker,
193+ # ):
194+ # INCREASE_OPERATIONS: Final[int] = 250
195+ # INCREASE_BY: Final[int] = 10
196+
197+ # class RaceConditionCounter:
198+ # def __init__(self):
199+ # self.value: int = 0
200+
201+ # async def race_condition_increase(self, by: int) -> None:
202+ # current_value = self.value
203+ # current_value += by
204+ # # most likely situation which creates issues
205+ # await asyncio.sleep(redis_constants.DEFAULT_LOCK_TTL.total_seconds() / 2)
206+ # self.value = current_value
207+
208+ # counter = RaceConditionCounter()
209+ # lock_name: str = faker.pystr()
210+ # # ensures it does nto time out before acquiring the lock
211+ # time_for_all_inc_counter_calls_to_finish_s: float = (
212+ # redis_constants.DEFAULT_LOCK_TTL.total_seconds() * INCREASE_OPERATIONS * 10
213+ # )
214+
215+ # async def _inc_counter() -> None:
216+ # async with get_redis_client_sdk( # noqa: SIM117
217+ # RedisDatabase.RESOURCES
218+ # ) as redis_client_sdk:
219+ # async with redis_client_sdk.lock_context(
220+ # lock_key=lock_name,
221+ # blocking=True,
222+ # blocking_timeout_s=time_for_all_inc_counter_calls_to_finish_s,
223+ # ):
224+ # await counter.race_condition_increase(INCREASE_BY)
225+
226+ # await limited_gather(
227+ # *(_inc_counter() for _ in range(INCREASE_OPERATIONS)), limit=15
228+ # )
229+ # assert counter.value == INCREASE_BY * INCREASE_OPERATIONS
251230
252231
253232async def test_redis_client_sdks_manager (
0 commit comments