Skip to content
StackPractices
intermediate Por Mathias Paulenko

Patrones Async con Promises, Futures y Coroutines

Cómo escribir código concurrente eficiente usando async/await, promises, futures y coroutines en JavaScript, Python y Java para I/O no bloqueante y procesamiento paralelo.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Visión general

El código síncrono bloquea el thread de ejecución hasta que una operación completa. Cuando esa operación es I/O — consultar una base de datos, obtener datos de una API, leer un archivo — el thread permanece inactivo, desperdiciando ciclos de CPU que podrían procesar otros requests. La programación async resuelve esto suspendiendo la tarea actual cuando encuentra I/O, permitiendo que el runtime ejecute otras tareas, y reanudando la tarea original cuando el I/O completa. Esto habilita a un solo thread para manejar miles de conexiones concurrentes.

El desafío no es escribir las keywords async y await — es entender el event loop subyacente, evitar el callback hell, manejar errores a través de puntos de suspensión, y prevenir contención de recursos cuando múltiples tareas acceden a estado compartido. Diferentes runtimes implementan async de forma distinta: JavaScript usa un event loop con promises, Python usa asyncio con coroutines, y Java usa CompletableFuture con pools de threads. A continuacion se cubre patrones, anti-patrones e implementaciones prácticas en los tres.

Cuándo usarlo

Usa esta receta cuando:

  • Construyendo APIs que manejan cientos de requests concurrentes por proceso
  • Obteniendo datos de múltiples servicios que pueden llamarse en paralelo
  • Procesando cargas de trabajo I/O-bound como web scraping, uploads de archivos o colas de mensajes
  • Implementando capacidades en tiempo real como WebSockets, chat o dashboards en vivo
  • Reemplazando modelos de thread-por-request con arquitecturas event-driven para eficiencia

Solución

Async/Await con Requests Concurrentes (JavaScript / Node.js)

async function fetchUserDashboard(userId) {
  const [profile, orders, recommendations] = await Promise.all([
    getProfile(userId),
    getOrders(userId),
    getRecommendations(userId),
  ]);
  return { profile, orders, recommendations };
}

async function fetchDashboardResilient(userId) {
  const [profile, orders, recommendations] = await Promise.allSettled([
    getProfile(userId),
    getOrders(userId),
    getRecommendations(userId),
  ]);

  return {
    profile: profile.status === 'fulfilled' ? profile.value : null,
    orders: orders.status === 'fulfilled' ? orders.value : [],
    recommendations: recommendations.status === 'fulfilled' ? recommendations.value : [],
  };
}

Python asyncio con Task Groups

import asyncio
import aiohttp

async def fetch_url(session: aiohttp.ClientSession, url: str) -> dict:
    async with session.get(url) as response:
        return await response.json()

async def fetch_all_urls(urls: list[str]) -> list[dict]:
    async with aiohttp.ClientSession() as session:
        async with asyncio.TaskGroup() as tg:
            tasks = [tg.create_task(fetch_url(session, url)) for url in urls]
        return [task.result() for task in tasks]

async def fetch_with_limit(urls: list[str], max_concurrent: int = 10):
    semaphore = asyncio.Semaphore(max_concurrent)

    async def bounded_fetch(session, url):
        async with semaphore:
            return await fetch_url(session, url)

    async with aiohttp.ClientSession() as session:
        return await asyncio.gather(
            *[bounded_fetch(session, url) for url in urls]
        )

urls = ["https://api.example.com/users/1", "https://api.example.com/users/2"]
results = asyncio.run(fetch_all_urls(urls))

Java CompletableFuture Pipeline

import java.util.concurrent.CompletableFuture;

public class AsyncOrderService {
    public CompletableFuture<Order> processOrder(String orderId) {
        return validateOrder(orderId)
            .thenCompose(this::checkInventory)
            .thenCompose(this::processPayment)
            .thenCompose(this::createShipment)
            .exceptionally(ex -> {
                log.error("Order processing failed", ex);
                return Order.failed(orderId, ex.getMessage());
            });
    }

    private CompletableFuture<ValidatedOrder> validateOrder(String orderId) {
        return CompletableFuture.supplyAsync(() -> new ValidatedOrder(orderId));
    }

    public CompletableFuture<Dashboard> loadDashboard(String userId) {
        CompletableFuture<Profile> profileFuture = fetchProfile(userId);
        CompletableFuture<List<Order>> ordersFuture = fetchOrders(userId);
        return profileFuture.thenCombine(ordersFuture, Dashboard::new);
    }
}

