Back home

La confiabilidad del tiempo de ejecución de Rust/Wasm requiere manejar tanto la recuperación de pánico como la de aborto

Una vez que la instancia compartida de Wasm comienza a aceptar llamadas durante un período prolongado, el bloqueo pasará de una falla única a un problema de recuperación de estado y aislamiento de fallas.

Al principio, Wasm puede considerarse fácilmente como una capa de portabilidad: el código se puede programar, la página se puede ejecutar, el rendimiento es bueno y todo parece ser más o menos igual. Realmente empieza a ponerse difícil, generalmente después de pasar la demostración. Una vez que módulos como editores, renderizadores y analizadores de documentos pasen de experimentos de una sola página a tiempos de ejecución residentes a largo plazo, los modelos de fallas cambiarán inmediatamente.

En este momento, el pánico y el aborto ya no son ramas excepcionales en la capa del lenguaje. Lo que deciden es: si esta instancia puede continuar recibiendo trabajo posterior, si el estado de la memoria está contaminado, si la capa de host debe descartar la instancia inmediatamente y si es necesario llenar el grupo de instancias. Cuando el equipo móvil traslada a la Web un kernel que se ha estado ejecutando en contenedores nativos durante mucho tiempo, es esta capa de cambio la que se subestima más fácilmente.

Después de pasar la demostración, el modelo de falla acaba de comenzar.

Los fallos en una sola llamada no son difíciles de entender. Un clic en un botón activa una llamada Wasm. Si falla, se informará un error en la operación. Actualiza la página y vuelve a intentarlo. El costo aún es controlable.

El problema ocurre después de que el tiempo de ejecución comienza a reutilizar instancias. Cuando la misma instancia de Wasm abre continuamente varios documentos, recibe varias rondas de eventos de entrada y pasa por varias llamadas de puente JS, el alcance de la influencia del pánico y el aborto ya no se detiene en la acción actual. Una falla incompleta puede retrasar solicitudes posteriores.

A menudo, estos riesgos no se exponen el primer día. En la primera etapa, normalmente solo verá informes de errores dispersos: fallas ocasionales en la representación, una determinada exportación está bloqueada y un determinado documento se encuentra en un estado incorrecto después de cerrarlo y reabrirlo. Si sigues investigando, las pistas convergerán gradualmente hacia el mismo fenómeno: aunque el fallo ocurrió en una cadena de llamadas, el daño permaneció en la instancia compartida.

En este punto, el foco de la discusión ya no es “si el código Rust entrará en pánico”, sino “si este tiempo de ejecución está calificado para continuar atendiendo la siguiente llamada después del pánico”.

Se puede detectar el pánico, la cancelación solo puede cambiar de instancia.

Lo más importante a separar en Rust/Wasm son las dos semánticas de falla, pánico y aborto.

El pánico también tiene la oportunidad de retroceder a lo largo de los límites establecidos. Siempre que la capa de enlace y la capa de host acuerden el método de recuperación de antemano, la llamada actual puede fallar y también se pueden mantener otros estados en la instancia. Abortar no es el camino a seguir en absoluto. Significa que la ejecución actual ha alcanzado un estado irrecuperable. Si continúa usando la misma instancia para recibir solicitudes, básicamente está apostando a que la memoria y los recursos no se dañarán a mitad de camino.

Una vez que los dos se mezclan durante el tiempo de ejecución, definitivamente ocurrirán problemas en el procesamiento posterior:

  • El aborto de trago es una excepción normal y el grupo de instancias continuará reutilizando objetos que han perdido credibilidad.
  • Trate todos los pánicos como si la instancia debiera destruirse y el rendimiento se reduciría innecesariamente.
  • El host JS sólo sabe “la llamada falló”, pero no sabe si volver a intentarlo, perder la instancia o cortar la sesión actual.

Esto también es lo más realista acerca de la confiabilidad del tiempo de ejecución de Wasm: la semántica de recuperación debe definirse primero antes de que se pueda implementar el aislamiento y la programación posteriores.

Si la capa de enlace no proporciona semántica de recuperación, la capa de host aceptará el mal estado y continuará aceptando el trabajo.

El lugar más peligroso para este tipo de problemas no es el código comercial, sino la capa vinculante que parece “ya haber sido solucionada”. La capa de host a menudo solo ve un objeto de error generado y lo registra como una falla de llamada normal. El registro está ahí y la página no falla inmediatamente, pero es posible que el sistema haya dejado el mal estado dentro de la instancia.

