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

304 lines
7.5 KiB
Python

import asyncio
import sys
import time
from collections import Counter
import numpy as np
import pytest
import ray
from ray._raylet import ObjectRefGenerator
from ray.exceptions import TaskCancelledError
def test_threaded_actor_generator(shutdown_only):
ray.init()
@ray.remote(max_concurrency=10)
class Actor:
def f(self):
for i in range(30):
time.sleep(0.1)
yield np.ones(1024 * 1024) * i
@ray.remote(max_concurrency=20)
class AsyncActor:
async def f(self):
for i in range(30):
await asyncio.sleep(0.1)
yield np.ones(1024 * 1024) * i
async def main():
a = Actor.remote()
asy = AsyncActor.remote()
async def run():
i = 0
async for ref in a.f.remote():
val = ray.get(ref)
print(val)
print(ref)
assert np.array_equal(val, np.ones(1024 * 1024) * i)
i += 1
del ref
async def run2():
i = 0
async for ref in asy.f.remote():
val = await ref
print(ref)
print(val)
assert np.array_equal(val, np.ones(1024 * 1024) * i), ref
i += 1
del ref
coroutines = [run() for _ in range(10)]
coroutines = [run2() for _ in range(20)]
await asyncio.gather(*coroutines)
asyncio.run(main())
def test_generator_dist_gather(ray_start_cluster):
cluster = ray_start_cluster
cluster.add_node(num_cpus=0, object_store_memory=1 * 1024 * 1024 * 1024)
ray.init()
cluster.add_node(num_cpus=1)
cluster.add_node(num_cpus=1)
cluster.add_node(num_cpus=1)
cluster.add_node(num_cpus=1)
@ray.remote(num_cpus=1)
class Actor:
def __init__(self, child=None):
self.child = child
def get_data(self):
for _ in range(10):
time.sleep(0.1)
yield np.ones(5 * 1024 * 1024)
async def all_gather():
actor = Actor.remote()
async for ref in actor.get_data.remote():
val = await ref
assert np.array_equal(np.ones(5 * 1024 * 1024), val)
del ref
async def main():
await asyncio.gather(all_gather(), all_gather(), all_gather(), all_gather())
asyncio.run(main())
summary = ray._private.internal_api.memory_summary(stats_only=True)
print(summary)
def test_generator_wait(shutdown_only):
"""
Make sure the generator works with ray.wait.
"""
ray.init(num_cpus=8)
@ray.remote
def f(sleep_time):
for i in range(2):
time.sleep(sleep_time)
yield i
@ray.remote
def g(sleep_time):
time.sleep(sleep_time)
return 10
gen = f.remote(1)
"""
Test basic cases.
"""
for expected_rval in [0, 1]:
s = time.time()
r, ur = ray.wait([gen], num_returns=1)
print(time.time() - s)
assert len(r) == 1
assert ray.get(next(r[0])) == expected_rval
assert len(ur) == 0
# Should raise a stop iteration.
for _ in range(3):
s = time.time()
r, ur = ray.wait([gen], num_returns=1)
print(time.time() - s)
assert len(r) == 1
with pytest.raises(StopIteration):
assert next(r[0]) == 0
assert len(ur) == 0
gen = f.remote(0)
# Wait until the generator task finishes
ray.get(gen._generator_ref)
for i in range(2):
r, ur = ray.wait([gen], timeout=0)
assert len(r) == 1
assert len(ur) == 0
assert ray.get(next(r[0])) == i
"""
Test the case ref is mixed with regular object ref.
"""
gen = f.remote(0)
ref = g.remote(3)
ready, unready = [], [gen, ref]
result_set = set()
while unready:
ready, unready = ray.wait(unready)
print(ready, unready)
assert len(ready) == 1
for r in ready:
if isinstance(r, ObjectRefGenerator):
try:
ref = next(r)
print(ref)
print(ray.get(ref))
result_set.add(ray.get(ref))
except StopIteration:
pass
else:
unready.append(r)
else:
result_set.add(ray.get(r))
assert result_set == {0, 1, 10}
"""
Test timeout.
"""
gen = f.remote(3)
ref = g.remote(1)
ready, unready = ray.wait([gen, ref], timeout=2)
assert len(ready) == 1
assert len(unready) == 1
"""
Test num_returns
"""
gen = f.remote(1)
ref = g.remote(1)
ready, unready = ray.wait([ref, gen], num_returns=2)
assert len(ready) == 2
assert len(unready) == 0
@pytest.mark.parametrize("backpressure", [True, False])
def test_generator_wait_e2e(shutdown_only, backpressure):
ray.init(num_cpus=8)
if backpressure:
threshold = 1
else:
threshold = -1
@ray.remote
def f(sleep_time):
for i in range(2):
time.sleep(sleep_time)
yield i
@ray.remote
def g(sleep_time):
time.sleep(sleep_time)
return 10
gen = [
f.options(
_generator_backpressure_num_objects=threshold,
).remote(1)
for _ in range(4)
]
ref = [g.remote(2) for _ in range(4)]
ready, unready = [], [*gen, *ref]
result = []
start = time.time()
while unready:
ready, unready = ray.wait(unready, num_returns=len(unready), timeout=0.1)
for r in ready:
if isinstance(r, ObjectRefGenerator):
try:
ref = next(r)
result.append(ray.get(ref))
except StopIteration:
pass
else:
unready.append(r)
else:
result.append(ray.get(r))
elapsed = time.time() - start
assert elapsed < 4
assert 2 < elapsed
assert len(result) == 12
result = Counter(result)
assert result[0] == 4
assert result[1] == 4
assert result[10] == 4
def test_completed_next_ready_is_finished(shutdown_only):
@ray.remote
def f():
for _ in range(3):
time.sleep(1)
yield 1
gen = f.remote()
assert not gen.is_finished()
assert not gen.next_ready()
r, _ = ray.wait([gen])
gen = r[0]
assert gen.next_ready()
_, ur = ray.wait([gen.completed()], timeout=0)
assert len(ur) == 1
# Consume object refs
next(gen)
assert not gen.is_finished()
_, ur = ray.wait([gen.completed()], timeout=0)
assert len(ur) == 1
next(gen)
assert not gen.is_finished()
_, ur = ray.wait([gen.completed()], timeout=0)
assert len(ur) == 1
next(gen)
with pytest.raises(StopIteration):
next(gen)
assert gen.is_finished()
# Since the next should raise StopIteration,
# it should be False.
assert not gen.next_ready()
r, _ = ray.wait([gen.completed()], timeout=0)
assert len(r) == 1
# Test the failed case.
gen = f.remote()
next(gen)
ray.cancel(gen, force=True)
r, _ = ray.wait([gen])
assert len(r) == 1
# The last exception is not taken yet.
assert gen.next_ready()
assert not gen.is_finished()
with pytest.raises(TaskCancelledError):
ray.get(gen.completed())
with pytest.raises(TaskCancelledError):
ray.get(next(gen))
assert not gen.next_ready()
assert gen.is_finished()
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))