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

259 lines
7.6 KiB
Python

import os
import subprocess
import sys
from unittest.mock import patch
import pytest
import ray
from ray._common.test_utils import Semaphore, wait_for_condition
from ray._private.test_utils import (
client_test_enabled,
get_gcs_memory_used,
)
from ray.experimental.internal_kv import _internal_kv_list
@pytest.fixture
def shutdown_only_with_initialization_check():
yield None
# The code after the yield will run as teardown code.
ray.shutdown()
assert not ray.is_initialized()
def test_initialized(shutdown_only_with_initialization_check):
assert not ray.is_initialized()
ray.init(num_cpus=0)
assert ray.is_initialized()
def test_ray_start_and_stop():
for i in range(10):
subprocess.check_call(["ray", "start", "--head"])
subprocess.check_call(["ray", "stop"])
def test_ray_memory(shutdown_only):
ray.init(num_cpus=1)
subprocess.check_call(["ray", "memory"])
def test_jemalloc_env_var_propagate():
"""Test `propagate_jemalloc_env_var`"""
gcs_ptype = ray._private.ray_constants.PROCESS_TYPE_GCS_SERVER
"""
If the shared library path is not specified,
it should return an empty dict.
"""
expected = {}
actual = ray._private.services.propagate_jemalloc_env_var(
jemalloc_path="", jemalloc_conf="", jemalloc_comps=[], process_type=gcs_ptype
)
assert actual == expected
actual = ray._private.services.propagate_jemalloc_env_var(
jemalloc_path=None,
jemalloc_conf="a,b,c",
jemalloc_comps=[ray._private.ray_constants.PROCESS_TYPE_GCS_SERVER],
process_type=gcs_ptype,
)
assert actual == expected
"""
When the shared library is specified
"""
library_path = "/abc"
expected = {"LD_PRELOAD": library_path, "RAY_LD_PRELOAD_ON_WORKERS": "0"}
actual = ray._private.services.propagate_jemalloc_env_var(
jemalloc_path=library_path,
jemalloc_conf="",
jemalloc_comps=[ray._private.ray_constants.PROCESS_TYPE_GCS_SERVER],
process_type=gcs_ptype,
)
assert actual == expected
# comps should be a list type.
with pytest.raises(AssertionError):
ray._private.services.propagate_jemalloc_env_var(
jemalloc_path=library_path,
jemalloc_conf="",
jemalloc_comps="ray._private.ray_constants.PROCESS_TYPE_GCS_SERVER,",
process_type=gcs_ptype,
)
# When comps don't match the process_type, it should not contain MALLOC_CONF.
actual = ray._private.services.propagate_jemalloc_env_var(
jemalloc_path=library_path,
jemalloc_conf="",
jemalloc_comps=[ray._private.ray_constants.PROCESS_TYPE_RAYLET],
process_type=gcs_ptype,
)
assert "MALLOC_CONF" not in actual
"""
When the malloc config is specified
"""
library_path = "/abc"
malloc_conf = "a,b,c"
expected = {
"LD_PRELOAD": library_path,
"MALLOC_CONF": malloc_conf,
"RAY_LD_PRELOAD_ON_WORKERS": "0",
}
actual = ray._private.services.propagate_jemalloc_env_var(
jemalloc_path=library_path,
jemalloc_conf=malloc_conf,
jemalloc_comps=[ray._private.ray_constants.PROCESS_TYPE_GCS_SERVER],
process_type=gcs_ptype,
)
assert actual == expected
@patch.dict(os.environ, {"RAY_LD_PRELOAD_ON_WORKERS": "1"})
def test_enable_jemallc_for_workers():
library_path = "/abc"
malloc_conf = "a,b,c"
expected = {
"LD_PRELOAD": library_path,
"MALLOC_CONF": malloc_conf,
"RAY_LD_PRELOAD_ON_WORKERS": "1",
}
actual = ray._private.services.propagate_jemalloc_env_var(
jemalloc_path=library_path,
jemalloc_conf=malloc_conf,
jemalloc_comps=[ray._private.ray_constants.PROCESS_TYPE_WORKER],
process_type=ray._private.ray_constants.PROCESS_TYPE_WORKER,
)
assert actual == expected
def test_back_pressure(shutdown_only_with_initialization_check):
ray.init()
signal_actor = Semaphore.options(max_pending_calls=10).remote(value=0)
try:
for i in range(10):
signal_actor.acquire.remote()
except ray.exceptions.PendingCallsLimitExceeded:
assert False
with pytest.raises(ray.exceptions.PendingCallsLimitExceeded):
signal_actor.acquire.remote()
@ray.remote
def release(signal_actor):
ray.get(signal_actor.release.remote())
return 1
# Release signal actor through common task,
# because actor tasks will be back pressured
for i in range(10):
ray.get(release.remote(signal_actor))
# Check whether we can call remote actor normally after
# back presssure released.
try:
signal_actor.acquire.remote()
except ray.exceptions.PendingCallsLimitExceeded:
assert False
ray.shutdown()
def function_entry_num(job_id):
from ray._private.ray_constants import KV_NAMESPACE_FUNCTION_TABLE
return (
len(
_internal_kv_list(
b"RemoteFunction:" + job_id, namespace=KV_NAMESPACE_FUNCTION_TABLE
)
)
+ len(
_internal_kv_list(
b"ActorClass:" + job_id, namespace=KV_NAMESPACE_FUNCTION_TABLE
)
)
+ len(
_internal_kv_list(
b"FunctionsToRun:" + job_id, namespace=KV_NAMESPACE_FUNCTION_TABLE
)
)
)
@pytest.mark.skipif(
client_test_enabled(), reason="client api doesn't support namespace right now."
)
def test_function_table_gc(call_ray_start):
"""This test tries to verify that function table is cleaned up
after job exits.
"""
def f():
data = "0" * 1024 * 1024 # 1MB
@ray.remote
def r():
nonlocal data
@ray.remote
class Actor:
pass
return r.remote()
ray.init(address="auto", namespace="b")
# It should use > 500MB data
ray.get([f() for _ in range(500)])
# It's not working on win32.
if sys.platform != "win32":
assert get_gcs_memory_used() > 500 * 1024 * 1024
job_id = ray._private.worker.global_worker.current_job_id.hex().encode()
assert function_entry_num(job_id) > 0
ray.shutdown()
# now check the function table is cleaned up after job finished
ray.init(address="auto", namespace="a")
wait_for_condition(lambda: function_entry_num(job_id) == 0, timeout=30)
@pytest.mark.skipif(
client_test_enabled(), reason="client api doesn't support namespace right now."
)
def test_function_table_gc_actor(call_ray_start):
"""If there is a detached actor, the table won't be cleaned up."""
ray.init(address="auto", namespace="a")
@ray.remote
class Actor:
def ready(self):
return
# If there is a detached actor, the function won't be deleted.
a = Actor.options(lifetime="detached", name="a").remote()
ray.get(a.ready.remote())
job_id = ray._private.worker.global_worker.current_job_id.hex().encode()
ray.shutdown()
ray.init(address="auto", namespace="b")
with pytest.raises(Exception):
wait_for_condition(lambda: function_entry_num(job_id) == 0)
a = ray.get_actor("a", namespace="a")
ray.kill(a)
wait_for_condition(lambda: function_entry_num(job_id) == 0)
# If there is not a detached actor, it'll be deleted when the job finishes.
a = Actor.remote()
ray.get(a.ready.remote())
job_id = ray._private.worker.global_worker.current_job_id.hex().encode()
ray.shutdown()
ray.init(address="auto", namespace="c")
wait_for_condition(lambda: function_entry_num(job_id) == 0)
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))