Skip to content

Commit

Permalink
Thread-safe cache
Browse files Browse the repository at this point in the history
  • Loading branch information
andmat900 committed Oct 4, 2024
1 parent 83b5d7b commit 735e543
Showing 1 changed file with 70 additions and 14 deletions.
84 changes: 70 additions & 14 deletions python/src/etos_api/library/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import asyncio
import time
from typing import List, Union
from threading import Lock
from uuid import UUID

import requests
Expand All @@ -32,10 +33,71 @@

# pylint:disable=too-few-public-methods

# Cache for lazy testrunner validation. Keys: container names, values: timestamp.
# Only passed validations are cached.
TESTRUNNER_VALIDATION_CACHE = {}
TESTRUNNER_VALIDATION_WINDOW = 1800

class TestRunnerValidationCache:
"""Lazy test runner validation via in-memory cache."""

# Cache for lazy testrunner validation. Keys: container names, values: timestamp.
# Only passed validations are cached.
TESTRUNNER_VALIDATION_CACHE = {}
TESTRUNNER_VALIDATION_WINDOW = 1800 # seconds

lock = Lock()

@classmethod
def get_timestamp(cls, test_runner: str) -> Union[float, None]:
"""Get latest passed validation timestamp for the given testrunner.
:param test_runner: test runner container name
:type test_runner: str
:return: validation timestamp or none if not found
:rtype: float or NoneType
"""
with cls.lock:
if test_runner in cls.TESTRUNNER_VALIDATION_CACHE:
return cls.TESTRUNNER_VALIDATION_CACHE[test_runner]
return None

@classmethod
def set_timestamp(cls, test_runner: str, timestamp: float) -> None:
"""Set passed validation timestamp for the given testrunner.
:param test_runner: test runner container name
:type test_runner: str
:param timestamp: test runner container name
:type timestamp: float
:return: none
:rtype: NoneType
"""
with cls.lock:
cls.TESTRUNNER_VALIDATION_CACHE[test_runner] = timestamp

@classmethod
def remove(cls, test_runner: str) -> None:
"""Remove the given test runner from the validation cache.
:param test_runner: test runner container name
:type test_runner: str
:return: none
:rtype: NoneType
"""
with cls.lock:
if test_runner in cls.TESTRUNNER_VALIDATION_CACHE:
del cls.TESTRUNNER_VALIDATION_CACHE[test_runner]

@classmethod
def is_test_runner_valid(cls, test_runner: str) -> bool:
"""Determine if the given test runner is valid.
:param test_runner: test runner container name
:type test_runner: str
:return: validation result from cache
:rtype: bool
"""
timestamp = cls.get_timestamp(test_runner)
if timestamp is None:
return False
return (timestamp + cls.TESTRUNNER_VALIDATION_WINDOW) < time.time()


class Environment(BaseModel):
Expand Down Expand Up @@ -203,15 +265,9 @@ async def validate(self, test_suite_url):
test_runners.add(constraint.value)
docker = Docker()
for test_runner in test_runners:
if test_runner in TESTRUNNER_VALIDATION_CACHE:
timestamp = TESTRUNNER_VALIDATION_CACHE[test_runner]
if (timestamp + TESTRUNNER_VALIDATION_WINDOW) > time.time():
self.logger.info(
"Using cached testrunner validation result: %s", test_runner
)
continue
del TESTRUNNER_VALIDATION_CACHE[test_runner]

if TestRunnerValidationCache.is_test_runner_valid(test_runner):
self.logger.info("Using cached test runner validation result: %s", test_runner)
continue
for attempt in range(5):
if attempt > 0:
span.add_event(f"Test runner validation unsuccessful, retry #{attempt}")
Expand All @@ -223,7 +279,7 @@ async def validate(self, test_suite_url):
result = await docker.digest(test_runner)
if result:
# only passed validations shall be cached
TESTRUNNER_VALIDATION_CACHE[test_runner] = time.time()
TestRunnerValidationCache.set_timestamp(test_runner, time.time())
break
# Total wait time with 5 attempts: 55 seconds
sleep_time = (attempt + 1) ** 2
Expand Down

0 comments on commit 735e543

Please sign in to comment.