Files
ray-project--ray/python/ray/tests/test_out_of_disk_space.py
T
2026-07-13 13:17:40 +08:00

268 lines
8.5 KiB
Python

import os
import platform
import shutil
import sys
import tempfile
import time
from contextlib import contextmanager
import numpy as np
import pytest
import ray
from ray._common.test_utils import wait_for_condition
from ray.util.state import list_cluster_events
def calculate_capacity_threshold(disk_capacity_in_bytes):
usage = shutil.disk_usage("/tmp")
threshold = min(1, 1.0 - 1.0 * (usage.free - disk_capacity_in_bytes) / usage.total)
return threshold
def get_current_usage():
usage = shutil.disk_usage("/tmp")
print(f"free: {usage.free} ")
usage_percentage = 1.0 - 1.0 * usage.free / usage.total
print(f"current usage: {usage_percentage}")
return usage_percentage
@contextmanager
def create_tmp_file(n):
tmp_dir = tempfile.mkdtemp(dir="/tmp")
tmp_path = os.path.join(tmp_dir, "test.txt")
with open(tmp_path, "wb") as f:
f.write(os.urandom(n))
try:
yield tmp_path
finally:
os.remove(tmp_path)
@pytest.mark.skipif(platform.system() == "Windows", reason="Not targeting Windows")
def test_put_out_of_disk(shutdown_only):
local_fs_capacity_threshold = calculate_capacity_threshold(200 * 1024 * 1024)
ray.init(
num_cpus=1,
object_store_memory=80 * 1024 * 1024,
_system_config={
"local_fs_capacity_threshold": local_fs_capacity_threshold,
"local_fs_monitor_interval_ms": 10,
},
)
assert get_current_usage() < local_fs_capacity_threshold
ref = ray.put(np.random.rand(20 * 1024 * 1024))
del ref
# create a temp file so that the disk size is over the threshold.
# ray.put doesn't work is that fallback allocation uses mmaped file
# that doesn't neccssary allocate disk spaces.
with create_tmp_file(250 * 1024 * 1024):
wait_for_condition(lambda: get_current_usage() > local_fs_capacity_threshold)
with pytest.raises(ray.exceptions.OutOfDiskError):
ray.put(np.random.rand(20 * 1024 * 1024))
# delete tmp file to reclaim space back.
# Ideally get_current_usage() should immediately reflect the latest disk usage
# after the tmp file is deleted, but somehow there is some delays on CI machines
# so I use wait_for_condition here.
wait_for_condition(lambda: get_current_usage() < local_fs_capacity_threshold)
ray.put(np.random.rand(20 * 1024 * 1024))
@pytest.mark.skipif(platform.system() == "Windows", reason="Not targeting Windows")
def test_task_returns(shutdown_only):
local_fs_capacity_threshold = calculate_capacity_threshold(10 * 1024 * 1024)
ray.init(
num_cpus=1,
object_store_memory=80 * 1024 * 1024,
_system_config={
"local_fs_capacity_threshold": local_fs_capacity_threshold,
"local_fs_monitor_interval_ms": 10,
},
)
# create a temp file so that the disk size is over the threshold.
# ray.put doesn't work is that fallback allocation uses mmaped file
# that doesn't neccssary allocate disk spaces.
with create_tmp_file(250 * 1024 * 1024):
assert get_current_usage() > local_fs_capacity_threshold
time.sleep(1)
@ray.remote
def foo():
time.sleep(1)
return np.random.rand(20 * 1024 * 1024) # 160 MB data
try:
ray.get(foo.remote())
except ray.exceptions.RayTaskError as e:
assert isinstance(e.cause, ray.exceptions.OutOfDiskError)
@pytest.mark.skipif(platform.system() == "Windows", reason="Not targeting Windows")
def test_task_put(shutdown_only):
local_fs_capacity_threshold = calculate_capacity_threshold(1 * 1024 * 1024)
ray.init(
num_cpus=1,
object_store_memory=80 * 1024 * 1024,
_system_config={
"local_fs_capacity_threshold": local_fs_capacity_threshold,
"local_fs_monitor_interval_ms": 10,
},
)
# create a temp file so that the disk size is over the threshold.
# ray.put doesn't work is that fallback allocation uses mmaped file
# that doesn't neccssary allocate disk spaces.
with create_tmp_file(250 * 1024 * 1024):
assert get_current_usage() > local_fs_capacity_threshold
time.sleep(1)
@ray.remote
def foo():
ref = ray.put(np.random.rand(20 * 1024 * 1024)) # 160 MB data
return ref
try:
ray.get(foo.remote())
except ray.exceptions.RayTaskError as e:
assert isinstance(e.cause, ray.exceptions.OutOfDiskError)
@pytest.mark.skipif(platform.system() == "Windows", reason="Not targeting Windows")
def test_task_args(ray_start_cluster):
cluster = ray_start_cluster
cluster.add_node(
num_cpus=1,
object_store_memory=80 * 1024 * 1024,
_system_config={
"local_fs_capacity_threshold": 0,
},
resources={"out_of_memory": 1},
)
cluster.add_node(
num_cpus=1,
object_store_memory=200 * 1024 * 1024,
resources={"sufficient_memory": 1},
)
cluster.wait_for_nodes()
ray.init(address=cluster.address)
@ray.remote
def foo():
return np.random.rand(20 * 1024 * 1024) # 160 MB data
@ray.remote
def bar(obj):
print(obj)
ref = foo.options(resources={"sufficient_memory": 1}).remote()
try:
ray.get(bar.options(resources={"out_of_memory": 1}).remote(ref))
except ray.exceptions.RayTaskError as e:
assert isinstance(e.cause, ray.exceptions.OutOfDiskError)
@pytest.mark.skipif(platform.system() == "Windows", reason="Not targeting Windows")
def test_actor(ray_start_cluster):
cluster = ray_start_cluster
cluster.add_node(
num_cpus=1,
object_store_memory=80 * 1024 * 1024,
_system_config={
"local_fs_capacity_threshold": 0,
},
resources={"out_of_memory": 1},
)
cluster.add_node(
num_cpus=1,
object_store_memory=200 * 1024 * 1024,
resources={"sufficient_memory": 1},
)
cluster.wait_for_nodes()
ray.init(address=cluster.address)
@ray.remote
def foo():
return np.random.rand(20 * 1024 * 1024) # 160 MB data
@ray.remote
class Actor:
def __init__(self, obj):
self._obj = obj
def foo(self):
print(self._obj)
def args_ood(self, obj):
print(obj)
def return_ood(self):
return np.random.rand(20 * 1024 * 1024)
ref = foo.options(resources={"sufficient_memory": 1}).remote()
with pytest.raises(ray.exceptions.RayActorError):
a = Actor.options(resources={"out_of_memory": 0.001}).remote(ref)
ray.get(a.foo.remote())
a = Actor.options(resources={"out_of_memory": 1}).remote(1)
ray.get(a.foo.remote())
try:
ray.get(a.args_ood.remote(ref))
except ray.exceptions.RayTaskError as e:
assert isinstance(e.cause, ray.exceptions.OutOfDiskError)
ray.get(a.foo.remote())
try:
ray.get(a.return_ood.remote())
except ray.exceptions.RayTaskError as e:
assert isinstance(e.cause, ray.exceptions.OutOfDiskError)
@pytest.mark.skipif(platform.system() == "Windows", reason="Not targeting Windows")
def test_ood_events(shutdown_only):
local_fs_capacity_threshold = calculate_capacity_threshold(200 * 1024 * 1024)
ray.init(
num_cpus=1,
object_store_memory=80 * 1024 * 1024,
_system_config={
"local_fs_capacity_threshold": local_fs_capacity_threshold,
"local_fs_monitor_interval_ms": 10,
},
)
# create a temp file so that the disk size is over the threshold.
# ray.put doesn't work is that fallback allocation uses mmaped file
# that doesn't neccssary allocate disk spaces.
with create_tmp_file(250 * 1024 * 1024):
assert get_current_usage() > local_fs_capacity_threshold
time.sleep(1)
@ray.remote
def foo():
ref = ray.put(np.random.rand(20 * 1024 * 1024)) # 160 MB data
return ref
try:
ray.get(foo.remote())
except ray.exceptions.RayTaskError as e:
assert isinstance(e.cause, ray.exceptions.OutOfDiskError)
# Give it some time for events to appear.
# TODO(core-team): provide some way to wait for events to be flushed.
time.sleep(2)
events = list_cluster_events()
print(events)
# There could be more than 1 event depending on the test timing.
assert len(events) >= 1
assert (
"Object creation will fail if spilling is required" in events[0]["message"]
) # noqa
assert events[0]["severity"] == "ERROR"
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))