09. Asynchronous AI Serving with FastAPI & Cloud Run

High-throughput token streaming architectures, handling socket aborts, containerization, and autoscaling serverless infrastructure.

Android Architecture Mental Model: In mobile UI development, blocking the Main Looper drops frames and causes ANRs (Application Not Responding). Similarly, synchronous HTTP servers (like Flask with standard workers) dedicate an entire OS process/thread to a single incoming request. Because LLM generation can take 5–15 seconds, a blocking server exhausts its connection thread pool almost immediately under minimal load. Asynchronous gateways (FastAPI ASGI or Ktor Netty) operate like Kotlin structured concurrency (Coroutines): IO-bound inference requests suspend execution without monopolizing underlying threads, allowing a single lightweight container instance to handle hundreds of concurrent streaming sessions.

1. The Streaming Gateway Topology

The AI gateway acts as the secure intermediary between external client applications and upstream foundation models. It authenticates users, injects system prompt constraints, negotiates rate limits, and streams tokens over Server-Sent Events (SSE).

flowchart LR subgraph Clients["Client Tier (Android / Web)"] CLI["Android OkHttp SSE Listener"] end subgraph Gateway["Serverless Edge (Cloud Run / FastAPI)"] AUTH["Auth & Rate Limiter Middleware"] DISC["Socket Disconnect Watchdog"] ASYNC["Async Event Generator (ASGI)"] end subgraph LLM["Upstream Model Provider"] VAI["Vertex AI / OpenAI SSE Stream"] end CLI -->|HTTP/2 POST /stream| AUTH AUTH --> ASYNC ASYNC -->|Non-blocking gRPC/HTTP/2| VAI VAI -->|Raw Tokens| ASYNC ASYNC -->|SSE data: chunk| CLI DISC -.->|Monitors Socket| CLI DISC -->|On Client Drop: Cancel Coroutine/Task| ASYNC

2. Handling Client Disconnects & Runaway Costs

If an Android user navigates away or loses cellular connectivity mid-stream, standard HTTP endpoints often continue executing in the background until the LLM completes generation. In AI systems, where inference cost is calculated per output token, this leads to significant resource waste.

Architecture Strategy System Mechanism Impact on Compute & Spend
Unmonitored Generator Server continues pulling tokens from LLM API even after the TCP connection to the mobile device terminates. Wastes compute and money; burns API tokens for responses that are never displayed.
Socket Polling / Cancellation Engine checks request.is_disconnected() on every generated token chunk and cancels the upstream context. Immediately aborts the LLM API call, terminating token generation and freeing memory.

3. Dual-Stack Implementations: Streaming AI Server

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
import asyncio

app = FastAPI(title="AI Gateway")
ai_client = AsyncOpenAI()

async def event_generator(prompt: str, request: Request):
    try:
        # Request stream from inference engine
        stream = await ai_client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
            stream=True
        )

        async for chunk in stream:
            # Check if the client closed connection
            if await request.is_disconnected():
                print("Client disconnected. Aborting upstream generation.")
                break

            content = chunk.choices[0].delta.content
            if content:
                # Emit Server-Sent Events (SSE) standard protocol
                yield f"data: {content}\n\n"
                
        yield "data: [DONE]\n\n"

    except asyncio.CancelledError:
        print("Task explicitly cancelled by gateway.")
        raise

@app.post("/v1/stream")
async def stream_inference(request: Request):
    body = await request.json()
    prompt = body.get("prompt")
    if not prompt:
        raise HTTPException(status_code=400, detail="Missing prompt parameter")

    return StreamingResponse(
        event_generator(prompt, request),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no" # Prevents proxy buffering (NGINX/Cloudflare)
        }
    )
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.request.*
import io.ktor.http.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun Application.aiModule() {
    routing {
        post("/v1/stream") {
            val parameters = call.receive<Map<String, String>>()
            val prompt = parameters["prompt"] ?: return@post call.respond(HttpStatusCode.BadRequest)

            call.response.cacheControl(CacheControl.NoCache(null))
            call.respondTextWriter(contentType = ContentType.Text.EventStream) {
                try {
                    // Simulated asynchronous model token stream Flow
                    mockModelTokenStream(prompt).collect { token ->
                        write("data: $token\n\n")
                        flush()
                    }
                    write("data: [DONE]\n\n")
                    flush()
                } catch (e: CancellationException) {
                    // Triggered when client socket closes mid-stream
                    println("Ktor client disconnected. Coroutine cancelled.")
                    throw e
                }
            }
        }
    }
}

fun mockModelTokenStream(prompt: String): Flow<String> = flow {
    val tokens = listOf("Structured ", "concurrency ", "powers ", "scalable ", "gateways.")
    for (token in tokens) {
        delay(100) // Simulating network generation interval
        emit(token)
    }
}.flowOn(Dispatchers.IO)

4. Containerization & Cloud Run Tuning

A lightweight production Docker container paired with optimized Google Cloud Run configurations ensures minimal cold-start times and high concurrency.

FROM python:3.11-slim as base

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PORT=8080

WORKDIR /app

# Install runtime dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Run with Uvicorn ASGI server
CMD exec uvicorn main:app --host 0.0.0.0 --port ${PORT} --workers 2 --timeout-keep-alive 65
# Deploy service to Cloud Run with optimized streaming concurrency
gcloud run deploy ai-serving-gateway \
    --image gcr.io/my-project/ai-gateway:latest \
    --platform managed \
    --region us-central1 \
    --allow-unauthenticated \
    --concurrency 80 \
    --cpu 2 \
    --memory 2Gi \
    --min-instances 1 \
    --max-instances 20 \
    --timeout 300

5. Progressive Glossary

Term Technical Definition Android / Systems Analogy
ASGI Asynchronous Server Gateway Interface: the modern asynchronous Python standard for handling concurrent HTTP and WebSockets. Kotlin Coroutines Dispatchers.IO replacing legacy single-threaded handlers.
Concurrency Limit The maximum number of simultaneous HTTP requests a single Cloud Run container instance can process concurrently. OkHttp Dispatcher.maxRequestsPerHost connection pool ceiling.
Cold Start The latency delay incurred when a serverless platform boots up a new container image from zero instances. Android Application Cold Launch (Application class initialization before first frame).
Proxy Buffering Intermediate proxies (NGINX, Cloudflare) holding partial chunks until a large buffer is full before sending to client. A bad custom Flow operator collecting all emissions before emitting down the chain.

Sources & Reference Standards