Files
2026-07-13 13:17:40 +08:00

436 lines
13 KiB
Python

# flake8: noqa
"""
Code examples for the asyncio best practices guide.
All examples are structured to be runnable and demonstrate key concepts.
"""
# __imports_begin__
from ray import serve
import asyncio
# __imports_end__
# __echo_async_begin__
@serve.deployment
class Echo:
async def __call__(self, request):
await asyncio.sleep(0.1)
return "ok"
# __echo_async_end__
# __blocking_echo_begin__
@serve.deployment
class BlockingEcho:
def __call__(self, request):
# Blocking.
import time
time.sleep(1)
return "ok"
# __blocking_echo_end__
# __fastapi_deployment_begin__
from fastapi import FastAPI
app = FastAPI()
@serve.deployment
@serve.ingress(app)
class FastAPIDeployment:
@app.get("/sync")
def sync_endpoint(self):
# FastAPI runs this in a threadpool.
import time
time.sleep(1)
return "ok"
@app.get("/async")
async def async_endpoint(self):
# Runs directly on FastAPI's asyncio loop.
await asyncio.sleep(1)
return "ok"
# __fastapi_deployment_end__
# __blocking_http_begin__
@serve.deployment
class BlockingHTTP:
async def __call__(self, request):
# ❌ This blocks the event loop until the HTTP call finishes.
import requests
resp = requests.get("https://example.com/")
return resp.text
# __blocking_http_end__
# __async_http_begin__
@serve.deployment
class AsyncHTTP:
async def __call__(self, request):
import httpx
async with httpx.AsyncClient() as client:
resp = await client.get("https://example.com/")
return resp.text
# __async_http_end__
# __threaded_http_begin__
@serve.deployment
class ThreadedHTTP:
async def __call__(self, request):
import requests
def fetch():
return requests.get("https://example.com/").text
# ✅ Offload blocking I/O to a worker thread.
return await asyncio.to_thread(fetch)
# __threaded_http_end__
# __threadpool_override_begin__
from concurrent.futures import ThreadPoolExecutor
@serve.deployment
class CustomThreadPool:
def __init__(self):
loop = asyncio.get_running_loop()
loop.set_default_executor(ThreadPoolExecutor(max_workers=16))
async def __call__(self, request):
return await asyncio.to_thread(lambda: "ok")
# __threadpool_override_end__
# __numpy_deployment_begin__
@serve.deployment
class NumpyDeployment:
def _heavy_numpy(self, array):
import numpy as np
# Many NumPy ops release the GIL while executing C/Fortran code.
return np.linalg.svd(array)[0]
async def __call__(self, request):
import numpy as np
# Create a sample array from request data
array = np.random.rand(100, 100)
# ✅ Multiple threads can run _heavy_numpy in parallel if
# the underlying implementation releases the GIL.
return await asyncio.to_thread(self._heavy_numpy, array)
# __numpy_deployment_end__
# __max_ongoing_requests_begin__
@serve.deployment(max_ongoing_requests=32)
class MyService:
async def __call__(self, request):
await asyncio.sleep(1)
return "ok"
# __max_ongoing_requests_end__
# __async_io_bound_begin__
@serve.deployment(max_ongoing_requests=100)
class AsyncIOBound:
async def __call__(self, request):
# Mostly waiting on an external system.
await asyncio.sleep(0.1)
return "ok"
# __async_io_bound_end__
# __blocking_cpu_begin__
@serve.deployment(max_ongoing_requests=100)
class BlockingCPU:
def __call__(self, request):
# ❌ Blocks the user event loop.
import time
time.sleep(1)
return "ok"
# __blocking_cpu_end__
# __cpu_with_threadpool_begin__
@serve.deployment(max_ongoing_requests=100)
class CPUWithThreadpool:
def __call__(self, request):
# With RAY_SERVE_RUN_SYNC_IN_THREADPOOL=1, each call runs in a thread.
import time
time.sleep(1)
return "ok"
# __cpu_with_threadpool_end__
# __batched_model_begin__
@serve.deployment(max_ongoing_requests=64)
class BatchedModel:
@serve.batch(max_batch_size=32)
async def __call__(self, requests):
# requests is a list of request objects.
inputs = [r for r in requests]
outputs = await self._run_model(inputs)
return outputs
async def _run_model(self, inputs):
# Placeholder model function
return [f"result_{i}" for i in inputs]
# __batched_model_end__
# __batched_model_offload_begin__
@serve.deployment(max_ongoing_requests=64)
class BatchedModelOffload:
@serve.batch(max_batch_size=32)
async def __call__(self, requests):
# requests is a list of request objects.
inputs = [r for r in requests]
outputs = await self._run_model(inputs)
return outputs
async def _run_model(self, inputs):
def run_sync():
# Heavy CPU or GIL-releasing native code here.
# Placeholder model function
return [f"result_{i}" for i in inputs]
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, run_sync)
# __batched_model_offload_end__
# __blocking_stream_begin__
@serve.deployment
class BlockingStream:
def __call__(self, request):
# ❌ Blocks the event loop between yields.
import time
for i in range(10):
time.sleep(1)
yield f"{i}\n"
# __blocking_stream_end__
# __async_stream_begin__
@serve.deployment
class AsyncStream:
async def __call__(self, request):
# ✅ Yields items without blocking the loop.
async def generator():
for i in range(10):
await asyncio.sleep(1)
yield f"{i}\n"
return generator()
# __async_stream_end__
# __offload_io_begin__
@serve.deployment
class OffloadIO:
async def __call__(self, request):
import requests
def fetch():
return requests.get("https://example.com/").text
# Offload to a thread, free the event loop.
body = await asyncio.to_thread(fetch)
return body
# __offload_io_end__
# __offload_cpu_begin__
@serve.deployment
class OffloadCPU:
def _compute(self, x):
# CPU-intensive work.
total = 0
for i in range(10_000_000):
total += (i * x) % 7
return total
async def __call__(self, request):
x = 123
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, self._compute, x)
return str(result)
# __offload_cpu_end__
# __ray_parallel_begin__
import ray
@ray.remote
def heavy_task(x):
# Heavy compute runs in its own worker process.
return x * x
@serve.deployment
class RayParallel:
async def __call__(self, request):
values = [1, 2, 3, 4]
refs = [heavy_task.remote(v) for v in values]
results = await asyncio.gather(*[r for r in refs])
return {"results": results}
# __ray_parallel_end__
if __name__ == "__main__":
import ray
# Initialize Ray if not already running
if not ray.is_initialized():
ray.init()
print("Testing Echo deployment...")
# Test Echo
echo_handle = serve.run(Echo.bind())
result = echo_handle.remote(None).result()
print(f"Echo result: {result}")
assert result == "ok"
print("\nTesting BlockingEcho deployment...")
# Test BlockingEcho
blocking_handle = serve.run(BlockingEcho.bind())
result = blocking_handle.remote(None).result()
print(f"BlockingEcho result: {result}")
assert result == "ok"
print("\nTesting MyService deployment...")
# Test MyService
service_handle = serve.run(MyService.bind())
result = service_handle.remote(None).result()
print(f"MyService result: {result}")
assert result == "ok"
print("\nTesting AsyncIOBound deployment...")
# Test AsyncIOBound
io_bound_handle = serve.run(AsyncIOBound.bind())
result = io_bound_handle.remote(None).result()
print(f"AsyncIOBound result: {result}")
assert result == "ok"
print("\nTesting AsyncStream deployment...")
# Test AsyncStream (just create it, don't fully consume)
stream_handle = serve.run(AsyncStream.bind())
print("AsyncStream deployment created successfully")
print("\nTesting OffloadCPU deployment...")
# Test OffloadCPU
cpu_handle = serve.run(OffloadCPU.bind())
result = cpu_handle.remote(None).result()
print(f"OffloadCPU result: {result}")
print("\nTesting NumpyDeployment...")
# Test NumpyDeployment
numpy_handle = serve.run(NumpyDeployment.bind())
result = numpy_handle.remote(None).result()
print(f"NumpyDeployment result shape: {result.shape}")
assert result.shape == (100, 100)
print("\nTesting BlockingCPU deployment...")
# Test BlockingCPU
blocking_cpu_handle = serve.run(BlockingCPU.bind())
result = blocking_cpu_handle.remote(None).result()
print(f"BlockingCPU result: {result}")
assert result == "ok"
print("\nTesting CPUWithThreadpool deployment...")
# Test CPUWithThreadpool
cpu_threadpool_handle = serve.run(CPUWithThreadpool.bind())
result = cpu_threadpool_handle.remote(None).result()
print(f"CPUWithThreadpool result: {result}")
assert result == "ok"
print("\nTesting CustomThreadPool deployment...")
custom_threadpool_handle = serve.run(CustomThreadPool.bind())
result = custom_threadpool_handle.remote(None).result()
print(f"CustomThreadPool result: {result}")
assert result == "ok"
print("\nTesting BlockingStream deployment...")
# Test BlockingStream - just verify it can be created and called
blocking_stream_handle = serve.run(BlockingStream.bind())
# For generator responses, we need to handle them differently
# Just verify deployment works
print("BlockingStream deployment created successfully")
print("\nTesting RayParallel deployment...")
# Test RayParallel
ray_parallel_handle = serve.run(RayParallel.bind())
result = ray_parallel_handle.remote(None).result()
print(f"RayParallel result: {result}")
assert result == {"results": [1, 4, 9, 16]}
print("\nTesting BatchedModel deployment...")
# Test BatchedModel
batched_model_handle = serve.run(BatchedModel.bind())
result = batched_model_handle.remote(1).result()
print(f"BatchedModel result: {result}")
assert result == "result_1"
print("\nTesting BatchedModelOffload deployment...")
# Test BatchedModelOffload
batched_model_offload_handle = serve.run(BatchedModelOffload.bind())
result = batched_model_offload_handle.remote(1).result()
print(f"BatchedModelOffload result: {result}")
assert result == "result_1"
# Test HTTP-related deployments with try-except
print("\n--- Testing HTTP-related deployments (may fail due to network) ---")
print("\nTesting BlockingHTTP deployment...")
try:
blocking_http_handle = serve.run(BlockingHTTP.bind())
result = blocking_http_handle.remote(None).result()
print(f"BlockingHTTP result (first 50 chars): {result[:50]}...")
print("✅ BlockingHTTP test passed")
except Exception as e:
print(f"⚠️ BlockingHTTP test failed (expected): {type(e).__name__}: {e}")
print("\nTesting AsyncHTTP deployment...")
try:
async_http_handle = serve.run(AsyncHTTP.bind())
result = async_http_handle.remote(None).result()
print(f"AsyncHTTP result (first 50 chars): {result[:50]}...")
print("✅ AsyncHTTP test passed")
except Exception as e:
print(f"⚠️ AsyncHTTP test failed (expected): {type(e).__name__}: {e}")
print("\nTesting ThreadedHTTP deployment...")
try:
threaded_http_handle = serve.run(ThreadedHTTP.bind())
result = threaded_http_handle.remote(None).result()
print(f"ThreadedHTTP result (first 50 chars): {result[:50]}...")
print("✅ ThreadedHTTP test passed")
except Exception as e:
print(f"⚠️ ThreadedHTTP test failed (expected): {type(e).__name__}: {e}")
print("\nTesting OffloadIO deployment...")
try:
offload_io_handle = serve.run(OffloadIO.bind())
result = offload_io_handle.remote(None).result()
print(f"OffloadIO result (first 50 chars): {result[:50]}...")
print("✅ OffloadIO test passed")
except Exception as e:
print(f"⚠️ OffloadIO test failed (expected): {type(e).__name__}: {e}")
print("\nTesting FastAPIDeployment...")
fastapi_handle = serve.run(FastAPIDeployment.bind())
# Give it a moment to start
import time
import requests
time.sleep(2)
# Test the sync endpoint
response = requests.get("http://127.0.0.1:8000/sync", timeout=5)
print(f"FastAPIDeployment /sync result: {response.json()}")
# Test the async endpoint
response = requests.get("http://127.0.0.1:8000/async", timeout=5)
print(f"FastAPIDeployment /async result: {response.json()}")
print("✅ FastAPIDeployment test passed")
print("\n✅ All core tests passed!")