chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,416 @@
|
||||
import asyncio
|
||||
from typing import AsyncIterator, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from ray import serve
|
||||
from ray._common.test_utils import SignalActor, async_wait_for_condition
|
||||
from ray.serve._private.proxy_response_generator import ProxyResponseGenerator
|
||||
|
||||
|
||||
def disconnect_task_and_event() -> Tuple[asyncio.Event, asyncio.Task]:
|
||||
"""Return an event and a task waiting on it for testing disconnect logic."""
|
||||
event = asyncio.Event()
|
||||
|
||||
async def wait_for_event():
|
||||
return await event.wait()
|
||||
|
||||
return event, asyncio.ensure_future(wait_for_event())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestUnary:
|
||||
async def test_basic(self, serve_instance):
|
||||
@serve.deployment
|
||||
class D:
|
||||
def __call__(self, name: str) -> str:
|
||||
return f"Hello {name}!"
|
||||
|
||||
def error(self):
|
||||
raise RuntimeError("oopsies")
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=False,
|
||||
)
|
||||
gen = ProxyResponseGenerator(h.remote("Alice"))
|
||||
|
||||
# Test simple response.
|
||||
responses = [r async for r in gen]
|
||||
assert len(responses) == 1
|
||||
assert set(responses) == {"Hello Alice!"}
|
||||
|
||||
with pytest.raises(StopAsyncIteration):
|
||||
await gen.__anext__()
|
||||
|
||||
# Test response raising exception.
|
||||
gen = ProxyResponseGenerator(h.error.remote())
|
||||
with pytest.raises(RuntimeError, match="oopsies"):
|
||||
await gen.__anext__()
|
||||
|
||||
with pytest.raises(StopAsyncIteration):
|
||||
await gen.__anext__()
|
||||
|
||||
async def test_result_callback(self, serve_instance):
|
||||
@serve.deployment
|
||||
class D:
|
||||
def __call__(self, name: str) -> str:
|
||||
return f"Hello {name}!"
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=False,
|
||||
)
|
||||
|
||||
def result_callback(result: str) -> str:
|
||||
return f"Callback called on: {result}"
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote("Alice"), result_callback=result_callback)
|
||||
|
||||
responses = [r async for r in gen]
|
||||
assert len(responses) == 1
|
||||
assert set(responses) == {"Callback called on: Hello Alice!"}
|
||||
|
||||
async def test_timeout_while_assigning(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
|
||||
@serve.deployment(max_ongoing_requests=1)
|
||||
class D:
|
||||
async def __call__(self):
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=False,
|
||||
)
|
||||
h.remote()
|
||||
|
||||
async def one_waiter():
|
||||
return await signal_actor.cur_num_waiters.remote() == 1
|
||||
|
||||
await async_wait_for_condition(one_waiter)
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote(), timeout_s=0.1)
|
||||
|
||||
with pytest.raises(TimeoutError):
|
||||
await gen.__anext__()
|
||||
|
||||
assert gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
async def test_timeout_while_executing(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
|
||||
@serve.deployment
|
||||
class D:
|
||||
async def __call__(self):
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=False,
|
||||
)
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote(), timeout_s=0.1)
|
||||
|
||||
with pytest.raises(TimeoutError):
|
||||
await gen.__anext__()
|
||||
|
||||
assert gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
async def test_disconnect_while_assigning(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
disconnect_event, disconnect_task = disconnect_task_and_event()
|
||||
|
||||
@serve.deployment(max_ongoing_requests=1)
|
||||
class D:
|
||||
async def __call__(self):
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=False,
|
||||
)
|
||||
h.remote()
|
||||
|
||||
async def one_waiter():
|
||||
return await signal_actor.cur_num_waiters.remote() == 1
|
||||
|
||||
await async_wait_for_condition(one_waiter)
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote(), disconnected_task=disconnect_task)
|
||||
|
||||
async def get_next():
|
||||
return await gen.__anext__()
|
||||
|
||||
gen_next = asyncio.ensure_future(get_next())
|
||||
done, _ = await asyncio.wait([gen_next], timeout=0.1)
|
||||
assert len(done) == 0
|
||||
|
||||
# Set the disconnect event, causing the disconnect task to finish.
|
||||
disconnect_event.set()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await gen_next
|
||||
|
||||
assert gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
async def test_disconnect_while_executing(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
disconnect_event, disconnect_task = disconnect_task_and_event()
|
||||
|
||||
@serve.deployment
|
||||
class D:
|
||||
async def __call__(self):
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=False,
|
||||
)
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote(), disconnected_task=disconnect_task)
|
||||
|
||||
async def get_next():
|
||||
return await gen.__anext__()
|
||||
|
||||
gen_next = asyncio.ensure_future(get_next())
|
||||
done, _ = await asyncio.wait([gen_next], timeout=0.1)
|
||||
assert len(done) == 0
|
||||
|
||||
# Set the disconnect event, causing the disconnect task to finish.
|
||||
disconnect_event.set()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await gen_next
|
||||
|
||||
assert gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestStreaming:
|
||||
async def test_basic(self, serve_instance):
|
||||
@serve.deployment
|
||||
class D:
|
||||
def __call__(self, name: str) -> AsyncIterator[str]:
|
||||
for _ in range(5):
|
||||
yield f"Hello {name}!"
|
||||
|
||||
def error(self, name: str) -> AsyncIterator[str]:
|
||||
yield f"Hello {name}!"
|
||||
raise RuntimeError("oopsies")
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=True,
|
||||
)
|
||||
gen = ProxyResponseGenerator(h.remote("Alice"))
|
||||
|
||||
# Test simple response.
|
||||
responses = [r async for r in gen]
|
||||
assert len(responses) == 5
|
||||
assert set(responses) == {"Hello Alice!"}
|
||||
with pytest.raises(StopAsyncIteration):
|
||||
await gen.__anext__()
|
||||
|
||||
# Test exception in the middle of response stream.
|
||||
gen = ProxyResponseGenerator(h.error.remote("Alice"))
|
||||
assert await gen.__anext__() == "Hello Alice!"
|
||||
with pytest.raises(RuntimeError, match="oopsies"):
|
||||
await gen.__anext__()
|
||||
with pytest.raises(StopAsyncIteration):
|
||||
await gen.__anext__()
|
||||
|
||||
async def test_result_callback(self, serve_instance):
|
||||
@serve.deployment
|
||||
class D:
|
||||
def __call__(self, name: str) -> AsyncIterator[str]:
|
||||
for _ in range(5):
|
||||
yield f"Hello {name}!"
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=True,
|
||||
)
|
||||
|
||||
def result_callback(result: str) -> str:
|
||||
return f"Callback called on: {result}"
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote("Alice"), result_callback=result_callback)
|
||||
|
||||
responses = [r async for r in gen]
|
||||
assert len(responses) == 5
|
||||
assert set(responses) == {"Callback called on: Hello Alice!"}
|
||||
|
||||
async def test_timeout_while_assigning(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
|
||||
@serve.deployment(max_ongoing_requests=1)
|
||||
class D:
|
||||
async def __call__(self) -> AsyncIterator[str]:
|
||||
yield "hi"
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=True,
|
||||
)
|
||||
h.remote()
|
||||
|
||||
async def one_waiter():
|
||||
return await signal_actor.cur_num_waiters.remote() == 1
|
||||
|
||||
await async_wait_for_condition(one_waiter)
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote(), timeout_s=0.1)
|
||||
|
||||
with pytest.raises(TimeoutError):
|
||||
await gen.__anext__()
|
||||
|
||||
assert gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
async def test_timeout_while_executing(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
|
||||
@serve.deployment
|
||||
class D:
|
||||
async def __call__(self) -> AsyncIterator[str]:
|
||||
yield "hi"
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=True,
|
||||
)
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote(), timeout_s=0.1)
|
||||
assert (await gen.__anext__()) == "hi"
|
||||
with pytest.raises(TimeoutError):
|
||||
await gen.__anext__()
|
||||
|
||||
assert gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
async def test_disconnect_while_assigning(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
disconnect_event, disconnect_task = disconnect_task_and_event()
|
||||
|
||||
@serve.deployment(max_ongoing_requests=1)
|
||||
class D:
|
||||
async def __call__(self) -> AsyncIterator[str]:
|
||||
yield "hi"
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=True,
|
||||
)
|
||||
h.remote()
|
||||
|
||||
async def one_waiter():
|
||||
return await signal_actor.cur_num_waiters.remote() == 1
|
||||
|
||||
await async_wait_for_condition(one_waiter)
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote(), disconnected_task=disconnect_task)
|
||||
|
||||
async def get_next():
|
||||
return await gen.__anext__()
|
||||
|
||||
gen_next = asyncio.ensure_future(get_next())
|
||||
done, _ = await asyncio.wait([gen_next], timeout=0.1)
|
||||
assert len(done) == 0
|
||||
|
||||
# Set the disconnect event, causing the disconnect task to finish.
|
||||
disconnect_event.set()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await gen_next
|
||||
|
||||
assert gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
async def test_disconnect_while_executing(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
disconnect_event, disconnect_task = disconnect_task_and_event()
|
||||
|
||||
@serve.deployment
|
||||
class D:
|
||||
async def __call__(self) -> AsyncIterator[str]:
|
||||
yield "hi"
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=True,
|
||||
)
|
||||
|
||||
gen = ProxyResponseGenerator(h.remote(), disconnected_task=disconnect_task)
|
||||
assert (await gen.__anext__()) == "hi"
|
||||
|
||||
async def get_next():
|
||||
return await gen.__anext__()
|
||||
|
||||
gen_next = asyncio.ensure_future(get_next())
|
||||
done, _ = await asyncio.wait([gen_next], timeout=0.1)
|
||||
assert len(done) == 0
|
||||
|
||||
# Set the disconnect event, causing the disconnect task to finish.
|
||||
disconnect_event.set()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await gen_next
|
||||
|
||||
assert gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
async def test_stop_checking_for_disconnect(self, serve_instance):
|
||||
signal_actor = SignalActor.remote()
|
||||
disconnect_event, disconnect_task = disconnect_task_and_event()
|
||||
|
||||
@serve.deployment
|
||||
class D:
|
||||
async def __call__(self) -> AsyncIterator[str]:
|
||||
yield "hi"
|
||||
await signal_actor.wait.remote()
|
||||
|
||||
h = serve.run(D.bind()).options(
|
||||
stream=True,
|
||||
)
|
||||
|
||||
gen = ProxyResponseGenerator(
|
||||
h.remote(),
|
||||
disconnected_task=disconnect_task,
|
||||
)
|
||||
assert (await gen.__anext__()) == "hi"
|
||||
|
||||
gen.stop_checking_for_disconnect()
|
||||
|
||||
async def get_next():
|
||||
return await gen.__anext__()
|
||||
|
||||
gen_next = asyncio.ensure_future(get_next())
|
||||
done, _ = await asyncio.wait([gen_next], timeout=0.1)
|
||||
assert len(done) == 0
|
||||
|
||||
# Set the disconnect event, causing the disconnect task to finish.
|
||||
# However, because stop_checking_for_disconnect was called this
|
||||
# should still time out.
|
||||
disconnect_event.set()
|
||||
done, _ = await asyncio.wait([gen_next], timeout=0.1)
|
||||
assert len(done) == 0
|
||||
|
||||
assert not gen.cancelled()
|
||||
|
||||
assert await signal_actor.cur_num_waiters.remote() == 1
|
||||
await signal_actor.send.remote()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
sys.exit(pytest.main(["-v", "-s", __file__]))
|
||||
Reference in New Issue
Block a user