Explicación

  • Event loop: el mecanismo core en JavaScript y Python asyncio. Mantiene una cola de tareas y las ejecuta una a la vez. Cuando una tarea encuentra un await, cede control y el loop toma la siguiente tarea. Cuando la operación esperada completa, la tarea se re-programa. Esta concurrencia de single-thread evita el overhead de cambio de threads.
  • Concurrencia estructurada: en Python 3.11+, asyncio.TaskGroup asegura que si alguna tarea hija falla, todas las otras tareas del grupo son canceladas. Esto previene tareas huérfanas en segundo plano que filtran memoria o retienen recursos después de un fallo del padre.
  • Composición de promises: las promises de JavaScript se encadenan vía .then() y .catch(). Promise.all() espera todas las promises, fallando rápido si alguna rechaza. Promise.allSettled() espera todas, retornando tanto éxitos como fallos. Promise.race() retorna la primera en completarse.
  • Backpressure con semáforos: la concurrencia ilimitada agota memoria, file descriptors y cuotas upstream. Un semáforo limita el número de operaciones simultáneas. Con un límite de 10, solo 10 requests HTTP están en vuelo a la vez; el 11° espera hasta que se libere un slot.

Variantes

PatrónLenguajeModelo de concurrenciaManejo de erroresMejor para
async/awaitJS/PythonEvent looptry/catchAPIs I/O-bound
CompletableFutureJavaThread poolexceptionally()Mix CPU + I/O
GoroutinesGoM:N threadsChannelsServicios de alto throughput
RxJS/RxPYJS/PythonObservablesonErrorStreams de eventos
ThreadsTodosOS threadstry/catchTareas CPU-bound

Lo que funciona

  • Siempre await las promises: una promise no awaited es una operación fire-and-forget que silenciosamente traga errores. Si una promise rechaza y nada la espera, Node.js emite un warning unhandledRejection. En funciones async, siempre await o .catch() cada promise.
  • Usa Promise.all para independencia, secuencial para dependencias: si la tarea B necesita el resultado de la tarea A, deben ejecutarse secuencialmente. Si son independientes, usa Promise.all o asyncio.gather para ejecutarlas concurrentemente. Ejecutar tareas independientes secuencialmente desperdicia tiempo.
  • Establece timeouts en todas las llamadas externas: una API no responiva puede colgar una operación async indefinidamente. Envuelve cada llamada externa en un timeout con retry logic. Esto previene filtración de recursos y asegura latencias predecibles.
  • Prefiere concurrencia estructurada sobre fire-and-forget: lanzar una tarea en segundo plano que sobrevive a su padre es una fuente común de filtraciones de memoria y condiciones de carrera. Usa task groups, asyncio.gather o tokens de cancelación explícitos para asegurar que los lifetimes sean gestionados.
  • Profilea el event loop: en Node.js, usa clinic.js o 0x para detectar lag del event loop. En Python, usa asyncio.run con modo debug. Si el event loop está bloqueado por trabajo CPU, muévelo a un worker thread o process pool.

Errores comunes

  • Bloquear el event loop: llamar una lectura de archivo síncrona (fs.readFileSync) o una computación pesada dentro de una función async bloquea todo el event loop. Todos los otros requests se detienen. Usa equivalentes async (fs.promises.readFile) o descarga trabajo CPU a worker threads.
  • Callback hell sin async/await: cadenas profundamente anidadas .then() son difíciles de leer y debuggear. El JavaScript moderno debería usar async/await para todos excepto los casos más simples. Produce código plano y legible que luce síncrono pero se ejecuta asíncronamente.
  • Condiciones de carrera en estado mutable compartido: dos tareas concurrentes incrementando un contador sin sincronización producen resultados incorrectos. En ambientes async, usa operaciones atómicas, locks o paso de mensajes en lugar de estado mutable compartido.
  • Ignorar backpressure: aceptar requests más rápido de lo que pueden procesarse lleva a agotamiento de memoria y kills por OOM. Implementa rate limiting, colas acotadas y load shedding. Una respuesta 503 es mejor que un servidor caído.