Lo que realmente necesita arreglarse no es sólo intentar/capturar, sino las acciones de manejo después del fallo. Una lógica similar a la siguiente acaba de comenzar a incorporarse al diseño de confiabilidad:

async function runWithRecovery(instance, input) {
  try {
    return await instance.exports.handle(input)
  } catch (error) {
    if (isAbort(error)) {
      pool.replace(instance.id)
    }
    throw error
  }
}

El foco de este código no está en la sintaxis, sino en un simple juicio: si el error actual ha marcado esta instancia como un objeto que no es de confianza. Si la respuesta es sí, la acción de recuperación no debe detenerse en generar errores, sino que debe continuar hasta la eliminación de instancias, la reconstrucción de recursos y el corte del flujo de solicitudes.

Mientras esta capa no esté claramente definida, parecerá que el sistema está manejando errores, pero lo que en realidad está haciendo es devolver un tiempo de ejecución potencialmente dañado a la ruta de producción.

Las instancias compartidas amplificarán el problema de recuperación hasta convertirlo en un problema de estrategia de agrupación.

Después de que Wasm se incorpora a productos reales, rara vez hay “una instancia hasta que se cierra la página”. Más comunes son los grupos de instancias, los grupos de trabajadores o los documentos en primer plano y las tareas en segundo plano que comparten un conjunto de recursos de tiempo de ejecución. En esta etapa, los costos de recuperación del pánico y el aborto reescribirán directamente la estrategia de agrupación.

Si la inicialización de la instancia es costosa, el sistema naturalmente tenderá a reutilizarla tanto como sea posible. Pero una vez que se establece la reutilización, el aislamiento de fallas debe actualizarse simultáneamente:

  • Qué estados sólo se pueden colgar en una única llamada, y se descartarán con la llamada después del fallo
  • Qué cachés se pueden retener en las llamadas y qué cachés se deben invalidar por completo una vez que se produce un aborto.
  • Después de reemplazar la instancia, ¿cómo se migrarán las tareas en cola? ¿Volver a intentarlo causará efectos secundarios dos veces?

Estas no son respuestas que la capa de idioma enviará automáticamente. Son diseños en tiempo de ejecución.

Debido a esto, si la discusión sobre la confiabilidad de Rust/Wasm solo se detiene en “¿se puede detectar el pánico?”, es fácil subestimar el problema. Lo que realmente amplía la brecha en los costos de mantenimiento es si el grupo de instancias puede mantener un límite de confianza claro después de una falla.

El límite aplicable está fuertemente relacionado con el ciclo de vida.

Este conjunto de diseños de restauración no es necesario para todos los proyectos de Wasm.

Si el módulo es solo una herramienta fuera de línea de una sola vez, o si toda la instancia se recicla cuando se destruye la página, entonces la diferencia entre pánico y cancelación seguirá existiendo, pero el beneficio de recuperación será mucho menor. A menudo basta con actualizar la página directamente y volver a ejecutar la tarea directamente.

Una vez que el sistema tenga las siguientes características, la semántica de recuperación cambiará rápidamente de un “elemento de optimización” a un “elemento de infraestructura”:

  • La instancia reside durante mucho tiempo y no se destruye junto con el ciclo de vida de una sola página.
  • La misma instancia realiza continuamente múltiples rondas de llamadas.
  • La capa de alojamiento necesita utilizar la agrupación a cambio del tiempo de inicio y el rendimiento.
  • Proteger el estado de la sesión, el estado de la caché y las tareas en cola después de una falla

Cuando los equipos móviles trasladan capacidades nativas a la Web, es más probable que se encuentre este límite. La relación de aislamiento que se estableció originalmente de forma predeterminada en el proceso de la aplicación a menudo tenía que volver a completarse después de alcanzar el límite del host JS/Wasm.

Wasm facilita que el código nativo ingrese al navegador, pero no trae consigo la semántica de recuperación en tiempo de ejecución. Tan pronto como el sistema comienza a compartir instancias, reutilizar estados y aceptar llamadas a largo plazo, el pánico y el aborto deben tratarse como dos eventos de tiempo de ejecución diferentes. Al primero le importa cómo finalizar la llamada actual y al segundo le importa si esta instancia puede continuar viviendo en el grupo. Si no se hace este juicio primero, cuanto más exitoso sea el trasplante de código, más difícil será lidiar con fallas posteriores.

FAQ

What to read next

Related

Continue reading