Últimos Cambios |
||
Blog personal: El hilo del laberinto |
Última Actualización: 24 de Febrero de 2000 - Jueves
Aunque el objetivo final es implementar un esquema distribuido, para acelerar el nacimiento de la red se ha empezado por utilizar un esquema centralizado empleando las facilidades proporcionadas por los servidores IRCd de Undernet.
En estos momentos los nodos de control son:
Las operaciones que cubren esos nodos son:
:nodo_origen PING nodo_origen :nodo_destino
:nodo_origen PONG nodo_origen nodo_destino
Como ventaja adicional, también se responde a PING para aquellos servidores que están por detrás de nosotros (JUPE).
Esta versión ha estado funcionando durante 13 días sin problemas, mientras se desplegaba la pasarela de pagos y las páginas de información sobre la contratación de clones.
No implemento esa funcionalidad en el resto de herramientas porque en cuanto se puedan gestionar los clones directamente online, sobrarán o se utilizarán muy de vez en cuando.
De todas formas, si se queda algún bloqueo perdido por ahí, lo más sencillo es parar todos los procesos y borrar las cachés "__db.xxx". Claro que, como no estamos trabajando con transacciones, todo lo que podemos hacer es rezar para que la base de datos no se haya corrompido.
También se puede utilizar la herramienta db_recover, que se supone que hace esto mismo y tiene en cuenta la posible existencia de transacciones, etc.
Quedan por pulir muchos detalles, eso sí.
Inicio una migración a DB como sustituto a la GDBM.
Para poder enlazarla tengo que hacer equilibrios en el Makefile.
Los programas mueren al hacer open sobre la base de datos. No me sirve de nada el estudiar programas antiguos como sendmail porque emplean la interfaz antigua. Prácticamente copiando los ejemplos del manual tampoco llego a nada.
Tras varias horas de investigar el tema, estudiar el funcionamiento interno de la librería DB, etc, encuentro por fin el problema: El GCC 2.95.2 genera stores de 64bits cuando se invoca memset, y UltraSparc I requiere que dichos accesos estén alineados a 64 bits. No obstante la librería sólo los alinea a 32 bits.
El problema es del compilador (o de su librería estándar) ya que si, por ejemplo, estamos trabajando con cadenas con alineamiento arbitrario, un memset puede matar el programa por problemas de alineamiento.
Haciendo pruebas en mis propios programas veo que típicamente memset es una invocación de una rutina externa. Sólo se generan este tipo de movimientos de datos cuando el tipo de datos al que apunta el puntero se alinea de forma natural a 64bits (por ejemplo, estructuras).
Informo del asunto a los autores de la librería DB y en la lista de correo del GCC/EGCS.
Vaya... No debemos hacer free del contenido de clave y contenido, al contrario que pasa con GDBM. Eso es configurable. Le paso la rutina "malloc()" del resto de la aplicación, y le digo que queremos hacer gestión explícita de memoria.
Uso la herramienta truss y compruebo que la base de datos se va leyendo en memoria a medida que se va accediendo a ella, sin usar mmap. A medida que se va leyendo, lo único que queda visible son los bloqueos sobre el fichero. La duda ahora es... Si la base de datos no se cierra, ¿cómo se tiene acceso a las actualizaciones de otros programas sobre ella?.
La respuesta es, parece ser, ninguna.
Por lo que se ve, debe realizarse caching de la apertura de la base de datos, ya que cualquier registro leído no se sincroniza con lo que puedan estar haciendo otros procesos. Podría ser debido al acceso de escritura en la base de datos, ya que se supone que es exclusivo, aunque con la nueva versión de la librería ya no tiene por qué ser así. Si un proceso de escritura tiene acceso exclusivo a la base de datos, el hacer caching de los registros que se leen es correcto, ya que no pueden ser modificados a menos que se cierre primero la base de datos.
La solución parece ser, por tanto, el abrir y cerrar la base de datos en cada acceso, con la consiguiente sobrecarga (un sistema operativo decente no debería tener mucha sobrecarga, ya que se usa la memoria como caché de disco).
No obstante esto no explica el hecho de que un proceso haga una modificación en un registro, y que dicha modificación se "pierda" cuando se mata el proceso... A menos que el no cerrar la base de datos COMPLETAMENTE mediante gdbm_close intente dejarla en un estado consistente, deshaciendo posibles cambios. Pero no, porque todos los cambios se comprometen a disco, y cuando se mata el programa se guarda el "profiling" y nada más.
Estudio someramente el código de la librería gdbm y parece que estoy en lo cierto respecto al caching. Pero sigue sin explicarse el que se "deshagan los cambios".
De momento voy a quitar el caching de apertura de la base de datos, para que cada nueva consulta sea una nueva sesión. Ello debería eliminar el caching de registros leídos, aún a costa de perder eficiencia.
Eliminar este caching de sesiones supone una carga inmensa en el servidor, sobre todo cuando el servidor se conecta a la red.
De todas formas sin el caching parece que todo va bien (aunque muy lento). Los registros se borran a la primera y permanecen aún cuando se abandona el programa.
La más inteligente parece el mantener el sistema de caching, pero canalizar todas las peticiones a esa base de datos a través de un único descriptor. Ello supone crear un módulo aparte, de gestión GDBM.
Como prueba de concepto sencillamente voy a abrir una sesión global a nivel de un proceso.
Bien, ahora cuando borro un registro, la siguiente búsqueda mata el servidor con una excepción, en al siguiente búsqueda de iline. Como solución de compromiso, cuando realizo un borrado, cierro la base de datos y la vuelvo a abrir.
Era un error de programación, pero lo dejo así de todas maneras, que parece más "coherente".
A pesar de todos estos cambios, parece que sigue fallando de vez en cuando, sobre todo porque dice que ha borrado y no lo ha borrado aún, etc.
Regenerando el fichero de ilines de nuevo soluciona el problema pero, obviamente, no es solución, sobre todo si algún día vamos a pasar del archivo de texto en plan "backup".
Lo más extraño es que la clave que devuelve un "nextkey" mide 22 bytes, cuando la cadena mide sólo 19 (incluyendo el '\0'). Pero el problema no es ese, sino que al hacer un "fetch" de dicha clave, no la encuentra, ¡¡y nos la acaba de dar en "nextkey"!!.
Si vuelve a ocurrir, habrá que blindar "ilines_lista" para que simplemente se salte estas claves problemáticas. Con suerte aparecerán dos registos; uno con la clave correcta y otro con la clave correcta.
Definir el símbolo "DEVELOP" al compilar el código, supone:
La gran ventaja de este esquema es que se puede modificar Olimpo y lanzarlo de nuevo, sin perder los informes históricos.
El problema es que estábamos contando como conexiones nuevas también los cambios de nicks y los cambios de modos de usuario. Solucionado. Ahora los dos contadores de conexiones deberían coincidir.
A la hora de alimentar el generador de números aleatorios para generar las claves, se tiene en cuenta toda la entropía posible: todo lo que se va recibiendo por la red, la hora de cada comando recibido, etc. Todo eso se mezcla con la salida de un generador pseudoaleatorio, inicializado con la hora de lanzamiento del bot. Esa hora no es tiempo real, sino el resultado del "gethrtime()" que da el tiempo en nanosegundos, con una precisión de 64 bits.
De esta forma el generador de números aleatorios conseguidos es de buena calidad. Al menos siempre que su invocación sea espaciada y la red tenga un tráfico elevado (situaciones ambas muy normales en una red de IRC).
Se amplía stats para que muestre la versión de la base de datos que existe en su HUB inmediato. Cuando el otro extremo no soporta base de datos distribuída, el valor es -1.
Los nicks a proteger se ponen en el fichero "squit.raw", siguiendo la sintáxis del protocolo P10 servidor-servidor, en cuanto a NICK. Las líneas del fichero se leen por orden y se envían al HUB tal cual, sin ningún procesado o interpretación. Ello implica, por tanto, que hay que tener cuidado con cosas como el inyectar nuevos usuarios a través de un número de nodo que no nos corresponde, o usar un NumNick en uso. Además, los comandos RAW pueden matar fácilmente el HUB.
Dado que el fichero se lee cada vez que hay un Squit, hay que tener cuidado para que no haya un squit EN MEDIO de una modificación del fichero. Esto puede ser CATASTRóFICO.
Para poder introducir nuevos NICKS he tenido que modificar la Numeric NickMask, pasando de "??" a "D]". Por lo que veo, este valor indica el máximo número de nicks que maneja un nodo, hasta un máximo de 4096. He configurado Olimpo para que soporte 1+64*3+63=256 nicks.
Es importante que:
Existe una ventana de desprotección de los nicks, en función del lag de la red. Es inevitable.
Con la reconfiguración de IRC-Hispano, Olimpo pasa a controlar los clones en dicha red. Ello hace necesarias numerosas modificaciones en el código, ya que muchos valores ESNET están "cableados" y es preciso moverlos a ficheros de configuración externos.
Una de mis mayores sorpresas ha sido el poco consumo de memoria y de CPU que ha supuesto el uso de este pseudoservidor, considerando las estructuras de datos tan primitivas que estamos utilizando. Salvo en los Splits/Joins, el consumo de CPU es muy bajo, no llegando al 10% en los picos de ocupación de la red.
Las modificaciones efectuadas al código nativo ESNET han sido:
En este momento se gestiona todo dentro de un bloque de memoria. Es un sistema muy sencillo.
Se puede compilar el código con make.
Se hace log de los comandos enviados por IRCops en "olimpo.log".
Añadido el comando "jupe" para jupear servidores. El efecto es instantaneo. Si el olimpo se relanza o se hace squit, se pierde.
Los mensajes que no van específicamente dirigidos a OLIMPO (por ejemplo, mensajes globales) se ignoran.
Cambiamos la identificador numérico de Olimpo de "0" a "A", que se corresponde con el nodo 0. De otra forma surgiría una colisión con el nodo "0" cuando la red se amplíe.
Derivado del anterior: el bot Olimpo pasa de "091" a "AAA".
Completado el tema de ilines.db. Almacena lo siguiente:
Añadido el comando "ilines" para listar las I-Lines activas.
Se genera recogida de basuras cuando:
ATENCION: Solo se guarda la primera letra del identificador
Para ello creo un "bot virtual" llamado "oraculo".
Cada campo se separa con espacios, y cada registro con un '\0'.
En estos momentos un SQUIT de un nodo fuerza un SQUIT/JOIN de ORACULO, de forma que actualiza datos de forma automática. Si eso se ve en la red, lo cambio luego.
Más información sobre los OpenBadges
Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS