Back to flin
flin

Cuando la VM se bloqueó en la creación de entidades

Cómo el sistema de acciones de FLIN creó una condición similar a un deadlock donde la creación de entidades en funciones fallaba silenciosamente, y el trabajo detectivesco que lo rastreó.

Thales & Claude | March 30, 2026 5 min flin
EN/ FR/ ES
flinrust

Un deadlock en el sentido clásico involucra dos hilos esperándose mutuamente, cada uno sosteniendo un recurso que el otro necesita. Ninguno puede avanzar. El sistema se congela. Pero existe una variante más sutil que no involucra hilos en absoluto -- un deadlock lógico donde una secuencia de operaciones crea una dependencia circular que impide cualquier progreso.

FLIN encontró esta variante durante el desarrollo de su sistema de acciones. El síntoma era reminiscente de un proceso que escribe en una tubería stderr llena y se bloquea para siempre -- excepto que en nuestro caso, era la VM encontrando un opcode que no entendía y vagando por la memoria de bytecode hasta golpear algo que causaba un retorno silencioso.

La arquitectura del sistema de acciones

El sistema de acciones de FLIN conecta la interactividad del navegador con la lógica del lado del servidor. Cuando un usuario hace clic en un botón en una aplicación FLIN, el navegador envía una solicitud POST al endpoint /_action. El servidor entonces:

  1. Lee el nombre de la acción y el estado actual de la página desde la solicitud
  2. Carga el archivo fuente FLIN de la página referente
  3. Compila el fuente con la llamada a función añadida
  4. Crea una nueva instancia de VM
  5. Inyecta el estado del navegador en la VM
  6. Ejecuta el bytecode compilado
  7. Verifica si alguna entidad fue modificada
  8. Devuelve {"type":"reload"} (entidades cambiaron) o {"type":"ok"} (sin cambios)

El detalle crítico es el paso 5: la VM se crea fresca para cada solicitud de acción. No tiene memoria de solicitudes anteriores. No comparte estado con la VM que renderizó la página. Cada acción se ejecuta en aislamiento.

El patrón de deadlock

El deadlock surgió de la interacción entre tres componentes: el manejador de acciones, la VM y el despachador de bytecode.

Cuando el manejador de acciones recibía una solicitud para llamar a addTask(), compilaba el fuente completo de la página con addTask() añadido al final. La función contenía creación de entidades:

flinfn addTask() {
    task = Task { title: newTitle }
    save task
}

El compilador generaba bytecode correcto. El método principal execute() de la VM podía ejecutarlo sin problema. Pero el manejador de acciones no usaba execute() -- usaba execute_until_return, que tiene su propia tabla de despacho de opcodes.

Cuando execute_until_return encontraba el opcode CreateEntity, no encontraba ningún manejador. El caso predeterminado avanzaba el puntero de instrucción un byte -- pero CreateEntity es una instrucción de cuatro bytes. El IP avanzaba al medio de los operandos de la instrucción. El siguiente byte era interpretado como un opcode. El IP vagaba por el bytecode como una aguja saltando surcos en un disco.

El sistema no estaba congelado en el sentido tradicional -- respondía rápidamente a cada solicitud. Pero estaba lógicamente bloqueado: el usuario repetidamente hacía clic en "Add Task", el servidor ejecutaba la función, y la función fallaba en crear cualquier entidad. No era posible ningún progreso.

El desafío diagnóstico

Lo que hacía este error excepcionalmente difícil de diagnosticar era la ausencia de cualquier señal de error. La respuesta HTTP era 200 OK. Los registros del servidor no mostraban errores. La consola del navegador estaba limpia. La compilación era exitosa. Todas las 2.248 pruebas pasaban.

Cada métrica observable indicaba un sistema saludable. El error existía en la brecha entre "la función se ejecutó" y "la función se ejecutó correctamente".

Rastreando la ruta de ejecución

Atacamos el problema desde tres ángulos simultáneamente: verificación de bytecode, registro de ejecución de la VM y un contador de operaciones de entidades. El contador fue la confirmación decisiva -- cuando era cero después de ejecutar una función que debería haber guardado una entidad, teníamos prueba definitiva.

El problema sistémico

El error revelaba un problema sistémico en la arquitectura de FLIN: la existencia de dos tablas paralelas de despacho de opcodes que deben mantenerse sincronizadas. Más de 30 opcodes, abarcando cada categoría, necesitaban existir en execute_until_return.

Lecciones del deadlock

Las tablas de despacho paralelas son un peligro de mantenimiento. Cuando dos rutas de código deben manejar el mismo conjunto de casos, inevitablemente se desincronizarán.

Los fallos silenciosos requieren detección proactiva. El contador de operaciones de entidades fue el diagnóstico clave -- no verificaba errores sino la ausencia de resultados esperados.

Prueba la ruta de creación separadamente de la ruta de modificación. La creación y la modificación de entidades son operaciones fundamentalmente diferentes.

La VM no se bloqueó en el sentido de libro de texto. Ningún hilo estaba bloqueado. Pero el efecto fue el mismo: un sistema que parecía funcionar pero no hacía ningún progreso. Un deadlock de intención, si no de implementación.


Esta es la Parte 160 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abidjan y un CTO de IA diseñaron y construyeron un lenguaje de programación desde cero.

Navegación de la serie: - [159] El error de renderizado de espacios en blanco HTML - [160] Cuando la VM se bloqueó en la creación de entidades (estás aquí) - [161] El error de seguimiento de versiones temporales

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles