Tema representativo de entrevista

Entrevista técnica: ¿Cómo debe asyncio gestionar un deadline dinámico?

CodingDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Una solicitud asíncrona conoce su presupuesto restante solo después de leer los metadatos de upstream. Diséñela con asyncio.timeout o timeout_at, y explique reschedule, expired, CancelledError, TimeoutError, anidamiento y wait_for.

Prompt y contexto

Una solicitud asíncrona lee metadatos de enrutamiento antes de conocer el presupuesto para las llamadas downstream. El diseño debe utilizar el reloj monotónico del bucle de eventos, crear un timeout sin deadline inicial, reprogramar un deadline absoluto una vez que lleguen los metadatos y mantener la cancelación del llamador diferenciada de un timeout.

Python documenta asyncio.timeout() como un administrador de contexto asíncrono reprogramable y timeout_at() como un deadline absoluto basado en el reloj del bucle de eventos. La cancelación causada por el contexto se transforma en TimeoutError fuera del contexto.

Qué evalúa el entrevistador

Busque una distinción clara entre duración relativa y deadline absoluto, timeout de negocio y cancelación externa, además del uso correcto de reschedule(), expired(), anidamiento y wait_for(). Las respuestas sólidas propagan un único presupuesto a cada E/S y limpian los recursos.

Preguntas aclaratorias para hacer primero

  • ¿Qué componente es dueño del presupuesto y cuánto se consumió leyendo los metadatos?
  • ¿El cliente HTTP, la base de datos y la cola aceptan una señal de timeout o cancelación?
  • ¿Las llamadas secundarias deben compartir un deadline absoluto o tener presupuestos independientes?
  • ¿El timeout es una respuesta degradada, un reintento o un error?
  • ¿Cuál es la versión mínima de Python en producción?

Estructura de respuesta en 30 segundos

“Calculo un deadline absoluto con loop.time(). Comienzo con asyncio.timeout(None) y luego llamo a cm.reschedule(deadline) después de que llegan los metadatos. La cancelación creada por el contexto se convierte en TimeoutError cuando este finaliza, por lo que la capturo afuera; un CancelledError externo sigue siendo cancelación después de la limpieza. Cada operación downstream recibe el presupuesto restante y las llamadas anidadas no pueden restablecer el timeout completo. Utilizo cm.expired() para diagnóstico y pruebo condiciones de carrera.”

Análisis detallado paso a paso

Paso 1: Expresar el deadline con un reloj monotónico

No calcule el tiempo restante a partir del tiempo de reloj de pared porque las correcciones de reloj pueden dar saltos. Utilice loop.time() y pase un único deadline absoluto a cada operación downstream.

python
loop = asyncio.get_running_loop()
deadline = loop.time() + 2.0
async with asyncio.timeout_at(deadline):
    await call_dependency(deadline)

Paso 2: Iniciar un contexto reprogramable

Cuando el presupuesto sea desconocido, utilice asyncio.timeout(None) as cm. Después de que lleguen los metadatos, calcule el deadline absoluto y llame a cm.reschedule(deadline). Mantenga un solo contexto para que el trabajo preliminar y las llamadas downstream compartan un único límite.

Paso 3: Propagar el presupuesto restante

Cada cliente calcula deadline - loop.time() y trata un valor no positivo como un fallo inmediato. Pasar el mismo deadline evita que las capas de enrutamiento, base de datos y HTTP otorguen, cada una, un timeout relativo completo.

Paso 4: Comprender la conversión de cancelación a timeout

El contexto cancela la Task actual internamente y convierte esa cancelación en TimeoutError al salir. En consecuencia, TimeoutError debe capturarse fuera de async with, no dentro de él.

python
try:
    async with asyncio.timeout(1.0):
        await slow_call()
except TimeoutError:
    return degraded_result()

Paso 5: Preservar la cancelación externa

Si un cliente se desconecta o un proceso padre cancela la solicitud, CancelledError no es un timeout de negocio. Libere conexiones, bloqueos y archivos temporales, y luego propáguela nuevamente mediante raise en lugar de devolver un resultado degradado exitoso.

