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

279 lines
6.3 KiB
Python

import asyncio
import sys
import time
from unittest.mock import MagicMock
import pytest
import ray
from ray.util import ActorPool
@pytest.fixture
def init():
ray.init(num_cpus=4)
yield
ray.shutdown()
def test_get_next(init):
@ray.remote
class MyActor:
def __init__(self):
pass
def f(self, x):
return x + 1
def double(self, x):
return 2 * x
actors = [MyActor.remote() for _ in range(4)]
pool = ActorPool(actors)
for i in range(5):
pool.submit(lambda a, v: a.f.remote(v), i)
assert pool.get_next() == i + 1
def test_get_next_unordered(init):
@ray.remote
class MyActor:
def __init__(self):
pass
def f(self, x):
return x + 1
def double(self, x):
return 2 * x
actors = [MyActor.remote() for _ in range(4)]
pool = ActorPool(actors)
total = []
for i in range(5):
pool.submit(lambda a, v: a.f.remote(v), i)
while pool.has_next():
total += [pool.get_next_unordered()]
assert all(elem in [1, 2, 3, 4, 5] for elem in total)
def test_map(init):
@ray.remote
class MyActor:
def __init__(self):
pass
def f(self, x):
return x + 1
def double(self, x):
return 2 * x
actors = [MyActor.remote() for _ in range(4)]
pool = ActorPool(actors)
index = 0
for v in pool.map(lambda a, v: a.double.remote(v), range(5)):
assert v == 2 * index
index += 1
def test_map_eager(init):
"""Verify that submit is called eagerly when map is called.
If the results are directly yielded, then the submit calls are not
executed until the results are consumed.
"""
@ray.remote
class MyActor:
def f(self, x):
pass
actor = MyActor.remote()
pool = ActorPool([actor])
pool.submit = MagicMock()
pool.map(lambda a, v: a.f.remote(v), range(1))
pool.submit.assert_called()
def test_map_unordered(init):
@ray.remote
class MyActor:
def __init__(self):
pass
def f(self, x):
return x + 1
def double(self, x):
return 2 * x
actors = [MyActor.remote() for _ in range(4)]
pool = ActorPool(actors)
total = []
for v in pool.map(lambda a, v: a.double.remote(v), range(5)):
total += [v]
assert all(elem in [0, 2, 4, 6, 8] for elem in total)
def test_map_gh23107(init):
sleep_time = 40
# Reference - https://github.com/ray-project/ray/issues/23107
@ray.remote
class DummyActor:
async def identity(self, s):
if s == 6:
await asyncio.sleep(sleep_time)
return s, time.time()
def func(a, v):
return a.identity.remote(v)
map_values = [1, 2, 3, 4, 5]
pool_map = ActorPool([DummyActor.remote() for i in range(2)])
pool_map.submit(func, 6)
start_time = time.time()
gen = pool_map.map(func, map_values)
assert all(elem[0] in [1, 2, 3, 4, 5] for elem in list(gen))
assert all(
abs(elem[1] - start_time) < sleep_time in [1, 2, 3, 4, 5] for elem in list(gen)
)
pool_map_unordered = ActorPool([DummyActor.remote() for i in range(2)])
pool_map_unordered.submit(func, 6)
start_time = time.time()
gen = pool_map_unordered.map_unordered(func, map_values)
assert all(elem[0] in [1, 2, 3, 4, 5] for elem in list(gen))
assert all(
abs(elem[1] - start_time) < sleep_time in [1, 2, 3, 4, 5] for elem in list(gen)
)
def test_get_next_timeout(init):
@ray.remote
class MyActor:
def __init__(self):
pass
def f(self, x):
while True:
x = x + 1
time.sleep(1)
return None
def double(self, x):
return 2 * x
actors = [MyActor.remote() for _ in range(4)]
pool = ActorPool(actors)
pool.submit(lambda a, v: a.f.remote(v), 0)
with pytest.raises(TimeoutError):
pool.get_next_unordered(timeout=0.1)
def test_get_next_unordered_timeout(init):
@ray.remote
class MyActor:
def __init__(self):
pass
def f(self, x):
while True:
x + 1
time.sleep(1)
return
def double(self, x):
return 2 * x
actors = [MyActor.remote() for _ in range(4)]
pool = ActorPool(actors)
pool.submit(lambda a, v: a.f.remote(v), 0)
with pytest.raises(TimeoutError):
pool.get_next_unordered(timeout=0.1)
def test_multiple_returns(init):
@ray.remote
class Foo(object):
@ray.method(num_returns=2)
def bar(self):
return 1, 2
pool = ActorPool([Foo.remote() for _ in range(2)])
for _ in range(4):
pool.submit(lambda a, v: a.bar.remote(), None)
while pool.has_next():
assert pool.get_next(timeout=None) == [1, 2]
def test_pop_idle(init):
@ray.remote
class MyActor:
def __init__(self):
pass
def f(self, x):
return x + 1
def double(self, x):
return 2 * x
actors = [MyActor.remote()]
pool = ActorPool(actors)
pool.submit(lambda a, v: a.double.remote(v), 1)
assert pool.pop_idle() is None
assert pool.has_free() is False # actor is busy
assert pool.get_next() == 2
assert pool.has_free()
pool.pop_idle() # removes actor from pool
assert pool.has_free() is False # no more actors in pool
def test_push(init):
@ray.remote
class MyActor:
def __init__(self):
pass
def f(self, x):
return x + 1
def double(self, x):
return 2 * x
a1, a2, a3 = MyActor.remote(), MyActor.remote(), MyActor.remote()
pool = ActorPool([a1])
pool.submit(lambda a, v: a.double.remote(v), 1)
assert pool.has_free() is False # actor is busy
with pytest.raises(ValueError):
pool.push(a1)
pool.push(a2)
assert pool.has_free() # a2 is available
pool.submit(lambda a, v: a.double.remote(v), 1)
pool.submit(lambda a, v: a.double.remote(v), 1)
assert pool.has_free() is False
assert len(pool._pending_submits) == 1
pool.push(a3)
assert pool.has_free() is False # a3 is used for pending submit
assert len(pool._pending_submits) == 0
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))