chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
import ray
|
||||
from ray import serve
|
||||
from ray._common.test_utils import SignalActor, wait_for_condition
|
||||
from ray.serve._private.common import (
|
||||
OBJ_REF_NOT_SUPPORTED_ERROR,
|
||||
DeploymentID,
|
||||
RequestMetadata,
|
||||
)
|
||||
from ray.serve._private.replica_result import (
|
||||
ActorReplicaResult,
|
||||
ReplicaResult,
|
||||
gRPCReplicaResult,
|
||||
)
|
||||
from ray.serve._private.request_router.replica_wrapper import ReplicaSelection
|
||||
from ray.serve.handle import DeploymentHandle
|
||||
from ray.serve.tests.conftest import * # noqa
|
||||
from ray.serve.tests.conftest import _shared_serve_instance # noqa
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"by_reference,expected_result",
|
||||
[(True, ActorReplicaResult), (False, gRPCReplicaResult)],
|
||||
)
|
||||
def test_init_by_reference(
|
||||
serve_instance, by_reference: bool, expected_result: ReplicaResult
|
||||
):
|
||||
@serve.deployment
|
||||
def f():
|
||||
return "hi"
|
||||
|
||||
h = serve.run(f.bind())
|
||||
|
||||
resp = h.options(_by_reference=by_reference).remote()
|
||||
assert resp.result() == "hi"
|
||||
assert isinstance(resp._replica_result, expected_result)
|
||||
|
||||
|
||||
@pytest.mark.timeout(60)
|
||||
async def test_by_reference_false_raises_error(serve_instance):
|
||||
signal = SignalActor.remote()
|
||||
|
||||
@serve.deployment
|
||||
async def f():
|
||||
await signal.wait.remote()
|
||||
return "hi"
|
||||
|
||||
h = serve.run(f.bind())
|
||||
with pytest.raises(
|
||||
RuntimeError, match=re.escape(OBJ_REF_NOT_SUPPORTED_ERROR.args[0])
|
||||
):
|
||||
response = h.options(_by_reference=False).remote()
|
||||
await response._to_object_ref()
|
||||
|
||||
await signal.send.remote()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("inner_by_reference", [True, False])
|
||||
@pytest.mark.parametrize("outer_by_reference", [True, False])
|
||||
def test_compose_deployments_in_app(
|
||||
serve_instance, inner_by_reference, outer_by_reference
|
||||
):
|
||||
"""Test composing deployment handle refs within a deployment."""
|
||||
|
||||
@serve.deployment
|
||||
class Downstream:
|
||||
def __init__(self, msg: str):
|
||||
self._msg = msg
|
||||
|
||||
def __call__(self, inp1: str, inp2: str):
|
||||
return f"{self._msg}|{inp1}|{inp2}"
|
||||
|
||||
@serve.deployment
|
||||
class Deployment:
|
||||
def __init__(self, handle1: DeploymentHandle, handle2: DeploymentHandle):
|
||||
self._handle1 = handle1.options(_by_reference=outer_by_reference)
|
||||
self._handle2 = handle2.options(_by_reference=inner_by_reference)
|
||||
|
||||
async def __call__(self):
|
||||
result = await self._handle1.remote(
|
||||
self._handle2.remote("hi1", inp2="hi2"),
|
||||
inp2=self._handle2.remote("hi3", inp2="hi4"),
|
||||
)
|
||||
return f"driver|{result}"
|
||||
|
||||
handle = serve.run(
|
||||
Deployment.bind(
|
||||
Downstream.options(name="downstream1").bind("downstream1"),
|
||||
Downstream.options(name="downstream2").bind("downstream2"),
|
||||
),
|
||||
)
|
||||
assert (
|
||||
handle.remote().result()
|
||||
== "driver|downstream1|downstream2|hi1|hi2|downstream2|hi3|hi4"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("inner_by_reference", [True, False])
|
||||
@pytest.mark.parametrize("outer_by_reference", [True, False])
|
||||
def test_compose_apps(serve_instance, inner_by_reference, outer_by_reference):
|
||||
"""Test composing deployment handle refs outside of a deployment."""
|
||||
|
||||
@serve.deployment
|
||||
class Deployment:
|
||||
def __init__(self, msg: str):
|
||||
self._msg = msg
|
||||
|
||||
def __call__(self, inp1: str, inp2: str):
|
||||
return f"{self._msg}|{inp1}|{inp2}"
|
||||
|
||||
handle1 = serve.run(
|
||||
Deployment.bind("app1"), name="app1", route_prefix="/app1"
|
||||
).options(_by_reference=outer_by_reference)
|
||||
handle2 = serve.run(
|
||||
Deployment.bind("app2"), name="app2", route_prefix="/app2"
|
||||
).options(_by_reference=inner_by_reference)
|
||||
|
||||
assert (
|
||||
handle1.remote(
|
||||
handle2.remote("hi1", inp2="hi2"),
|
||||
inp2=handle2.remote("hi3", inp2="hi4"),
|
||||
).result()
|
||||
== "app1|app2|hi1|hi2|app2|hi3|hi4"
|
||||
)
|
||||
|
||||
|
||||
def test_dispatch_rejects_selection_from_different_deployment():
|
||||
handle = DeploymentHandle("deployment-a", "app")
|
||||
selection = ReplicaSelection(
|
||||
replica_id="replica-1",
|
||||
node_ip="127.0.0.1",
|
||||
port=None,
|
||||
node_id="node-1",
|
||||
availability_zone=None,
|
||||
replica_metadata={},
|
||||
_replica=object(),
|
||||
_deployment_id=DeploymentID(name="deployment-b", app_name="app"),
|
||||
_request_metadata=RequestMetadata(
|
||||
request_id="request-id",
|
||||
internal_request_id="internal-request-id",
|
||||
call_method="__call__",
|
||||
),
|
||||
_method_name="__call__",
|
||||
_slot_token="slot-1",
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="different deployment"):
|
||||
handle.dispatch(selection)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.timeout(30)
|
||||
async def test_non_grpc_exception_no_self_cause(serve_instance):
|
||||
"""Regression test for https://github.com/ray-project/ray/issues/62358."""
|
||||
|
||||
@ray.remote
|
||||
class _ExceptionChainDetector:
|
||||
def __init__(self):
|
||||
self._processed = False
|
||||
self._cycle_detected = False
|
||||
|
||||
def record_result(self, cycle_detected: bool):
|
||||
self._processed = True
|
||||
self._cycle_detected = cycle_detected
|
||||
|
||||
def processed(self):
|
||||
return self._processed
|
||||
|
||||
def cycle_detected(self):
|
||||
return self._cycle_detected
|
||||
|
||||
detector = _ExceptionChainDetector.remote()
|
||||
|
||||
class CauseWalker(logging.Handler):
|
||||
"""Walk the exception chain to catch self-referential causes."""
|
||||
|
||||
def __init__(self, detector_handle):
|
||||
super().__init__()
|
||||
self._detector = detector_handle
|
||||
|
||||
def emit(self, record):
|
||||
if not record.exc_info or record.exc_info[1] is None:
|
||||
return
|
||||
|
||||
exc = record.exc_info[1]
|
||||
if not isinstance(exc, ValueError) or str(exc) != "user code exception":
|
||||
return
|
||||
|
||||
cycle_detected = False
|
||||
try:
|
||||
seen = set()
|
||||
while exc.__cause__ is not None:
|
||||
if id(exc) in seen:
|
||||
cycle_detected = True
|
||||
return
|
||||
|
||||
seen.add(id(exc))
|
||||
exc = exc.__cause__
|
||||
finally:
|
||||
self._detector.record_result.remote(cycle_detected)
|
||||
|
||||
@serve.deployment
|
||||
class App:
|
||||
def __init__(self, detector_handle):
|
||||
logging.getLogger("ray.serve").addHandler(CauseWalker(detector_handle))
|
||||
|
||||
async def fail(self):
|
||||
raise ValueError("user code exception")
|
||||
|
||||
handle = serve.run(App.bind(detector))
|
||||
|
||||
with pytest.raises(ValueError, match="user code exception"):
|
||||
await handle.fail.remote()
|
||||
|
||||
wait_for_condition(lambda: ray.get(detector.processed.remote()), timeout=10)
|
||||
|
||||
assert not ray.get(detector.cycle_detected.remote()), (
|
||||
"Self-referential __cause__ chain detected: "
|
||||
"raise e from e was used on a non-gRPC request"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
sys.exit(pytest.main(["-v", "-s", __file__]))
|
||||
Reference in New Issue
Block a user