La Hora en Erlang

Cuando hablamos de Erlang y enumeramos sus características entre ellas aparece el tiempo real blando (soft realtime). Erlang se ejecuta en una máquina virtual que tiene una gestión de procesos, memoria y acceso a recursos, pero lo que no había citado hasta el momento es su capacidad para controlar los eventos horarios. Sí, tiene gestión horaria dentro de su máquina virtual también, ¿para qué?, ¿cómo funciona?

Desde el principio: Tiempo monótono.

El tiempo real de un ordenador se ve modificado constantemente, se reajusta saltando hacia adelante o a veces hacia atrás para obtener siempre la hora del día. Incluso si empleamos técnicas como el uso de un cliente NTP, este cliente puede ajustar el reloj local para ajustarlo a la hora “real” medida con precisión por otro sistema.

Si necesitamos que un evento en el tiempo tarde un tiempo específico o tenga un tiempo de duración máxima (timeout), necesitamos que el tiempo se mantenga constante, avanzando siempre y con la misma velocidad a ser posible. Decimos que necesitamos una medición de tiempo monótona (en ingles monotonic time).

Además, podemos decir que un sistema emplea un tiempo monótono estricto únicamente si siempre que es llamado nos proporciona un valor diferente y ascendente con respecto al anterior. En Erlang, hasta la versión 18 hemos podido emplear erlang:now/0 como una marca de tiempo monótona estricta. Pero esto tiene un alto coste.

Debido al coste computacional de erlang:now/0, en la versión 18 se introducen un nuevo conjunto de funciones de tiempo. Esta vez empleando tiempo monótono no estricto, es decir, el tiempo puede retornar valores repetidos pero nunca ir hacia atrás.

Esta nueva forma de obtener el tiempo ha hecho posible incrementar el rendimiento de temporizadores (timers) y gestión de eventos.

La hora de Erlang y la hora del sistema operativo

Para que el sistema sea fiable finalmente debe mantener la hora dentro de la máquina virtual, la hora monótona, y poder proporcionar la hora del sistema operativo. Esto lo conseguimos con las siguientes funciones:

  • erlang:monotonic_time/0 o erlang:monotonic_time/1: permite obtener el tiempo monótono representado por un valor negativo siempre ascendente.
  • erlang:system_time/0 o erlang:system_time/1: permite obtener la hora de la máquina virtual de Erlang.
  • erlang:time_offset/0 o erlang:time_offset/1: permite ver la diferencia entre el reloj de la máquina virtual de Erlang y el reloj del sistema.

La hora del sistema operativo se puede obtener en el formato antiguo de {MegaSec,Sec,MicroSec} con la función os:timestamp/0, la equivalente en este mismo formato para obtener la hora de Erlang era anteriormente erlang:now/0. A partir de la versión 18 es más aconsejable emplear erlang:timestamp/0.

¿Cómo era antes de la versión 18?

La hora monótona estricta se obtenía con erlang:now/0 tal y como he mencionado antes, pero esto tiene varios problemas, el más significativo es el cuello de botella que puede suponer si es empleada de forma constante y en realidad no se necesita un tiempo monótono estricto, sino tan solo uno monótono. Siempre cabe la posibilidad de emplear os:timestamp/0, pero esta función no retorna valores monótonos.

Además, el reloj interno de la máquina virtual de Erlang tiende a compensarse incrementando o decrementando su frecuencia de actualización para adaptarse paulatinamente al reloj del sistema.

Este comportamiento puede ser contraproducente si nuestro sistema depende del disparo de eventos basados en tiempo a lo largo del cluster debido a que el sistema lanzará los eventos con un poco de mayor o menor frecuencia que el resto de nodos del cluster, según sea el caso.

Para evitar este comportamiento siempre podemos emplear la optión +c que deshabilita este ajuste de hora.

¿Y ahora en la versión 18?

Las funcionalidades de erlang:now/0 se han partido en varias funciones:

  • Obtención de un identificador único: se puede hacer directamente con erlang:unique_integer/0 o erlang:unique_integer/1. Si queremos enteros solo positivos y monótonos estrictos tendremos que pasar estos parámetros a la función: [postive, monotonic].
  • Obtención de una marca de tiempo monótona estricta: emplearemos de nuevo la función erlang:unique_integer/1, con las opciones [monotonic]. Esto nos asegura una progresión monótona, aunque los números puedan comenzar por valores positivos o negativos sin representar la hora real del sistema.
  • Obtención de una marca de tiempo monótona no estricta: para este caso podemos emplear erlang:monotonic_time/0.

Así mismo, la opción de la máquina virtual +c ha adoptado varias opciones posibles, según el tipo de túnel del tiempo que queramos usar.

Túneles del tiempo… ¿qué son?

Básicamente es la configuración del desplazamiento que se configurará para ajustar el reloj de la máquina virtual de Erlang con la hora del sistema cada vez que haya un cambio de hora en el sistema.

Tenemos varias configuraciones posibles que se ajustan con la opción +C. Entre ellas (tomado de las definiciones del libro Learn You Some Erlang…):

  • +C no_time_warp +c true: es la opción por defecto y se mantiene por retrocompatibilidad con las versiones anteriores de Erlang. No se usa túnel de tiempo y se mantiene activo el ajuste.
  • +C no_time_warp +c false: es la misma opción que si deshabilitamos el túnel de tiempo, o si empleamos solo +c false.
  • +C multi_time_warp +c true: el desplazamiento temporal es ajustado hacia adelante y hacia atrás para coincidir con la hora del sistema operativo. El reloj monótono permanece estable y preciso.
  • +C multi_time_warp +c false: el desplazamiento temporal es ajustado hacia adelante y hacia atrás para coincidir con la hora del sistema operativo. No hay corrección de tiempo por lo que el reloj monótono podría pausarse brevemente.
  • +C single_time_warp +c true: no se realizan ajustes de tiempo pero intenta mantenerse el reloj monótono lo más estable posible.
  • +C single_time_warp +c false: se mantiene igual que no_time_warp.

En el caso de single_time_warp se puede llamar a erlang:system_flag(time_offset, finalize) para indicar al sistema que puede tomar el offset en ese momento. Es útil para sistemas embebidos donde se emplea el ajuste del reloj solo al inicio del sistema y podemos o queremos ajustar el reloj tras este ajuste.

De todas formas, por la complejidad del ajuste, se recomienda mantener el código seguro o emplear en su lugar siempre no_time_warp.

¿Cómo mantener el código seguro?

La documentación de Erlang nos dicen cómo mantener el código seguro en estos casos y en el libro Learn You Some Erlang… nos sintetizan esta información en unos sencillos puntos a seguir cuando queramos utilizar el tiempo, ya sea por eventos o por el uso de fechas y horas:

  • Obtener la fecha y/o hora del sistema: erlang:system_time/0-1.
  • Medir diferencias de tiempo: erlang:monotonic_time/0-1 llamando antes y después del evento a medir y realizar la resta.
  • Definir un orden absoluto entre eventos en un nodo: erlang:unique_integer([monotonic]).
  • Medir el tiempo y asegurar que un orden absoluto ha sido definido: {erlang:monotonic_time(), erlang:unique_integer([monotonic])}.
  • Crear un nombre único: erlang:unique_integer([positive]).
  • Crear un nombre único en un cluster (igual que el anterior pero agregar el nombre del nodo).

Siguiendo estas guías, el código debe mantenerse seguro respecto a los túneles de tiempo.