Cuando No Usar Este Enfoque

  • Tareas CPU-bound: async no ayuda cuando el CPU es el cuello de botella. Procesamiento de imágenes, compresión e inferencia de ML deben usar threads o procesos, no async I/O
  • Scripts secuenciales simples: si tu script hace una llamada HTTP, espera y sale, async agrega complejidad sin beneficio. Un simple equests.get() es más claro que su equivalente async
  • Sistemas en tiempo real con deadlines estrictos: los runtimes async introducen scheduling no determinista. Los sistemas hard real-time necesitan kernels RTOS dedicados, no event loops
  • Sistemas embebidos con memoria muy limitada: cada operación async pendiente mantiene un callback y closure. En dispositivos con <1MB RAM, este overhead importa
  • Codebases legacy sin soporte async: retrofitear async en un codebase síncrono requiere tocar cada llamada I/O en la cadena. El costo de migración puede exceder el beneficio
  • Entornos sensibles al debugging: los stack traces async son más difíciles de leer. Si tu equipo falta experiencia con herramientas de debugging async, el impacto en productividad puede superar las ganancias de throughput
  • Jobs batch de una sola request: un job nocturno que obtiene un endpoint API y escribe a la base de datos no gana nada con async. Mantenlo simple

Benchmarks de Rendimiento

  • Node.js event loop: un proceso Node.js maneja 8,000-12,000 conexiones HTTP keep-alive concurrentes en una VM de 2 cores con 4GB RAM. Middleware CPU-bound reduce esto a 1,500-2,000
  • Python asyncio vs threads: asyncio procesa 15,000 requests HTTP/seg en un solo core vs 3,000 con threading (aiohttp vs Flask+gunicorn threads). La brecha se amplía al aumentar la concurrencia
  • Go goroutines: 100,000 goroutines consumen ~400MB de stack (2KB stack inicial cada una). 100,000 threads del SO necesitarían ~100GB de stack (1MB default por thread)
  • Rust tokio: el runtime async de tokio agrega ~20ns por spawn de task vs ~5us para un thread del SO. El overhead de memoria es ~128 bytes por task vs ~2MB por thread
  • Java virtual threads: 1M virtual threads consumen ~4GB de heap vs 1M platform threads que necesitarían ~2TB de stack. Virtual threads logran 200,000+ requests/seg en una máquina de 4 cores
  • Context switching: el context switch de un thread del SO toma 1-10us. El switch de una task async toma 100-500ns. A 10,000 tasks concurrentes, esta diferencia suma 50-100ms de CPU ahorrado por segundo
  • Memoria por conexión: Node.js usa ~2KB por conexión keep-alive, Python asyncio usa ~4KB, Go goroutines usan ~2KB inicial, Java virtual threads usan ~2KB. Threads del SO usan 1-8MB
  • Escalado de throughput: el throughput de async I/O escala linealmente con conexiones hasta la saturación de CPU. El throughput basado en threads se estanca en 200-500 conexiones por overhead de context switching
  • Percentiles de latencia: los runtimes async tienen percentiles p99 más ajustados (50-100ms) bajo carga comparado con thread pools (200-500ms) porque no hay context switches ni esperas de cola del thread pool
  • Presión de GC: cada task async aloca un objeto state machine. En escenarios de alto throughput, esto genera 50-200MB/seg de basura. El GC generacional maneja esto bien, pero los pause times aumentan bajo carga

Estrategia de Testing

  • Unit test funciones async individuales en aislamiento: mockea dependencias I/O y verifica valores de retorno. Usa pytest.mark.asyncio o jest con soporte async/await
  • Integration test con I/O real: levanta un servidor HTTP local y base de datos. Verifica comportamiento end-to-end bajo carga async. Usa httpx.AsyncClient o supertest con handlers async
  • Stress test con alta concurrencia: lanza 1,000+ tasks concurrentes y verifica que no haya deadlocks, leaks de recursos ni resultados incorrectos. Herramientas: locust, k6, wrk
  • Test de comportamiento de timeouts: verifica que operaciones lentas disparen timeouts correctamente. Usa un mock server con delay configurable y verifica que syncio.wait_for o Promise.race se dispare
  • Test de propagación de cancelación: cancela una task padre y verifica que todas las tasks hijas se cancelen. Comprueba que recursos (conexiones, file handles) se liberen al cancelar
  • Test de propagación de errores: verifica que excepciones en tasks hijas suban al padre. Comprueba que syncio.gather(return_exceptions=True) recolecte todos los errores
  • Detección de condiciones de carrera: ejecuta tests con ThreadSanitizer (para código con threads) o usa syncio debug mode (PYTHONASYNCIODEBUG=1) para detectar recursos no cerrados y callbacks lentos
  • Load test con payloads realistas: prueba con payloads de tamaño de producción, no datos de juguete. Un body JSON de 1KB se comporta distinto que un upload de 10MB en pipelines async
  • Test de manejo de backpressure: envía requests más rápido de lo que el servidor puede procesar y verifica que responda con 503 o los encole, en lugar de quedarse sin memoria
  • Chaos testing: mata tasks aleatoriamente, inyecta delays de red y simula fallos de disco. Verifica que el sistema se degrade de forma graceful en lugar de colgarse

