Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions eppo_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
ExperimentConfigurationRequestor,
)
from eppo_client.configuration_store import ConfigurationStore
from eppo_client.constants import MAX_CACHE_ENTRIES
from eppo_client.http_client import HttpClient, SdkParams
from eppo_client.models import Flag
from eppo_client.read_write_lock import ReadWriteLock
Expand All @@ -31,18 +30,15 @@ def init(config: Config) -> EppoClient:
apiKey=config.api_key, sdkName="python", sdkVersion=__version__
)
http_client = HttpClient(base_url=config.base_url, sdk_params=sdk_params)
config_store: ConfigurationStore[Flag] = ConfigurationStore(
max_size=MAX_CACHE_ENTRIES
)
config_store: ConfigurationStore[Flag] = ConfigurationStore()
config_requestor = ExperimentConfigurationRequestor(
http_client=http_client, config_store=config_store
)
assignment_logger = config.assignment_logger
is_graceful_mode = config.is_graceful_mode
global __client
global __lock
try:
__lock.acquire_write()
with __lock.writer():
if __client:
# if a client was already initialized, stop the background processes of the old client
__client._shutdown()
Expand All @@ -52,8 +48,6 @@ def init(config: Config) -> EppoClient:
is_graceful_mode=is_graceful_mode,
)
return __client
finally:
__lock.release_write()


def get_instance() -> EppoClient:
Expand All @@ -67,11 +61,8 @@ def get_instance() -> EppoClient:
"""
global __client
global __lock
try:
__lock.acquire_read()
with __lock.reader():
if __client:
return __client
else:
raise Exception("init() must be called before get_instance()")
finally:
__lock.release_read()
26 changes: 8 additions & 18 deletions eppo_client/configuration_store.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
from typing import Dict, Optional, TypeVar, Generic
from cachetools import LRUCache

from eppo_client.read_write_lock import ReadWriteLock

T = TypeVar("T")


class ConfigurationStore(Generic[T]):
def __init__(self, max_size: int):
self.__cache: LRUCache = LRUCache(maxsize=max_size)
def __init__(self):
self.__cache: Dict[str, T] = {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this cache used? If it's for assignments I think we do want some sort of eviction

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cache is used to store the flag configuration. There is no cache for assignments for the Python SDK at the moment.

self.__lock = ReadWriteLock()

def get_configuration(self, key: str) -> Optional[T]:
try:
self.__lock.acquire_read()
return self.__cache[key]
except KeyError:
return None # key does not exist
finally:
self.__lock.release_read()
with self.__lock.reader():
return self.__cache.get(key, None)

def set_configurations(self, configs: Dict[str, T]):
try:
self.__lock.acquire_write()
self.__cache.clear()
for key, config in configs.items():
self.__cache[key] = config
finally:
self.__lock.release_write()
with self.__lock.writer():
self.__cache = configs

def get_keys(self):
return list(self.__cache.keys())
with self.__lock.reader():
return list(self.__cache.keys())
3 changes: 0 additions & 3 deletions eppo_client/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# configuration cache
MAX_CACHE_ENTRIES = 1000 # arbitrary; the caching library requires a max limit

# poller
SECOND_MILLIS = 1000
MINUTE_MILLIS = 60 * SECOND_MILLIS
Expand Down
29 changes: 20 additions & 9 deletions eppo_client/read_write_lock.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import threading
from contextlib import contextmanager

# Copied from: https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s04.html
# Adapted from: https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s04.html
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Show me the LLM vectors

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that first, it went down a much more complicated path so I just added the contextmanager myself :)



class ReadWriteLock:
Expand All @@ -14,21 +15,15 @@ def __init__(self):
def acquire_read(self):
"""Acquire a read lock. Blocks only if a thread has
acquired the write lock."""
self._read_ready.acquire()
try:
with self._read_ready:
self._readers += 1
finally:
self._read_ready.release()

def release_read(self):
"""Release a read lock."""
self._read_ready.acquire()
try:
with self._read_ready:
self._readers -= 1
if not self._readers:
self._read_ready.notify_all()
finally:
self._read_ready.release()

def acquire_write(self):
"""Acquire a write lock. Blocks until there are no
Expand All @@ -40,3 +35,19 @@ def acquire_write(self):
def release_write(self):
"""Release a write lock."""
self._read_ready.release()

@contextmanager
def reader(self):
try:
self.acquire_read()
yield
finally:
self.release_read()

@contextmanager
def writer(self):
try:
self.acquire_write()
yield
finally:
self.release_write()
2 changes: 1 addition & 1 deletion eppo_client/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.0.1"
__version__ = "3.0.2"
4 changes: 2 additions & 2 deletions test/configuration_store_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

TEST_MAX_SIZE = 10

store: ConfigurationStore[str] = ConfigurationStore(max_size=TEST_MAX_SIZE)
store: ConfigurationStore[str] = ConfigurationStore()
mock_flag = Flag(
key="mock_flag",
variation_type=VariationType.STRING,
Expand Down Expand Up @@ -40,7 +40,7 @@ def test_evicts_old_entries_when_max_size_exceeded():


def test_evicts_old_entries_when_setting_new_flags():
store: ConfigurationStore[str] = ConfigurationStore(max_size=TEST_MAX_SIZE)
store: ConfigurationStore[str] = ConfigurationStore()

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