Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 87f423e

Browse files
authored
Add read lock to get_flag_keys method (#35)
1 parent ebc1a0b commit 87f423e

File tree

6 files changed

+34
-45
lines changed

6 files changed

+34
-45
lines changed

eppo_client/__init__.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
ExperimentConfigurationRequestor,
66
)
77
from eppo_client.configuration_store import ConfigurationStore
8-
from eppo_client.constants import MAX_CACHE_ENTRIES
98
from eppo_client.http_client import HttpClient, SdkParams
109
from eppo_client.models import Flag
1110
from eppo_client.read_write_lock import ReadWriteLock
@@ -31,18 +30,15 @@ def init(config: Config) -> EppoClient:
3130
apiKey=config.api_key, sdkName="python", sdkVersion=__version__
3231
)
3332
http_client = HttpClient(base_url=config.base_url, sdk_params=sdk_params)
34-
config_store: ConfigurationStore[Flag] = ConfigurationStore(
35-
max_size=MAX_CACHE_ENTRIES
36-
)
33+
config_store: ConfigurationStore[Flag] = ConfigurationStore()
3734
config_requestor = ExperimentConfigurationRequestor(
3835
http_client=http_client, config_store=config_store
3936
)
4037
assignment_logger = config.assignment_logger
4138
is_graceful_mode = config.is_graceful_mode
4239
global __client
4340
global __lock
44-
try:
45-
__lock.acquire_write()
41+
with __lock.writer():
4642
if __client:
4743
# if a client was already initialized, stop the background processes of the old client
4844
__client._shutdown()
@@ -52,8 +48,6 @@ def init(config: Config) -> EppoClient:
5248
is_graceful_mode=is_graceful_mode,
5349
)
5450
return __client
55-
finally:
56-
__lock.release_write()
5751

5852

5953
def get_instance() -> EppoClient:
@@ -67,11 +61,8 @@ def get_instance() -> EppoClient:
6761
"""
6862
global __client
6963
global __lock
70-
try:
71-
__lock.acquire_read()
64+
with __lock.reader():
7265
if __client:
7366
return __client
7467
else:
7568
raise Exception("init() must be called before get_instance()")
76-
finally:
77-
__lock.release_read()

eppo_client/configuration_store.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,23 @@
11
from typing import Dict, Optional, TypeVar, Generic
2-
from cachetools import LRUCache
32

43
from eppo_client.read_write_lock import ReadWriteLock
54

65
T = TypeVar("T")
76

87

98
class ConfigurationStore(Generic[T]):
10-
def __init__(self, max_size: int):
11-
self.__cache: LRUCache = LRUCache(maxsize=max_size)
9+
def __init__(self):
10+
self.__cache: Dict[str, T] = {}
1211
self.__lock = ReadWriteLock()
1312

1413
def get_configuration(self, key: str) -> Optional[T]:
15-
try:
16-
self.__lock.acquire_read()
17-
return self.__cache[key]
18-
except KeyError:
19-
return None # key does not exist
20-
finally:
21-
self.__lock.release_read()
14+
with self.__lock.reader():
15+
return self.__cache.get(key, None)
2216

2317
def set_configurations(self, configs: Dict[str, T]):
24-
try:
25-
self.__lock.acquire_write()
26-
self.__cache.clear()
27-
for key, config in configs.items():
28-
self.__cache[key] = config
29-
finally:
30-
self.__lock.release_write()
18+
with self.__lock.writer():
19+
self.__cache = configs
3120

3221
def get_keys(self):
33-
return list(self.__cache.keys())
22+
with self.__lock.reader():
23+
return list(self.__cache.keys())

eppo_client/constants.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# configuration cache
2-
MAX_CACHE_ENTRIES = 1000 # arbitrary; the caching library requires a max limit
3-
41
# poller
52
SECOND_MILLIS = 1000
63
MINUTE_MILLIS = 60 * SECOND_MILLIS

eppo_client/read_write_lock.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import threading
2+
from contextlib import contextmanager
23

3-
# Copied from: https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s04.html
4+
# Adapted from: https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s04.html
45

56

67
class ReadWriteLock:
@@ -14,21 +15,15 @@ def __init__(self):
1415
def acquire_read(self):
1516
"""Acquire a read lock. Blocks only if a thread has
1617
acquired the write lock."""
17-
self._read_ready.acquire()
18-
try:
18+
with self._read_ready:
1919
self._readers += 1
20-
finally:
21-
self._read_ready.release()
2220

2321
def release_read(self):
2422
"""Release a read lock."""
25-
self._read_ready.acquire()
26-
try:
23+
with self._read_ready:
2724
self._readers -= 1
2825
if not self._readers:
2926
self._read_ready.notify_all()
30-
finally:
31-
self._read_ready.release()
3227

3328
def acquire_write(self):
3429
"""Acquire a write lock. Blocks until there are no
@@ -40,3 +35,19 @@ def acquire_write(self):
4035
def release_write(self):
4136
"""Release a write lock."""
4237
self._read_ready.release()
38+
39+
@contextmanager
40+
def reader(self):
41+
try:
42+
self.acquire_read()
43+
yield
44+
finally:
45+
self.release_read()
46+
47+
@contextmanager
48+
def writer(self):
49+
try:
50+
self.acquire_write()
51+
yield
52+
finally:
53+
self.release_write()

eppo_client/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.0.1"
1+
__version__ = "3.0.2"

test/configuration_store_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
TEST_MAX_SIZE = 10
77

8-
store: ConfigurationStore[str] = ConfigurationStore(max_size=TEST_MAX_SIZE)
8+
store: ConfigurationStore[str] = ConfigurationStore()
99
mock_flag = Flag(
1010
key="mock_flag",
1111
variation_type=VariationType.STRING,
@@ -40,7 +40,7 @@ def test_evicts_old_entries_when_max_size_exceeded():
4040

4141

4242
def test_evicts_old_entries_when_setting_new_flags():
43-
store: ConfigurationStore[str] = ConfigurationStore(max_size=TEST_MAX_SIZE)
43+
store: ConfigurationStore[str] = ConfigurationStore()
4444

4545
store.set_configurations({"flag": mock_flag, "second_flag": mock_flag})
4646
assert store.get_configuration("flag") == mock_flag

0 commit comments

Comments
 (0)