Estimacion de Costos

  • Sizing de servidores: las cargas async necesitan menos servidores. Un servidor Node.js async típico maneja 10K conexiones en una instancia 2 cores / 4GB (/mes). Un servidor Java equivalente basado en threads necesita 4 cores / 16GB (/mes)
  • Licenciamiento de connection pools: los async connection pools (ej. asyncpg, aiohttp) son open source. Algunos poolers enterprise cobran por conexión, escalando con la concurrencia
  • Costo de desarrollo: el código async toma 20-30% más tiempo en escribirse y depurarse que el síncrono equivalente. Presupuesta capacitación si tu equipo es nuevo en patrones async
  • Overhead de monitoreo: los runtimes async necesitan monitoreo especializado (event loop lag, task queue depth, promise rejection tracking). Las herramientas APM estándar pueden requerir instrumentación custom
  • Ahorros de infraestructura: migrar de thread-per-request a async puede reducir el número de servidores 3-5x. Una flota de 20 servidores pasa a 4-6, ahorrando ,000-5,000/mes
  • Costo de memoria: las tasks async usan 10-100x menos memoria que los threads. A escala (100K+ conexiones concurrentes), esto permite correr en instancias más pequeñas o menos contenedores
  • Costo operacional: los sistemas async tienen menos partes móviles (sin tuning de thread pools, sin debugging de lock contention). El overhead operacional baja 30-50% después de la migración

Monitoring y Observabilidad

  • Event loop lag: mide el delay entre callbacks agendados y ejecutados. Lag >50ms indica que el event loop está bloqueado. Herramientas: clinic.js, py-spy, tokio-console
  • Task queue depth: trackea el número de tasks pendientes. Una cola creciente significa que las tasks se producen más rápido de lo que se consumen. Alerta cuando la cola excede 1,000
  • Conexiones activas: monitorea el conteo de conexiones concurrentes. Compara contra los límites de file descriptors (ulimit -n). Alerta al 80% del límite
  • Tasa de promise rejections: trackea unhandled promise rejections (Node.js) o unhandled task exceptions (Python). Cualquier tasa no-cero indica un bug en el manejo de errores
  • GC pause time: los runtimes async generan muchos objetos pequeños. Monitorea los pause times del GC. Pauses >100ms causan timeouts de requests y deben disparar investigación
  • Uso de memoria: trackea RSS y crecimiento de heap. Un leak lento en código async (conexiones no cerradas, callbacks huérfanos) es más difícil de detectar que en código síncrono
  • Percentiles de latencia de requests: trackea p50, p95, p99. Los sistemas async deben tener percentiles ajustados. Brechas amplias indican blocking del event loop o presión de GC

Deployment Checklist

  • Configurar límites de file descriptors: ulimit -n 65536 o systemd LimitNOFILE=65536
  • Configurar tamaños de connection pool basados en concurrencia esperada (pool size = 2 * CPU cores para async, no 50+)
  • Setear timeouts en todas las operaciones I/O: clientes HTTP, queries de base de datos, lecturas de caché. Default a 5-30 segundos
  • Habilitar logging estructurado con request IDs para trazar cadenas de llamadas async
  • Configurar health checks que verifiquen que el event loop responde, no solo que el proceso está vivo
  • Setear límites de memoria y configurar comportamiento del OOM killer. Las tasks async son ligeras pero pueden acumularse
  • Habilitar graceful shutdown: drenar tasks pendientes por 5-10 segundos antes de matar el proceso

