Files
2026-07-13 13:17:40 +08:00

224 lines
6.5 KiB
Python

import sys
import threading
import time
import pytest
from ray.util.client.common import (
INT32_MAX,
OrderedResponseCache,
ResponseCache,
_id_is_newer,
)
def test_id_is_newer():
"""
Sanity checks the logic for ID is newer. In general, we would expect
that higher IDs are newer than lower IDs, for example 25 can be assumed
to be newer than 24.
Since IDs roll over at INT32_MAX (~2**31), we should check for weird
behavior there. In particular, we would expect an ID like `11` to be
newer than the ID `2**31` since it's likely that the counter rolled
over.
"""
# Common cases -- higher IDs normally considered newer
assert _id_is_newer(30, 29)
assert _id_is_newer(12345, 12344)
assert not _id_is_newer(12344, 12345)
assert not _id_is_newer(5678, 5678)
# Check behavior near max int boundary
assert _id_is_newer(INT32_MAX, INT32_MAX - 1)
assert _id_is_newer(INT32_MAX - 1, INT32_MAX - 2)
# Low IDs are assumed newer than higher ones if it looks like rollover has
# occurred
assert _id_is_newer(0, INT32_MAX - 4)
assert _id_is_newer(1001, INT32_MAX - 123)
assert not _id_is_newer(INT32_MAX, 123)
def test_response_cache_complete_response():
"""
Test basic check/update logic of cache, and that nothing blocks
"""
cache = ResponseCache()
cache.check_cache(123, 15) # shouldn't block
cache.update_cache(123, 15, "abcdef")
assert cache.check_cache(123, 15) == "abcdef"
def test_ordered_response_cache_complete_response():
"""
Test basic check/update logic of ordered cache, and that nothing blocks
"""
cache = OrderedResponseCache()
cache.check_cache(15) # shouldn't block
cache.update_cache(15, "vwxyz")
assert cache.check_cache(15) == "vwxyz"
def test_response_cache_incomplete_response():
"""
Tests case where a cache entry is populated after a long time. Any new
threads attempting to access that entry should sleep until the response
is ready.
"""
cache = ResponseCache()
def populate_cache():
time.sleep(2)
cache.update_cache(123, 15, "abcdef")
cache.check_cache(123, 15) # shouldn't block
t = threading.Thread(target=populate_cache, args=())
t.start()
# Should block until other thread populates cache
assert cache.check_cache(123, 15) == "abcdef"
t.join()
def test_ordered_response_cache_incomplete_response():
"""
Tests case where an ordered cache entry is populated after a long time. Any
new threads attempting to access that entry should sleep until the response
is ready.
"""
cache = OrderedResponseCache()
def populate_cache():
time.sleep(2)
cache.update_cache(15, "vwxyz")
cache.check_cache(15) # shouldn't block
t = threading.Thread(target=populate_cache, args=())
t.start()
# Should block until other thread populates cache
assert cache.check_cache(15) == "vwxyz"
t.join()
def test_ordered_response_cache_cleanup():
"""
Tests that the cleanup method of ordered cache works as expected, in
particular that all entries <= the passed ID are cleared from the cache.
"""
cache = OrderedResponseCache()
for i in range(1, 21):
assert cache.check_cache(i) is None
cache.update_cache(i, str(i))
assert len(cache.cache) == 20
for i in range(1, 21):
assert cache.check_cache(i) == str(i)
# Expected: clean up all entries up to and including entry 10
cache.cleanup(10)
assert len(cache.cache) == 10
with pytest.raises(RuntimeError):
# Attempting to access value that has already been cleaned up
cache.check_cache(10)
for i in range(21, 31):
# Check that more entries can be inserted
assert cache.check_cache(i) is None
cache.update_cache(i, str(i))
# Cleanup everything
cache.cleanup(30)
assert len(cache.cache) == 0
with pytest.raises(RuntimeError):
cache.check_cache(30)
# Cleanup requests received out of order are tolerated
cache.cleanup(27)
cache.cleanup(23)
def test_response_cache_update_while_waiting():
"""
Tests that an error is thrown when a cache entry is updated with the
response for a different request than what was originally being
checked for.
"""
# Error when awaiting cache to update, but entry is cleaned up
cache = ResponseCache()
assert cache.check_cache(16, 123) is None
def cleanup_cache():
time.sleep(2)
cache.check_cache(16, 124)
cache.update_cache(16, 124, "asdf")
t = threading.Thread(target=cleanup_cache, args=())
t.start()
with pytest.raises(RuntimeError):
cache.check_cache(16, 123)
t.join()
def test_ordered_response_cache_cleanup_while_waiting():
"""
Tests that an error is thrown when an ordered cache entry is updated with
the response for a different request than what was originally being
checked for.
"""
# Error when awaiting cache to update, but entry is cleaned up
cache = OrderedResponseCache()
assert cache.check_cache(123) is None
def cleanup_cache():
time.sleep(2)
cache.cleanup(123)
t = threading.Thread(target=cleanup_cache, args=())
t.start()
with pytest.raises(RuntimeError):
cache.check_cache(123)
t.join()
def test_response_cache_cleanup():
"""
Checks that the response cache replaces old entries for a given thread
with new entries as they come in, instead of creating new entries
(possibly wasting memory on unneeded entries)
"""
# Check that the response cache cleans up previous entries for a given
# thread properly.
cache = ResponseCache()
cache.check_cache(16, 123)
cache.update_cache(16, 123, "Some response")
assert len(cache.cache) == 1
cache.check_cache(16, 124)
cache.update_cache(16, 124, "Second response")
assert len(cache.cache) == 1 # Should reuse entry for thread 16
assert cache.check_cache(16, 124) == "Second response"
def test_response_cache_invalidate():
"""
Check that ordered response cache invalidate works as expected
"""
cache = OrderedResponseCache()
e = RuntimeError("SomeError")
# No pending entries, cache should be valid
assert not cache.invalidate(e)
# No entry for 123 yet
assert cache.check_cache(123) is None
# this should invalidate the entry for 123
assert cache.invalidate(e)
assert cache.check_cache(123) == e
assert cache.invalidate(e)
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))