Paso 6: Comparar timeout, timeoutat y waitfor

timeout(delay) utiliza un retraso relativo y se puede reprogramar; timeout_at(when) toma un deadline monotónico absoluto y es ideal para la propagación. wait_for(aw, timeout) apunta a un único awaitable, lo cancela al agotarse el tiempo y puede esperar a que la cancelación finalice. Componer muchas llamadas a wait_for puede exceder el presupuesto de una solicitud.

Paso 7: Manejar el anidamiento y la expiración

Los contextos de timeout pueden anidarse; un deadline interno no debe exceder al externo. Después de salir, cm.expired() indica si el contexto realmente alcanzó su deadline. Las excepciones ordinarias, la cancelación externa y los resultados exitosos deben seguir siendo diferenciables.

Paso 8: Probar condiciones de carrera y limpieza

Pruebe metadatos retrasados, un deadline ya expirado, un timeout interno primero, cancelación externa y timeout simultáneos, una operación downstream que ignora la cancelación, reschedule(None), reprogramaciones repetidas y fallos en la limpieza. Registre el deadline, el presupuesto restante, el motivo de cancelación y la duración downstream sin incluir datos confidenciales.

Respuesta modelo de alta calidad

“Creo un deadline absoluto con loop.time() y lo paso hacia downstream. Cuando el presupuesto es desconocido, entro en timeout(None) y reprogramo después de que llegan los metadatos. El contexto convierte su cancelación interna en TimeoutError fuera del bloque; el CancelledError externo continúa propagándose. Las llamadas anidadas usan el deadline más cercano, y los clientes y bases de datos reciben el presupuesto restante. Las pruebas de concurrencia verifican que no se fuguen Tasks ni recursos.”

Errores comunes

  • Usar time.time() para un deadline → las correcciones del reloj cambian el presupuesto → use loop.time().
  • Capturar TimeoutError dentro del contexto → la excepción transformada no se encuentra allí → captúrela afuera.
  • Tratar la cancelación externa como un timeout → la cancelación del cliente se convierte en un falso resultado degradado → mantenga diferenciados los tipos de excepción.
  • Otorgar un timeout completo en cada capa → la latencia total excede el contrato → propague un único deadline absoluto.
  • Ignorar la espera de cancelación de wait_for → la duración real excede el timeout numérico → mida la convergencia de la cancelación.
  • Probar únicamente el éxito y un solo timeout → las rutas con condiciones de carrera fugan recursos → pruebe reschedule, cancelación simultánea y fallos de limpieza.

Preguntas de seguimiento y respuestas sólidas

Pregunta de seguimiento 1: ¿Por qué timeout_at es más estable que un timeout por capa?

Cada capa apunta al mismo instante absoluto, por lo que el redondeo de tiempo relativo y los presupuestos repetidos no pueden acumularse más allá del contrato de la solicitud.

Pregunta de seguimiento 2: ¿Qué sucede si reschedule recibe un deadline pasado?

El contexto expira en la siguiente oportunidad del bucle de eventos. Verifique el presupuesto restante antes de reprogramar y evite iniciar E/S no cancelables cuando ya esté agotado.

Pregunta de seguimiento 3: ¿Cómo respeta una base de datos el deadline?

Pase los segundos restantes a un statement timeout o a una API de cancelación. Cancelar únicamente la Task de Python no detiene necesariamente una consulta del lado del servidor.

Pregunta de seguimiento 4: ¿Qué registra cuando la cancelación y el timeout compiten en una carrera?

Conserve el motivo de cancelación del padre y el recuento de cancelaciones, y propague la cancelación externa. Guarde expired() como un campo de diagnóstico en lugar de etiquetar cada salida como un timeout de negocio.

Pregunta de seguimiento 5: ¿Cuándo elegiría wait_for?

Utilícelo para un único awaitable con un límite relativo simple. Prefiera timeout o timeout_at cuando las llamadas compartan un deadline dinámico o un presupuesto a nivel de toda la solicitud.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Captura para un ejercicio de código

Captura el problema y luego aborda en orden las restricciones, la solución, el código, los casos extremos y la complejidad.

Ver la herramienta