481 lines
13 KiB
Python
481 lines
13 KiB
Python
# coding: utf-8
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
import ray._private.profiling as profiling
|
|
import ray.cluster_utils
|
|
from ray._common.test_utils import wait_for_condition
|
|
from ray._private.internal_api import (
|
|
get_local_ongoing_lineage_reconstruction_tasks,
|
|
memory_summary,
|
|
)
|
|
from ray._private.test_utils import (
|
|
client_test_enabled,
|
|
)
|
|
from ray.core.generated import common_pb2
|
|
from ray.exceptions import ObjectFreedError
|
|
|
|
if client_test_enabled():
|
|
from ray.util.client import ray
|
|
else:
|
|
import ray
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# issue https://github.com/ray-project/ray/issues/7105
|
|
@pytest.mark.skipif(client_test_enabled(), reason="internal api")
|
|
def test_internal_free(shutdown_only):
|
|
ray.init(num_cpus=1)
|
|
|
|
@ray.remote
|
|
class Sampler:
|
|
def sample(self):
|
|
return [1, 2, 3, 4, 5]
|
|
|
|
def sample_big(self):
|
|
return np.zeros(1024 * 1024)
|
|
|
|
sampler = Sampler.remote()
|
|
|
|
# Free deletes from in-memory store.
|
|
obj_ref = sampler.sample.remote()
|
|
ray.get(obj_ref)
|
|
ray._private.internal_api.free(obj_ref)
|
|
with pytest.raises(ObjectFreedError):
|
|
ray.get(obj_ref)
|
|
|
|
# Free deletes big objects from plasma store.
|
|
big_id = sampler.sample_big.remote()
|
|
ray.get(big_id)
|
|
ray._private.internal_api.free(big_id)
|
|
with pytest.raises(ObjectFreedError):
|
|
ray.get(big_id)
|
|
|
|
|
|
@pytest.mark.skipif(client_test_enabled(), reason="internal api")
|
|
def test_internal_free_non_owned(shutdown_only):
|
|
info = ray.init(num_cpus=1)
|
|
|
|
@ray.remote
|
|
def gen_data():
|
|
return ray.put(np.zeros(1024 * 1024))
|
|
|
|
@ray.remote
|
|
def do_free(ref_list):
|
|
ray._private.internal_api.free(ref_list, local_only=False)
|
|
for ref in ref_list:
|
|
with pytest.raises(ObjectFreedError):
|
|
ray.get(ref)
|
|
|
|
# Can free locally owned objects from remote worker.
|
|
ref_1 = ray.put(np.zeros(1024 * 1024))
|
|
ref_2 = ray.put(np.zeros(1024 * 1024))
|
|
ray.get(do_free.remote([ref_1, ref_2]))
|
|
|
|
# Can free remotely owned objects from local worker.
|
|
ref_3 = ray.get(gen_data.remote())
|
|
ref_4 = ray.get(gen_data.remote())
|
|
ray._private.internal_api.free([ref_3, ref_4], local_only=False)
|
|
for ref in [ref_3, ref_4]:
|
|
with pytest.raises(ObjectFreedError):
|
|
ray.get(ref)
|
|
|
|
# Memory was really freed.
|
|
info = memory_summary(info.address_info["address"])
|
|
assert "Plasma memory usage 0 MiB, 0 objects" in info, info
|
|
|
|
|
|
@pytest.mark.skipif(client_test_enabled(), reason="internal api")
|
|
def test_internal_free_edge_case(shutdown_only):
|
|
ray.init(
|
|
num_cpus=1,
|
|
_system_config={
|
|
"fetch_fail_timeout_milliseconds": 200,
|
|
},
|
|
)
|
|
|
|
@ray.remote
|
|
def gen():
|
|
return ray.put(np.ones(1024 * 1024 * 100))
|
|
|
|
@ray.remote
|
|
def free(x):
|
|
ray._private.internal_api.free(x[0], local_only=False)
|
|
|
|
x = ray.get(gen.remote())
|
|
ray.get(x)
|
|
ray.get(free.remote([x]))
|
|
|
|
# This currently hangs, since as a borrower we never subscribe for
|
|
# object deletion events. Check that we at least hit the fetch timeout.
|
|
with pytest.raises(ray.exceptions.ObjectFetchTimedOutError):
|
|
ray.get(x)
|
|
|
|
|
|
@pytest.mark.skipif(client_test_enabled(), reason="internal api")
|
|
def test_internal_get_local_ongoing_lineage_reconstruction_tasks(
|
|
ray_start_cluster_enabled,
|
|
):
|
|
cluster = ray_start_cluster_enabled
|
|
cluster.add_node(resources={"head": 2})
|
|
ray.init(address=cluster.address)
|
|
worker1 = cluster.add_node(resources={"worker": 2})
|
|
|
|
@ray.remote(num_cpus=0, resources={"head": 1})
|
|
class Counter:
|
|
def __init__(self):
|
|
self.count = 0
|
|
|
|
def inc(self):
|
|
self.count = self.count + 1
|
|
return self.count
|
|
|
|
@ray.remote(
|
|
max_retries=-1, num_cpus=0, resources={"worker": 1}, _labels={"key1": "value1"}
|
|
)
|
|
def task(counter):
|
|
count = ray.get(counter.inc.remote())
|
|
if count > 1:
|
|
# lineage reconstruction
|
|
time.sleep(100000)
|
|
return [1] * 1024 * 1024
|
|
|
|
@ray.remote(
|
|
max_restarts=-1,
|
|
max_task_retries=-1,
|
|
num_cpus=0,
|
|
resources={"worker": 1},
|
|
_labels={"key2": "value2"},
|
|
)
|
|
class Actor:
|
|
def run(self, counter):
|
|
count = ray.get(counter.inc.remote())
|
|
if count > 1:
|
|
# lineage reconstruction
|
|
time.sleep(100000)
|
|
return [1] * 1024 * 1024
|
|
|
|
counter1 = Counter.remote()
|
|
obj1 = task.remote(counter1)
|
|
# Wait for task to finish
|
|
ray.wait([obj1], fetch_local=False)
|
|
|
|
counter2 = Counter.remote()
|
|
actor = Actor.remote()
|
|
obj2 = actor.run.remote(counter2)
|
|
# Wait for actor task to finish
|
|
ray.wait([obj2], fetch_local=False)
|
|
|
|
assert len(get_local_ongoing_lineage_reconstruction_tasks()) == 0
|
|
|
|
# Trigger lineage reconstruction of obj
|
|
cluster.remove_node(worker1)
|
|
|
|
def verify(expected_task_status):
|
|
lineage_reconstruction_tasks = get_local_ongoing_lineage_reconstruction_tasks()
|
|
lineage_reconstruction_tasks.sort(key=lambda task: task[0].name)
|
|
assert len(lineage_reconstruction_tasks) == 2
|
|
assert [
|
|
lineage_reconstruction_tasks[0][0].name,
|
|
lineage_reconstruction_tasks[1][0].name,
|
|
] == ["Actor.run", "task"]
|
|
assert (
|
|
lineage_reconstruction_tasks[0][0].labels == {"key2": "value2"}
|
|
and lineage_reconstruction_tasks[0][0].status == expected_task_status
|
|
and lineage_reconstruction_tasks[0][1] == 1
|
|
)
|
|
assert (
|
|
lineage_reconstruction_tasks[1][0].labels == {"key1": "value1"}
|
|
and lineage_reconstruction_tasks[1][0].status == expected_task_status
|
|
and lineage_reconstruction_tasks[1][1] == 1
|
|
)
|
|
|
|
return True
|
|
|
|
wait_for_condition(
|
|
lambda: verify(common_pb2.TaskStatus.PENDING_NODE_ASSIGNMENT),
|
|
timeout=30,
|
|
retry_interval_ms=1000,
|
|
)
|
|
cluster.add_node(resources={"worker": 2})
|
|
wait_for_condition(
|
|
lambda: verify(common_pb2.TaskStatus.SUBMITTED_TO_WORKER),
|
|
timeout=30,
|
|
retry_interval_ms=1000,
|
|
)
|
|
|
|
|
|
def test_multiple_waits_and_gets(shutdown_only):
|
|
# It is important to use three workers here, so that the three tasks
|
|
# launched in this experiment can run at the same time.
|
|
ray.init(num_cpus=3)
|
|
|
|
@ray.remote
|
|
def f():
|
|
return 1
|
|
|
|
@ray.remote
|
|
def g(input_list):
|
|
# The argument input_list should be a list containing one object ref.
|
|
ray.wait([input_list[0]])
|
|
|
|
@ray.remote
|
|
def h(input_list):
|
|
# The argument input_list should be a list containing one object ref.
|
|
ray.get(input_list[0])
|
|
|
|
# Make sure that multiple wait requests involving the same object ref
|
|
# all return.
|
|
x = f.remote()
|
|
ray.get([g.remote([x]), g.remote([x])])
|
|
|
|
# Make sure that multiple get requests involving the same object ref all
|
|
# return.
|
|
x = f.remote()
|
|
ray.get([h.remote([x]), h.remote([x])])
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
"RAY_PROFILING" not in os.environ, reason="Only tested in client/profiling build."
|
|
)
|
|
@pytest.mark.skipif(
|
|
client_test_enabled(),
|
|
reason=(
|
|
"wait_for_function will miss in this mode. To be fixed after using"
|
|
" gcs to bootstrap all component."
|
|
),
|
|
)
|
|
def test_profiling_api(shutdown_only):
|
|
|
|
ray.init(
|
|
num_cpus=2,
|
|
_system_config={
|
|
"task_events_report_interval_ms": 200,
|
|
"enable_timeline": True,
|
|
},
|
|
)
|
|
|
|
@ray.remote
|
|
def f(delay):
|
|
with profiling.profile("custom_event", extra_data={"name": "custom name"}):
|
|
time.sleep(delay)
|
|
pass
|
|
|
|
@ray.remote
|
|
def g(input_list):
|
|
# The argument input_list should be a list containing one object ref.
|
|
ray.wait([input_list[0]])
|
|
|
|
ray.put(1)
|
|
x = f.remote(1)
|
|
ray.get([g.remote([x]), g.remote([x])])
|
|
|
|
def verify():
|
|
profile_data = ray.timeline()
|
|
actual_types = {event["cat"] for event in profile_data}
|
|
expected_types = {
|
|
"task::f", # for f
|
|
"task::g", # for g
|
|
"task:deserialize_arguments",
|
|
"task:execute",
|
|
"task:store_outputs",
|
|
"wait_for_function",
|
|
"ray.get",
|
|
"ray.put",
|
|
"ray.wait",
|
|
"submit_task",
|
|
"fetch_and_run_function",
|
|
"custom_event", # This is the custom one from ray.profile.
|
|
}
|
|
assert expected_types == actual_types
|
|
return True
|
|
|
|
wait_for_condition(verify, timeout=20, retry_interval_ms=1000)
|
|
|
|
# Test for content of the profiling events.
|
|
@ray.remote
|
|
def k():
|
|
exec_time_us = time.time() * (10**6)
|
|
worker_id = ray._private.worker.global_worker.core_worker.get_worker_id().hex()
|
|
return worker_id, exec_time_us
|
|
|
|
k_worker_id, k_exec_time_us = ray.get(k.remote())
|
|
|
|
def verify():
|
|
profile_data = ray.timeline()
|
|
k_events = [
|
|
event for event in profile_data if event["tid"] == f"worker:{k_worker_id}"
|
|
]
|
|
assert len(k_events) > 0
|
|
for event in k_events:
|
|
if event["name"] == "task:execute":
|
|
reported_exec_time = event["ts"]
|
|
# diff smaller than 3 secs, a fine-tuned threshold from running locally.
|
|
assert abs(reported_exec_time - k_exec_time_us) < 3 * (10**6)
|
|
|
|
return True
|
|
|
|
wait_for_condition(verify, timeout=20, retry_interval_ms=1000)
|
|
|
|
|
|
def test_wait_cluster(ray_start_cluster_enabled):
|
|
cluster = ray_start_cluster_enabled
|
|
cluster.add_node(num_cpus=1, resources={"RemoteResource": 1})
|
|
cluster.add_node(num_cpus=1, resources={"RemoteResource": 1})
|
|
ray.init(address=cluster.address)
|
|
|
|
@ray.remote(resources={"RemoteResource": 1})
|
|
def f():
|
|
return
|
|
|
|
# Submit some more tasks that can only be executed on the remote nodes.
|
|
tasks = [f.remote() for _ in range(10)]
|
|
# Wait for all tasks to finish.
|
|
_, _ = ray.wait(tasks, num_returns=len(tasks), fetch_local=False)
|
|
# Make sure a wait with 0 timeout works.
|
|
_, unready = ray.wait(tasks, num_returns=len(tasks), timeout=0)
|
|
# All remote tasks should have finished.
|
|
assert len(unready) == 0
|
|
|
|
|
|
@pytest.mark.skip(reason="TODO(ekl)")
|
|
def test_object_transfer_dump(ray_start_cluster_enabled):
|
|
cluster = ray_start_cluster_enabled
|
|
|
|
num_nodes = 3
|
|
for i in range(num_nodes):
|
|
cluster.add_node(resources={str(i): 1}, object_store_memory=10**9)
|
|
ray.init(address=cluster.address)
|
|
|
|
@ray.remote
|
|
def f(x):
|
|
return
|
|
|
|
# These objects will live on different nodes.
|
|
object_refs = [f._remote(args=[1], resources={str(i): 1}) for i in range(num_nodes)]
|
|
|
|
# Broadcast each object from each machine to each other machine.
|
|
for object_ref in object_refs:
|
|
ray.get(
|
|
[
|
|
f._remote(args=[object_ref], resources={str(i): 1})
|
|
for i in range(num_nodes)
|
|
]
|
|
)
|
|
|
|
# The profiling information only flushes once every second.
|
|
time.sleep(1.1)
|
|
|
|
transfer_dump = ray._private.state.object_transfer_timeline()
|
|
# Make sure the transfer dump can be serialized with JSON.
|
|
json.loads(json.dumps(transfer_dump))
|
|
assert len(transfer_dump) >= num_nodes**2
|
|
assert (
|
|
len(
|
|
{
|
|
event["pid"]
|
|
for event in transfer_dump
|
|
if event["name"] == "transfer_receive"
|
|
}
|
|
)
|
|
== num_nodes
|
|
)
|
|
assert (
|
|
len(
|
|
{
|
|
event["pid"]
|
|
for event in transfer_dump
|
|
if event["name"] == "transfer_send"
|
|
}
|
|
)
|
|
== num_nodes
|
|
)
|
|
|
|
|
|
def test_identical_function_names(ray_start_regular):
|
|
# Define a bunch of remote functions and make sure that we don't
|
|
# accidentally call an older version.
|
|
|
|
num_calls = 200
|
|
|
|
@ray.remote
|
|
def f():
|
|
return 1
|
|
|
|
results1 = [f.remote() for _ in range(num_calls)]
|
|
|
|
@ray.remote
|
|
def f():
|
|
return 2
|
|
|
|
results2 = [f.remote() for _ in range(num_calls)]
|
|
|
|
@ray.remote
|
|
def f():
|
|
return 3
|
|
|
|
results3 = [f.remote() for _ in range(num_calls)]
|
|
|
|
@ray.remote
|
|
def f():
|
|
return 4
|
|
|
|
results4 = [f.remote() for _ in range(num_calls)]
|
|
|
|
@ray.remote
|
|
def f():
|
|
return 5
|
|
|
|
results5 = [f.remote() for _ in range(num_calls)]
|
|
|
|
assert ray.get(results1) == num_calls * [1]
|
|
assert ray.get(results2) == num_calls * [2]
|
|
assert ray.get(results3) == num_calls * [3]
|
|
assert ray.get(results4) == num_calls * [4]
|
|
assert ray.get(results5) == num_calls * [5]
|
|
|
|
@ray.remote
|
|
def g():
|
|
return 1
|
|
|
|
@ray.remote # noqa: F811
|
|
def g(): # noqa: F811
|
|
return 2
|
|
|
|
@ray.remote # noqa: F811
|
|
def g(): # noqa: F811
|
|
return 3
|
|
|
|
@ray.remote # noqa: F811
|
|
def g(): # noqa: F811
|
|
return 4
|
|
|
|
@ray.remote # noqa: F811
|
|
def g(): # noqa: F811
|
|
return 5
|
|
|
|
result_values = ray.get([g.remote() for _ in range(num_calls)])
|
|
assert result_values == num_calls * [5]
|
|
|
|
|
|
def test_illegal_api_calls(ray_start_regular):
|
|
|
|
# Verify that we cannot call put on an ObjectRef.
|
|
x = ray.put(1)
|
|
with pytest.raises(Exception):
|
|
ray.put(x)
|
|
# Verify that we cannot call get on a regular value.
|
|
with pytest.raises(Exception):
|
|
ray.get(3)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main(["-sv", __file__]))
|