chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
+397
View File
@@ -0,0 +1,397 @@
import json
import platform
import random
import re
import shutil
import sys
import time
import zlib
from collections import defaultdict
import numpy as np
import pytest
import ray
from ray._common.test_utils import wait_for_condition
from ray.cluster_utils import Cluster, cluster_not_supported
from ray.tests.test_object_spilling import is_dir_empty
# Note: Disk write speed can be as low as 6 MiB/s in AWS Mac instances, so we have to
# increase the timeout.
pytestmark = [pytest.mark.timeout(900 if platform.system() == "Darwin" else 180)]
@pytest.mark.skipif(platform.system() in ["Windows"], reason="Failing on Windows.")
def test_multiple_directories(tmp_path, shutdown_only):
num_dirs = 3
temp_dirs = []
for i in range(num_dirs):
temp_folder = tmp_path / f"spill_{i}"
temp_folder.mkdir()
temp_dirs.append(temp_folder)
# Limit our object store to 75 MiB of memory.
min_spilling_size = 0
object_spilling_config = json.dumps(
{
"type": "filesystem",
"params": {"directory_path": [str(directory) for directory in temp_dirs]},
}
)
ray_context = ray.init(
object_store_memory=75 * 1024 * 1024,
_system_config={
"max_io_workers": 5,
"object_store_full_delay_ms": 100,
"object_spilling_config": object_spilling_config,
"min_spilling_size": min_spilling_size,
},
)
arr = np.ones(74 * 1024 * 1024, dtype=np.uint8) # 74MB.
object_refs = []
# Now the storage is full.
object_refs.append(ray.put(arr))
num_object_spilled = 20
for _ in range(num_object_spilled):
object_refs.append(ray.put(arr))
num_files = defaultdict(int)
for temp_dir in temp_dirs:
# Under temp_dir there are spilled_objects_dir(s) with name pattern
# "ray_spilled_objects[_<node_id>]", each containing spilled object files.
for spilled_objects_dir in temp_folder.iterdir():
for path in spilled_objects_dir.iterdir():
num_files[str(temp_folder)] += 1
for ref in object_refs:
assert np.array_equal(ray.get(ref), arr)
print("Check distribution...")
min_count = 5
is_distributed = [n_files >= min_count for n_files in num_files.values()]
assert all(is_distributed)
print("Check deletion...")
# Empty object refs.
object_refs = []
# Add a new small object so that the last entry is evicted and we don't
# exceed the spill threshold.
ref = ray.put(np.ones(5 * 1024 * 1024, dtype=np.uint8))
for temp_dir in temp_dirs:
temp_folder = temp_dir
wait_for_condition(lambda: is_dir_empty(temp_folder, ray_context["node_id"]))
# Now kill ray and see all directories are deleted.
print("Check directories are deleted...")
ray.shutdown()
for temp_dir in temp_dirs:
wait_for_condition(lambda: is_dir_empty(temp_dir, ray_context["node_id"]))
def _check_spilled(num_objects_spilled=0):
def ok():
s = ray._private.internal_api.memory_summary(stats_only=True)
if num_objects_spilled == 0:
return "Spilled " not in s
m = re.search(r"Spilled (\d+) MiB, (\d+) objects", s)
if m is not None:
actual_num_objects = int(m.group(2))
return actual_num_objects >= num_objects_spilled
return False
wait_for_condition(ok, timeout=90, retry_interval_ms=5000)
def _test_object_spilling_threshold(thres, num_objects, num_objects_spilled):
try:
ray.init(
object_store_memory=2_200_000_000,
_system_config={"object_spilling_threshold": thres} if thres else {},
)
objs = []
for _ in range(num_objects):
objs.append(ray.put(np.empty(200_000_000, dtype=np.uint8)))
if num_objects_spilled == 0:
time.sleep(10) # Wait for spilling to happen
_check_spilled(num_objects_spilled)
finally:
ray.shutdown()
@pytest.mark.skipif(platform.system() != "Linux", reason="Failing on Windows/macOS.")
def test_object_spilling_threshold_default():
_test_object_spilling_threshold(None, 10, 5)
@pytest.mark.skipif(platform.system() != "Linux", reason="Failing on Windows/macOS.")
def test_object_spilling_threshold_1_0():
_test_object_spilling_threshold(1.0, 10, 0)
@pytest.mark.skipif(platform.system() != "Linux", reason="Failing on Windows/macOS.")
def test_object_spilling_threshold_0_1():
_test_object_spilling_threshold(0.1, 10, 5)
def test_partial_retval_allocation(ray_start_cluster_enabled):
cluster = ray_start_cluster_enabled
cluster.add_node(object_store_memory=100 * 1024 * 1024)
ray.init(cluster.address)
@ray.remote(num_returns=4)
def f():
return [np.zeros(50 * 1024 * 1024, dtype=np.uint8) for _ in range(4)]
ret = f.remote()
for obj in ret:
obj = ray.get(obj)
print(obj.size)
def test_pull_spilled_object(
ray_start_cluster_enabled, multi_node_object_spilling_config, shutdown_only
):
cluster = ray_start_cluster_enabled
object_spilling_config, _ = multi_node_object_spilling_config
# Head node.
cluster.add_node(
num_cpus=1,
resources={"custom": 0},
object_store_memory=75 * 1024 * 1024,
_system_config={
"max_io_workers": 2,
"min_spilling_size": 1 * 1024 * 1024,
"automatic_object_spilling_enabled": True,
"object_store_full_delay_ms": 100,
"object_spilling_config": object_spilling_config,
},
)
ray.init(cluster.address)
# add 1 worker node
cluster.add_node(
num_cpus=1, resources={"custom": 1}, object_store_memory=75 * 1024 * 1024
)
cluster.wait_for_nodes()
@ray.remote(num_cpus=1, resources={"custom": 1})
def create_objects():
results = []
for size in range(5):
arr = np.random.rand(size * 1024 * 1024)
hash_value = zlib.crc32(arr.tobytes())
results.append([ray.put(arr), hash_value])
# ensure the objects are spilled
arr = np.random.rand(5 * 1024 * 1024)
ray.get(ray.put(arr))
ray.get(ray.put(arr))
return results
@ray.remote(num_cpus=1, resources={"custom": 0})
def get_object(arr):
return zlib.crc32(arr.tobytes())
results = ray.get(create_objects.remote())
for value_ref, hash_value in results:
hash_value1 = ray.get(get_object.remote(value_ref))
assert hash_value == hash_value1
# TODO(chenshen): fix error handling when spilled file
# missing/corrupted
@pytest.mark.skipif(True, reason="Currently hangs.")
def test_pull_spilled_object_failure(object_spilling_config, ray_start_cluster):
object_spilling_config, temp_folder = object_spilling_config
cluster = ray_start_cluster
# Head node.
cluster.add_node(
num_cpus=1,
resources={"custom": 0},
object_store_memory=75 * 1024 * 1024,
_system_config={
"max_io_workers": 2,
"min_spilling_size": 1 * 1024 * 1024,
"automatic_object_spilling_enabled": True,
"object_store_full_delay_ms": 100,
"object_spilling_config": object_spilling_config,
},
)
ray.init(cluster.address)
# add 1 worker node
cluster.add_node(
num_cpus=1, resources={"custom": 1}, object_store_memory=75 * 1024 * 1024
)
cluster.wait_for_nodes()
@ray.remote(num_cpus=1, resources={"custom": 1})
def create_objects():
arr = np.random.rand(5 * 1024 * 1024)
hash_value = zlib.crc32(arr.tobytes())
results = [ray.put(arr), hash_value]
# ensure the objects are spilled
arr = np.random.rand(5 * 1024 * 1024)
ray.get(ray.put(arr))
ray.get(ray.put(arr))
return results
@ray.remote(num_cpus=1, resources={"custom": 0})
def get_object(arr):
return zlib.crc32(arr.tobytes())
[ref, hash_value] = ray.get(create_objects.remote())
# remove spilled file
shutil.rmtree(temp_folder)
hash_value1 = ray.get(get_object.remote(ref))
assert hash_value == hash_value1
@pytest.mark.xfail(cluster_not_supported, reason="cluster not supported")
def test_spill_dir_cleanup_on_node_removal(fs_only_object_spilling_config):
object_spilling_config, temp_folder = fs_only_object_spilling_config
cluster = Cluster()
cluster.add_node(
num_cpus=0,
object_store_memory=75 * 1024 * 1024,
_system_config={"object_spilling_config": object_spilling_config},
)
ray.init(address=cluster.address)
node2 = cluster.add_node(num_cpus=1, object_store_memory=75 * 1024 * 1024)
# This task will run on node 2 because node 1 has no CPU resource
@ray.remote(num_cpus=1)
def run_workload():
ids = []
for _ in range(2):
arr = np.random.rand(5 * 1024 * 1024) # 40 MB
ids.append(ray.put(arr))
return ids
ids = ray.get(run_workload.remote())
node2_id = node2.node_id
assert not is_dir_empty(temp_folder, node2_id)
# Kill node 2
cluster.remove_node(node2)
# Verify that the spill folder is cleaned up upon node removal
assert is_dir_empty(temp_folder, node2_id)
# We hold the object refs to prevent them from being deleted
# due to out of scope.
del ids
ray.shutdown()
cluster.shutdown()
def test_spill_deadlock(object_spilling_config, shutdown_only):
object_spilling_config, _ = object_spilling_config
# Limit our object store to 75 MiB of memory.
ray.init(
object_store_memory=75 * 1024 * 1024,
_system_config={
"max_io_workers": 1,
"automatic_object_spilling_enabled": True,
"object_store_full_delay_ms": 100,
"object_spilling_config": object_spilling_config,
"min_spilling_size": 0,
},
)
arr = np.random.rand(1024 * 1024) # 8 MB data
replay_buffer = []
# Create objects of more than 400 MiB.
for _ in range(50):
ref = None
while ref is None:
ref = ray.put(arr)
replay_buffer.append(ref)
# This is doing random sampling with 50% prob.
if random.randint(0, 9) < 5:
for _ in range(5):
ref = random.choice(replay_buffer)
sample = ray.get(ref, timeout=None)
assert np.array_equal(sample, arr)
def test_spill_reconstruction_errors(ray_start_cluster, object_spilling_config):
config = {
"health_check_failure_threshold": 10,
"health_check_period_ms": 100,
"health_check_initial_delay_ms": 0,
"max_direct_call_object_size": 100,
"task_retry_delay_ms": 100,
"object_timeout_milliseconds": 200,
}
cluster = ray_start_cluster
# Head node with no resources.
cluster.add_node(num_cpus=0, _system_config=config, object_store_memory=10**8)
ray.init(address=cluster.address)
# Node to place the initial object.
node_to_kill = cluster.add_node(num_cpus=1, object_store_memory=10**8)
@ray.remote
def put():
return np.zeros(10**5, dtype=np.uint8)
@ray.remote
def check(x):
return
ref = put.remote()
for _ in range(4):
ray.get(check.remote(ref))
cluster.remove_node(node_to_kill, allow_graceful=False)
node_to_kill = cluster.add_node(num_cpus=1, object_store_memory=10**8)
# All reconstruction attempts used up. The object's value should now be an
# error in the local store.
# Force object spilling and check that it can complete.
xs = []
for _ in range(20):
xs.append(ray.put(np.zeros(10**7, dtype=np.uint8)))
for x in xs:
ray.get(x, timeout=10)
with pytest.raises(ray.exceptions.ObjectLostError):
ray.get(ref)
def test_evict_secondary_copies_before_spill(ray_start_cluster, object_spilling_config):
cluster = ray_start_cluster
cluster.add_node(num_cpus=1, object_store_memory=10**8)
ray.init(address=cluster.address)
for _ in range(3):
cluster.add_node(num_cpus=1, object_store_memory=10**8)
wait_for_condition(lambda: ray.cluster_resources()["CPU"] >= 4)
# Spread data onto all nodes.
@ray.remote
def gen():
time.sleep(0.5)
return np.ones(10 * 1024 * 1024, dtype=np.uint8)
refs = [
gen.options(scheduling_strategy="SPREAD").remote() for _ in range(16)
] # 160MiB
# Iterate over the data on the worker nodes from the head node.
for i in range(10):
for j, r in enumerate(refs):
print("Iteration", i, j)
ray.get(r)
_check_spilled()
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))