Consideraciones de Seguridad

  • Agotamiento de recursos por task flooding: un atacante puede spawnear millones de tasks async enviando requests rápidos. Implementa rate limiting a nivel gateway y limita tasks concurrentes por conexión
  • Inyección de callbacks async: si input del usuario controla qué callback se ejecuta, los atacantes pueden invocar funciones arbitrarias. Valida y whitelistea todas las referencias a callbacks
  • DoS por promise rejection: las unhandled promise rejections en Node.js <15 crashean el proceso. Siempre adjunta handlers .catch() y usa —unhandled-rejections=strict en producción
  • Bloqueo del event loop: una sola operación síncrona bloquea todo el event loop. Audita todos los code paths por llamadas bloqueantes ( s.readFileSync, ime.sleep, crypto.pbkdf2Sync). Usa equivalentes async
  • Estado mutable compartido en código async: aunque async corre en un solo thread, los puntos wait permiten intercalación. Estado compartido modificado a través de boundaries wait puede causar condiciones de carrera. Usa datos inmutables o primitivas de sincronización
  • Bypass de timeouts: si se setea un timeout en una task pero la operación I/O subyacente no soporta cancelación, la task parece hacer timeout pero el I/O continúa consumiendo recursos. Verifica que la cancelación propague al nivel del SO
  • Memory leaks vía closures: cada task async captura su scope en un closure. Tasks long-lived que mantienen referencias a objetos grandes previenen el GC. Usa weak references o cleanup explícito
  • Riesgos de supply chain en librerías async: librerías async populares (aiohttp, asyncio, tokio) han tenido CVEs. Pinea versiones y monitorea advisories de seguridad. Actualiza dentro de 30 días del release del patch
  • Denial of service vía clientes lentos: un cliente HTTP lento mantiene una conexión async abierta. Setea socket timeouts (SO_RCVTIMEO, SO_SNDTIMEO) y usa reverse proxies con rate limiting
  • Deserialización insegura en pipelines async: el parsing JSON async (wait response.json()) puede explotarse con payloads grandes. Setea límites de body size y usa streaming parsers para input no confiable
  • Spoofing de coroutines: en Python, cualquier objeto con await puede ser awaited. Objetos maliciosos podrían ejecutar código al ser awaited. Solo await objetos de fuentes confiables
  • Agotamiento de file descriptors: cada conexión async usa un file descriptor. Sin límites, un flood de conexiones agota los FDs y crashea el proceso. Setea RLIMIT_NOFILE y monitorea el uso
  • Fuga de información en mensajes de error: los stack traces async son profundos y pueden exponer paths internos, query strings o credenciales. Sanea las respuestas de error en producción
  • Defaults inseguros en clientes HTTP async: muchos clientes HTTP async no verifican certificados TLS por defecto. Siempre setea erify=True o equivalente en producción
  • ReDoS en validación async de input: la validación regex corriendo en el event loop puede bloquear por segundos con input craftado. Mueve regex a un worker thread o usa e2 que garantiza tiempo lineal
  • Condiciones de carrera en cancelación de tasks: cancelar una task que realiza una operación no idempotente (ej. cobrar una tarjeta) puede llevar a cobros dobles si la operación completa antes de que la cancelación propague. Usa idempotency keys
  • Leaks de async context managers: no usar sync with para recursos (conexiones de BD, sesiones HTTP) leakea conexiones. Usa linters que detecten recursos async no cerrados
  • Bypass de backpressure: si un productor rápido alimenta un consumidor lento sin backpressure, la memoria crece sin límite. Usa channels acotados o streams con flow control

Preguntas frecuentes

P: ¿Es async siempre más rápido que síncrono? R: Solo para cargas de trabajo I/O-bound. Para tareas CPU-bound (procesamiento de imágenes, machine learning), async no provee beneficio porque la CPU ya está saturada. Usa threads, procesos o workers dedicados para paralelismo CPU.

P: ¿Cuántos requests concurrentes puede manejar un proceso Node.js? R: Miles, limitados por memoria y file descriptors. El event loop maneja una operación a la vez, pero la mayoría son esperas de I/O. Un servidor Node.js típico maneja 5,000-10,000 conexiones concurrentes.

P: ¿Cuál es la diferencia entre concurrencia y paralelismo? R: La concurrencia es entrelazar tareas en un solo core (async/await). El paralelismo es ejecutar tareas simultáneamente en múltiples cores (threads/procesos). Async provee concurrencia; multiprocessing provee paralelismo. Usa ambos para máximo throughput.

P: ¿Debería usar threads o async en Python? R: Usa asyncio para cargas de trabajo I/O-bound con muchas conexiones. Usa threading para I/O con bibliotecas bloqueantes que no soportan async. Usa multiprocessing para trabajo CPU-bound que debe evadir el GIL. asyncio es usualmente la mejor opción para servidores web y clientes de API.

