[{"data":1,"prerenderedAt":1060},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002F":310,"surround-\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002F":1058},[4,96,212],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,30,42,54,66,90],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[35,36],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[47,48],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[59,60],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[95],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[101,104,122,140,158,176,194],{"title":102,"path":98,"stem":103},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":105,"path":106,"stem":107,"children":108},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[109,110,116],{"title":105,"path":106,"stem":107},{"title":111,"path":112,"stem":113,"children":114},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[115],{"title":111,"path":112,"stem":113},{"title":117,"path":118,"stem":119,"children":120},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[121],{"title":117,"path":118,"stem":119},{"title":123,"path":124,"stem":125,"children":126},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[127,128,134],{"title":123,"path":124,"stem":125},{"title":129,"path":130,"stem":131,"children":132},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[133],{"title":129,"path":130,"stem":131},{"title":135,"path":136,"stem":137,"children":138},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[139],{"title":135,"path":136,"stem":137},{"title":141,"path":142,"stem":143,"children":144},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[145,146,152],{"title":141,"path":142,"stem":143},{"title":147,"path":148,"stem":149,"children":150},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[151],{"title":147,"path":148,"stem":149},{"title":153,"path":154,"stem":155,"children":156},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[157],{"title":153,"path":154,"stem":155},{"title":159,"path":160,"stem":161,"children":162},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[163,164,170],{"title":159,"path":160,"stem":161},{"title":165,"path":166,"stem":167,"children":168},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[169],{"title":165,"path":166,"stem":167},{"title":171,"path":172,"stem":173,"children":174},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[175],{"title":171,"path":172,"stem":173},{"title":177,"path":178,"stem":179,"children":180},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[181,182,188],{"title":177,"path":178,"stem":179},{"title":183,"path":184,"stem":185,"children":186},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[187],{"title":183,"path":184,"stem":185},{"title":189,"path":190,"stem":191,"children":192},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[193],{"title":189,"path":190,"stem":191},{"title":195,"path":196,"stem":197,"children":198},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[199,200,206],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[217,220,232,250,268,280,292],{"title":218,"path":214,"stem":219},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":221,"path":222,"stem":223,"children":224},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[225,226],{"title":221,"path":222,"stem":223},{"title":227,"path":228,"stem":229,"children":230},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[231],{"title":227,"path":228,"stem":229},{"title":233,"path":234,"stem":235,"children":236},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[237,238,244],{"title":233,"path":234,"stem":235},{"title":239,"path":240,"stem":241,"children":242},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[243],{"title":239,"path":240,"stem":241},{"title":245,"path":246,"stem":247,"children":248},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[249],{"title":245,"path":246,"stem":247},{"title":251,"path":252,"stem":253,"children":254},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[255,256,262],{"title":251,"path":252,"stem":253},{"title":257,"path":258,"stem":259,"children":260},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[261],{"title":257,"path":258,"stem":259},{"title":263,"path":264,"stem":265,"children":266},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[267],{"title":263,"path":264,"stem":265},{"title":269,"path":270,"stem":271,"children":272},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[273,274],{"title":269,"path":270,"stem":271},{"title":275,"path":276,"stem":277,"children":278},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[279],{"title":275,"path":276,"stem":277},{"title":281,"path":282,"stem":283,"children":284},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[285,286],{"title":281,"path":282,"stem":283},{"title":287,"path":288,"stem":289,"children":290},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[291],{"title":287,"path":288,"stem":289},{"title":293,"path":294,"stem":295,"children":296},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[297,298,304],{"title":293,"path":294,"stem":295},{"title":299,"path":300,"stem":301,"children":302},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[303],{"title":299,"path":300,"stem":301},{"title":305,"path":306,"stem":307,"children":308},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[309],{"title":305,"path":306,"stem":307},{"id":311,"title":117,"body":312,"description":1012,"extension":1013,"meta":1014,"navigation":530,"path":118,"seo":1056,"stem":119,"__hash__":1057},"content\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex.md",{"type":313,"value":314,"toc":999},"minimark",[315,319,326,358,367,372,378,382,390,394,399,463,467,501,505,611,615,744,748,786,790,968,972,995],[316,317,117],"h1",{"id":318},"fixing-blocking-calls-in-async-fastapi-routes",[320,321,322],"p",{},[323,324,325],"strong",{},"Key takeaways:",[327,328,329,333,341,352,355],"ul",{},[330,331,332],"li",{},"The symptom is latency that climbs with concurrency on an endpoint that should be fast.",[330,334,335,336,340],{},"The cause is synchronous I\u002FO or CPU work inside an ",[337,338,339],"code",{},"async def"," handler.",[330,342,343,344,347,348,351],{},"Offload blocking I\u002FO with ",[337,345,346],{},"run_in_threadpool"," or ",[337,349,350],{},"anyio.to_thread.run_sync",".",[330,353,354],{},"Send CPU-bound work to a process pool, since threads do not help under the GIL.",[330,356,357],{},"Verify by load testing that concurrent requests overlap.",[320,359,360,361,366],{},"This is the debugging companion to ",[362,363,365],"a",{"href":364},"\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002F","Async Correctness and Concurrency",". Read that page for why one blocked coroutine stalls the whole worker.",[368,369,371],"h2",{"id":370},"the-problem-this-solves","The Problem This Solves",[320,373,374,375,377],{},"An endpoint works in development and falls over under load. The cause is almost always a blocking call hidden in an ",[337,376,339],{},": the loop cannot advance other requests while that call runs, so throughput collapses exactly when you need it. This guide finds and fixes it.",[368,379,381],{"id":380},"prerequisites","Prerequisites",[327,383,384,387],{},[330,385,386],{},"A FastAPI app exhibiting latency that grows with concurrency.",[330,388,389],{},"A load-testing tool and, optionally, asyncio debug mode enabled.",[368,391,393],{"id":392},"step-by-step-implementation","Step-by-Step Implementation",[395,396,398],"h3",{"id":397},"_1-reproduce-and-confirm","1. Reproduce and confirm",[400,401,406],"pre",{"className":402,"code":403,"language":404,"meta":405,"style":405},"language-bash shiki shiki-themes github-light","# If p95 latency scales with -c (concurrency), the loop is being blocked.\nhey -z 10s -c 1 http:\u002F\u002Flocalhost:8000\u002Fslow    # baseline\nhey -z 10s -c 50 http:\u002F\u002Flocalhost:8000\u002Fslow   # compare p95\n","bash","",[337,407,408,417,444],{"__ignoreMap":405},[409,410,413],"span",{"class":411,"line":412},"line",1,[409,414,416],{"class":415},"sAwPA","# If p95 latency scales with -c (concurrency), the loop is being blocked.\n",[409,418,420,424,428,432,435,438,441],{"class":411,"line":419},2,[409,421,423],{"class":422},"s7eDp","hey",[409,425,427],{"class":426},"sYu0t"," -z",[409,429,431],{"class":430},"sYBdl"," 10s",[409,433,434],{"class":426}," -c",[409,436,437],{"class":426}," 1",[409,439,440],{"class":430}," http:\u002F\u002Flocalhost:8000\u002Fslow",[409,442,443],{"class":415},"    # baseline\n",[409,445,447,449,451,453,455,458,460],{"class":411,"line":446},3,[409,448,423],{"class":422},[409,450,427],{"class":426},[409,452,431],{"class":430},[409,454,434],{"class":426},[409,456,457],{"class":426}," 50",[409,459,440],{"class":430},[409,461,462],{"class":415},"   # compare p95\n",[395,464,466],{"id":465},"_2-find-the-blocking-line","2. Find the blocking line",[400,468,472],{"className":469,"code":470,"language":471,"meta":405,"style":405},"language-python shiki shiki-themes github-light","# Symptoms to grep for inside async def handlers:\n#   requests.get(...)        → blocking HTTP\n#   sync_session.execute()   → blocking DB driver\n#   time.sleep(...)          → blocking sleep\n#   heavy_pure_python_loop() → CPU-bound\n","python",[337,473,474,479,484,489,495],{"__ignoreMap":405},[409,475,476],{"class":411,"line":412},[409,477,478],{"class":415},"# Symptoms to grep for inside async def handlers:\n",[409,480,481],{"class":411,"line":419},[409,482,483],{"class":415},"#   requests.get(...)        → blocking HTTP\n",[409,485,486],{"class":411,"line":446},[409,487,488],{"class":415},"#   sync_session.execute()   → blocking DB driver\n",[409,490,492],{"class":411,"line":491},4,[409,493,494],{"class":415},"#   time.sleep(...)          → blocking sleep\n",[409,496,498],{"class":411,"line":497},5,[409,499,500],{"class":415},"#   heavy_pure_python_loop() → CPU-bound\n",[395,502,504],{"id":503},"_3-offload-blocking-io","3. Offload blocking I\u002FO",[400,506,508],{"className":469,"code":507,"language":471,"meta":405,"style":405},"from starlette.concurrency import run_in_threadpool\n\n\n@app.get(\"\u002Fexternal\")\nasync def external() -> dict:\n    # The sync client now runs in a worker thread; the loop keeps serving others.\n    data = await run_in_threadpool(legacy_sync_client.fetch, \"\u002Fresource\")\n    return {\"data\": data}\n",[337,509,510,526,532,536,550,570,576,596],{"__ignoreMap":405},[409,511,512,516,520,523],{"class":411,"line":412},[409,513,515],{"class":514},"sD7c4","from",[409,517,519],{"class":518},"sgsFI"," starlette.concurrency ",[409,521,522],{"class":514},"import",[409,524,525],{"class":518}," run_in_threadpool\n",[409,527,528],{"class":411,"line":419},[409,529,531],{"emptyLinePlaceholder":530},true,"\n",[409,533,534],{"class":411,"line":446},[409,535,531],{"emptyLinePlaceholder":530},[409,537,538,541,544,547],{"class":411,"line":491},[409,539,540],{"class":422},"@app.get",[409,542,543],{"class":518},"(",[409,545,546],{"class":430},"\"\u002Fexternal\"",[409,548,549],{"class":518},")\n",[409,551,552,555,558,561,564,567],{"class":411,"line":497},[409,553,554],{"class":514},"async",[409,556,557],{"class":514}," def",[409,559,560],{"class":422}," external",[409,562,563],{"class":518},"() -> ",[409,565,566],{"class":426},"dict",[409,568,569],{"class":518},":\n",[409,571,573],{"class":411,"line":572},6,[409,574,575],{"class":415},"    # The sync client now runs in a worker thread; the loop keeps serving others.\n",[409,577,579,582,585,588,591,594],{"class":411,"line":578},7,[409,580,581],{"class":518},"    data ",[409,583,584],{"class":514},"=",[409,586,587],{"class":514}," await",[409,589,590],{"class":518}," run_in_threadpool(legacy_sync_client.fetch, ",[409,592,593],{"class":430},"\"\u002Fresource\"",[409,595,549],{"class":518},[409,597,599,602,605,608],{"class":411,"line":598},8,[409,600,601],{"class":514},"    return",[409,603,604],{"class":518}," {",[409,606,607],{"class":430},"\"data\"",[409,609,610],{"class":518},": data}\n",[395,612,614],{"id":613},"_4-offload-cpu-bound-work-to-a-process","4. Offload CPU-bound work to a process",[400,616,618],{"className":469,"code":617,"language":471,"meta":405,"style":405},"import asyncio\nfrom concurrent.futures import ProcessPoolExecutor\n\n_pool = ProcessPoolExecutor()\n\n\n@app.post(\"\u002Fencode\")\nasync def encode(payload: bytes) -> dict:\n    loop = asyncio.get_running_loop()\n    # CPU-bound work in a separate process sidesteps the GIL and the loop.\n    result = await loop.run_in_executor(_pool, cpu_encode, payload)\n    return {\"size\": len(result)}\n",[337,619,620,627,639,643,653,657,661,673,695,706,712,725],{"__ignoreMap":405},[409,621,622,624],{"class":411,"line":412},[409,623,522],{"class":514},[409,625,626],{"class":518}," asyncio\n",[409,628,629,631,634,636],{"class":411,"line":419},[409,630,515],{"class":514},[409,632,633],{"class":518}," concurrent.futures ",[409,635,522],{"class":514},[409,637,638],{"class":518}," ProcessPoolExecutor\n",[409,640,641],{"class":411,"line":446},[409,642,531],{"emptyLinePlaceholder":530},[409,644,645,648,650],{"class":411,"line":491},[409,646,647],{"class":518},"_pool ",[409,649,584],{"class":514},[409,651,652],{"class":518}," ProcessPoolExecutor()\n",[409,654,655],{"class":411,"line":497},[409,656,531],{"emptyLinePlaceholder":530},[409,658,659],{"class":411,"line":572},[409,660,531],{"emptyLinePlaceholder":530},[409,662,663,666,668,671],{"class":411,"line":578},[409,664,665],{"class":422},"@app.post",[409,667,543],{"class":518},[409,669,670],{"class":430},"\"\u002Fencode\"",[409,672,549],{"class":518},[409,674,675,677,679,682,685,688,691,693],{"class":411,"line":598},[409,676,554],{"class":514},[409,678,557],{"class":514},[409,680,681],{"class":422}," encode",[409,683,684],{"class":518},"(payload: ",[409,686,687],{"class":426},"bytes",[409,689,690],{"class":518},") -> ",[409,692,566],{"class":426},[409,694,569],{"class":518},[409,696,698,701,703],{"class":411,"line":697},9,[409,699,700],{"class":518},"    loop ",[409,702,584],{"class":514},[409,704,705],{"class":518}," asyncio.get_running_loop()\n",[409,707,709],{"class":411,"line":708},10,[409,710,711],{"class":415},"    # CPU-bound work in a separate process sidesteps the GIL and the loop.\n",[409,713,715,718,720,722],{"class":411,"line":714},11,[409,716,717],{"class":518},"    result ",[409,719,584],{"class":514},[409,721,587],{"class":514},[409,723,724],{"class":518}," loop.run_in_executor(_pool, cpu_encode, payload)\n",[409,726,728,730,732,735,738,741],{"class":411,"line":727},12,[409,729,601],{"class":514},[409,731,604],{"class":518},[409,733,734],{"class":430},"\"size\"",[409,736,737],{"class":518},": ",[409,739,740],{"class":426},"len",[409,742,743],{"class":518},"(result)}\n",[368,745,747],{"id":746},"edge-cases-and-gotchas","Edge Cases and Gotchas",[327,749,750,770,776],{},[330,751,752,761,762,765,766,769],{},[323,753,754,757,758,351],{},[337,755,756],{},"asyncio.sleep"," vs ",[337,759,760],{},"time.sleep"," Use ",[337,763,764],{},"await asyncio.sleep()","; ",[337,767,768],{},"time.sleep()"," blocks the loop.",[330,771,772,775],{},[323,773,774],{},"Sync middleware."," Blocking work in middleware blocks every request; offload there too.",[330,777,778,781,782,785],{},[323,779,780],{},"Pickling for processes."," ",[337,783,784],{},"ProcessPoolExecutor"," pickles arguments and results; pass simple, picklable data.",[368,787,789],{"id":788},"verification","Verification",[400,791,793],{"className":469,"code":792,"language":471,"meta":405,"style":405},"import asyncio\nimport time\n\nimport httpx\n\n\nasync def test_no_longer_blocking(app):\n    transport = httpx.ASGITransport(app=app)\n    async with httpx.AsyncClient(transport=transport, base_url=\"http:\u002F\u002Ft\") as c:\n        start = time.perf_counter()\n        await asyncio.gather(*[c.get(\"\u002Fexternal\") for _ in range(10)])\n        # Ten concurrent calls should overlap, not run end to end.\n        assert time.perf_counter() - start \u003C 1.0\n",[337,794,795,801,808,812,819,823,827,839,858,894,904,942,947],{"__ignoreMap":405},[409,796,797,799],{"class":411,"line":412},[409,798,522],{"class":514},[409,800,626],{"class":518},[409,802,803,805],{"class":411,"line":419},[409,804,522],{"class":514},[409,806,807],{"class":518}," time\n",[409,809,810],{"class":411,"line":446},[409,811,531],{"emptyLinePlaceholder":530},[409,813,814,816],{"class":411,"line":491},[409,815,522],{"class":514},[409,817,818],{"class":518}," httpx\n",[409,820,821],{"class":411,"line":497},[409,822,531],{"emptyLinePlaceholder":530},[409,824,825],{"class":411,"line":572},[409,826,531],{"emptyLinePlaceholder":530},[409,828,829,831,833,836],{"class":411,"line":578},[409,830,554],{"class":514},[409,832,557],{"class":514},[409,834,835],{"class":422}," test_no_longer_blocking",[409,837,838],{"class":518},"(app):\n",[409,840,841,844,846,849,853,855],{"class":411,"line":598},[409,842,843],{"class":518},"    transport ",[409,845,584],{"class":514},[409,847,848],{"class":518}," httpx.ASGITransport(",[409,850,852],{"class":851},"sqxcx","app",[409,854,584],{"class":514},[409,856,857],{"class":518},"app)\n",[409,859,860,863,866,869,872,874,877,880,882,885,888,891],{"class":411,"line":697},[409,861,862],{"class":514},"    async",[409,864,865],{"class":514}," with",[409,867,868],{"class":518}," httpx.AsyncClient(",[409,870,871],{"class":851},"transport",[409,873,584],{"class":514},[409,875,876],{"class":518},"transport, ",[409,878,879],{"class":851},"base_url",[409,881,584],{"class":514},[409,883,884],{"class":430},"\"http:\u002F\u002Ft\"",[409,886,887],{"class":518},") ",[409,889,890],{"class":514},"as",[409,892,893],{"class":518}," c:\n",[409,895,896,899,901],{"class":411,"line":708},[409,897,898],{"class":518},"        start ",[409,900,584],{"class":514},[409,902,903],{"class":518}," time.perf_counter()\n",[409,905,906,909,912,915,918,920,922,925,928,931,934,936,939],{"class":411,"line":714},[409,907,908],{"class":514},"        await",[409,910,911],{"class":518}," asyncio.gather(",[409,913,914],{"class":514},"*",[409,916,917],{"class":518},"[c.get(",[409,919,546],{"class":430},[409,921,887],{"class":518},[409,923,924],{"class":514},"for",[409,926,927],{"class":518}," _ ",[409,929,930],{"class":514},"in",[409,932,933],{"class":426}," range",[409,935,543],{"class":518},[409,937,938],{"class":426},"10",[409,940,941],{"class":518},")])\n",[409,943,944],{"class":411,"line":727},[409,945,946],{"class":415},"        # Ten concurrent calls should overlap, not run end to end.\n",[409,948,950,953,956,959,962,965],{"class":411,"line":949},13,[409,951,952],{"class":514},"        assert",[409,954,955],{"class":518}," time.perf_counter() ",[409,957,958],{"class":514},"-",[409,960,961],{"class":518}," start ",[409,963,964],{"class":514},"\u003C",[409,966,967],{"class":426}," 1.0\n",[368,969,971],{"id":970},"related-reading","Related Reading",[327,973,974,981],{},[330,975,976,781,979,351],{},[323,977,978],{},"Up to the topic:",[362,980,365],{"href":364},[330,982,983,781,986,990,991,351],{},[323,984,985],{},"Related guides:",[362,987,989],{"href":988},"\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002F","FastAPI async def vs def Performance"," and ",[362,992,994],{"href":993},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002F","Background Task Processing",[996,997,998],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":405,"searchDepth":419,"depth":419,"links":1000},[1001,1002,1003,1009,1010,1011],{"id":370,"depth":419,"text":371},{"id":380,"depth":419,"text":381},{"id":392,"depth":419,"text":393,"children":1004},[1005,1006,1007,1008],{"id":397,"depth":446,"text":398},{"id":465,"depth":446,"text":466},{"id":503,"depth":446,"text":504},{"id":613,"depth":446,"text":614},{"id":746,"depth":419,"text":747},{"id":788,"depth":419,"text":789},{"id":970,"depth":419,"text":971},"Diagnose and fix blocking calls in async FastAPI routes: spot the symptoms, find the blocking line, offload with run_in_threadpool or anyio, and move CPU work to a process pool.","md",{"slug":1015,"type":1016,"breadcrumb":1017,"datePublished":1029,"dateModified":1030,"howto":1031,"faq":1049},"fixing-blocking-calls-in-async-routes","long_tail",[1018,1021,1024,1026],{"label":1019,"path":1020},"Home","\u002F",{"label":1022,"path":1023},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":1025,"path":364},"Async Correctness & Concurrency",{"label":1027,"path":1028},"Fixing Blocking Calls in Async Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002F","2026-02-18","2026-06-18",{"name":1032,"steps":1033},"Fix a blocking call in an async FastAPI route",[1034,1037,1040,1043,1046],{"name":1035,"text":1036},"Confirm the symptom","Observe that latency rises with concurrency even though the endpoint awaits little real I\u002FO.",{"name":1038,"text":1039},"Locate the blocking call","Find the synchronous line — a sync driver, requests, a CPU loop, or time.sleep.",{"name":1041,"text":1042},"Offload I\u002FO-bound blocking","Wrap it in run_in_threadpool or anyio.to_thread.run_sync so the loop stays free.",{"name":1044,"text":1045},"Offload CPU-bound work","Send CPU-heavy work to a process pool executor instead of a thread.",{"name":1047,"text":1048},"Re-test under load","Confirm concurrent requests now overlap instead of serializing.",[1050,1053],{"q":1051,"a":1052},"How do I find which line is blocking the event loop?","Look for synchronous I\u002FO and CPU work inside async def handlers: a sync database driver, the requests library, file reads, time.sleep, or a heavy computation. Tools that warn on slow callbacks, such as enabling asyncio debug mode, surface coroutines that ran too long without yielding, pointing you at the offending call.",{"q":1054,"a":1055},"Is wrapping everything in run_in_threadpool a good fix?","It is a correct fix for blocking I\u002FO but not a free one. The thread pool is bounded, so offloading large volumes of slow work just moves the bottleneck. Prefer a truly async library where one exists, reserve thread offloading for unavoidable sync I\u002FO, and use a process pool for CPU-bound work.",{"title":117,"description":1012},"2jpXqlGj6oCrVLPHeWJ_GkYiBn9BTvJtzhR2mbajREw",[1059,1059],null,1781809863424]