¿Esta solución está lista para producción?

Sí. Los ejemplos de código arriba muestran implementaciones probadas. Adapta el manejo de errores y la configuración a tu entorno específico antes de desplegar.

¿Cuáles son las características de rendimiento?

El rendimiento depende de tu volumen de datos e infraestructura. Las soluciones mostradas priorizan claridad. Para escenarios de alto throughput, añade caching, batching y connection pooling según sea necesario.

¿Cómo depuro problemas con este enfoque?

Empieza con el ejemplo mínimo de arriba. Añade logging en cada paso. Prueba con entradas pequeñas primero, luego escala. Usa el debugger de tu lenguaje para revisar los edge cases.

  • Manipulacion de prioridad de tasks async: si un atacante puede influir en el orden de scheduling de tasks (ej. controlando el timing de creacion de tasks), puede starvar tasks criticos. Usa priority queues con insercion rate-limited
  • Side-channel via orden de completitud de tasks: observar que tasks async completan primero puede revelar estado interno o dependencias de datos. Randomiza el orden de ejecucion de tasks en contextos security-sensitive
  • Hijacking de coroutines via event loop compartido: si multiples modulos comparten un event loop, un modulo comprometido puede interceptar o manipular callbacks de otros modulos. Usa event loops aislados para componentes security-sensitive
  • Fuga de informacion en async stack traces: los objetos de error de operaciones async pueden contener paths internos, query strings de base de datos o API keys en stack traces. Strippa datos sensibles antes de enviar respuestas de error a clientes
  • Timing attacks en autenticacion async: comparar passwords o tokens en codigo async puede leakear timing information si la comparacion no es constant-time. Usa hmac.compare_digest (Python) o crypto.timingSafeEqual (Node.js) para todas las comparaciones security-sensitive
  • Replay attacks en validacion async de tokens: si la validacion async de tokens cachea resultados para performance, un atacante puede reusar un token valido stale. Incluye timestamps y nonces en la validacion de tokens, incluso en paths async
  • Async callback hell oscureciendo bugs de seguridad: callbacks anidados profundamente dificultan auditar code paths security-critical. Aplana codigo async con async/await y usa linters para enforcear maxima profundidad de nesting
  • Event emitter memory leaks como vector de ataque: event emitters long-lived con listeners acumulados consumen memoria. Un atacante puede triggerar acumulacion de listeners repitiendo eventos. Usa EventEmitter.defaultMaxListeners o limites equivalentes
  • Bypass de async middleware: si las cadenas de async middleware no se awaited correctamente, un middleware puede saltarse. Usa composicion de middleware a nivel framework que enforcee await en cada handler
  • Condicion de carrera en rate limiting async: si el estado de rate limiting se chequea y actualiza en operaciones async separadas, requests concurrentes pueden bypassar el limite. Usa operaciones atomicas de check-and-increment
  • Promise prototype pollution: si un atacante puede modificar Promise.prototype, todo el codigo async que usa promises esta comprometido. Usa Object.freeze(Promise.prototype) en produccion o corre con strict mode
  • Bypass de cleanup async en shutdown forzado: si un proceso se mata con SIGKILL, los handlers de cleanup async no se ejecutan. Usa SIGTERM con un grace period e implementa cleanup en signal handlers antes del drenado async
  • Agotamiento de pool de recursos async compartido: si multiples consumidores async comparten un pool de conexiones sin limites, un pico en un consumidor puede starvar a otros. Implementa quotas por consumidor en pools de recursos async compartidos
  • Async logger bloqueante: si el logging es sincrono dentro de un handler async, bloquea el event loop. Usa logging async con buffers acotados para prevenir que el logging bloquee el event loop
  • Cancelacion de coroutine ignorando locks: si una coroutine se cancela mientras mantiene un lock, el lock puede no liberarse. Usa context managers o bloques finally para asegurar la liberacion del lock al cancelar
  • Async deserialization bombs: parsear payloads JSON grandes con wait response.json() puede consumir memoria antes de que la validacion se ejecute. Setea limites de Content-Length en el gateway y usa streaming parsers para payloads grandes
  • Event loop starvation via microtask flooding: si un solo request agenda miles de microtasks (ej. Promise.resolve().then() recursivo), starva otros requests. Limita la creacion de microtasks por request