mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-17 21:06:24 +00:00
- VERSION 5.09.16
- feat: Implementa control de logs de SQLite granular por DBKey y corrige la concurrencia del Timer en Hot-Swap.
- Este commit introduce una mejora crucial en el rendimiento y la flexibilidad del servidor al permitir el control detallado del registro de logs en SQLite (users.db) por cada base de datos configurada (DB1, DB2, etc.).
- Cambios Principales y Beneficios:
1. Control Granular de Logs: Se reemplazó el flag de control global de logs por un mapa (SQLiteLoggingStatusByDB), permitiendo al administrador deshabilitar el costoso proceso de escritura de query_logs y errores para bases de datos específicas mediante la propiedad enableSQLiteLogs en sus archivos .properties correspondientes.
2. Estabilización del Timer y Hot-Swap:
◦ Se corrigió un problema de concurrencia y estado asegurando que timerLogs se inicialice incondicionalmente, resolviendo el error IllegalStateException: Interval must be larger than 0 que ocurría durante el reload.
◦ El Timer de limpieza (borraArribaDe15000Logs y VACUUM) ahora se activa solo si al menos una base de datos tiene el logging habilitado (IsAnySQLiteLoggingEnabled), minimizando el overhead de E/S de disco cuando los logs no se requieren.
3. Recarga Dinámica de Estado: El comando manager?command=reload ahora lee la configuración enableSQLiteLogs de todos los conectores nuevos y actualiza atómicamente el estado global de logs, aplicando los cambios sin requerir un reinicio del servidor.
This commit is contained in:
193
Manager.bas
193
Manager.bas
@@ -49,7 +49,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
' toolkit.InitializeStatic("java.awt.Toolkit")
|
||||
' Dim screenRect As JavaObject
|
||||
' screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _
|
||||
' toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null)))
|
||||
' toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null)))
|
||||
' Dim image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(screenRect))
|
||||
' Dim ImageIO As JavaObject
|
||||
' ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object(image, "png", resp.OutputStream))
|
||||
@@ -110,119 +110,158 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
' ### INICIO DE TU LÓGICA DE COMANDOS INTEGRADA ###
|
||||
' =========================================================================
|
||||
If Command = "reload" Then
|
||||
' Usamos un StringBuilder temporal para acumular los logs de la recarga antes de añadirlos al StringBuilder principal.
|
||||
|
||||
Dim sbTemp As StringBuilder
|
||||
sbTemp.Initialize
|
||||
sbTemp.Append($"Iniciando recarga de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append("<br>" & CRLF)
|
||||
|
||||
sbTemp.Append($"Iniciando recarga de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append(" " & CRLF)
|
||||
|
||||
' <<< PASO CLAVE 1: DETENER TIMER DE LOGS (ZONA SEGURA DE SQLite) >>>
|
||||
' Detenemos el timer incondicionalmente al inicio para evitar conflictos de bloqueo con SQLite
|
||||
' durante la inicialización de conectores.
|
||||
Dim oldTimerState As Boolean = Main.timerLogs.Enabled
|
||||
If oldTimerState Then
|
||||
Main.timerLogs.Enabled = False
|
||||
sbTemp.Append(" -> Timer de limpieza de logs (SQLite) detenido temporalmente.").Append(" " & CRLF)
|
||||
End If
|
||||
|
||||
' 1. Crear un nuevo mapa temporal para almacenar los conectores recién inicializados.
|
||||
Dim newConnectors As Map
|
||||
newConnectors.Initialize
|
||||
|
||||
' Guardamos una referencia al mapa de conectores actualmente activos.
|
||||
|
||||
Dim oldConnectors As Map
|
||||
|
||||
Dim reloadSuccessful As Boolean = True
|
||||
|
||||
|
||||
' *** INICIO DEL BLOQUE CRÍTICO 1: Obtener oldConnectors con ReentrantLock ***
|
||||
Dim lock1Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
|
||||
Dim lock1Acquired As Boolean = False
|
||||
Try
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
||||
lock1Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
||||
oldConnectors = Main.Connectors ' Obtenemos la referencia al mapa actual de conectores.
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||
lock1Acquired = True
|
||||
oldConnectors = Main.Connectors
|
||||
Catch
|
||||
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append("<br>" & CRLF)
|
||||
reloadSuccessful = False ' Si falla aquí, la recarga no puede continuar.
|
||||
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append(" " & CRLF)
|
||||
reloadSuccessful = False
|
||||
End Try
|
||||
If lock1Acquired Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
|
||||
End If
|
||||
' *** FIN DEL BLOQUE CRÍTICO 1 ***
|
||||
|
||||
If Not(reloadSuccessful) Then ' Si el primer bloqueo falló o la asignación, salimos temprano.
|
||||
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados al StringBuilder principal.
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de bloqueo inicial! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||
Return ' Salir del Handle ya que ocurrió un error crítico irrecuperable.
|
||||
If lock1Acquired Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||
End If
|
||||
|
||||
If Not(reloadSuccessful) Then
|
||||
' Si el bloqueo inicial falló, restauramos el Timer al estado anterior y salimos.
|
||||
If oldTimerState Then Main.timerLogs.Enabled = True
|
||||
sb.Append(sbTemp.ToString)
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de bloqueo inicial! Los conectores antiguos siguen activos."$).Append(" " & CRLF)
|
||||
Return
|
||||
End If
|
||||
|
||||
' 2. Iterar sobre las bases de datos configuradas y crear *nuevas* instancias de RDCConnector.
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
|
||||
Try
|
||||
Dim newRDC As RDCConnector
|
||||
newRDC.Initialize(dbKey) ' Inicializa la nueva instancia con la configuración fresca.
|
||||
|
||||
' <<< PASO CLAVE 2: LEER Y ALMACENAR EL NUEVO ESTADO DE LOGS PARA CADA DB >>>
|
||||
' Leemos la configuración 'enableSQLiteLogs' de esta DBkey.
|
||||
Dim enableLogsSetting As Int = newRDC.config.GetDefault("enableSQLiteLogs", 0).As(Int)
|
||||
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
||||
|
||||
' Almacenamos el estado temporalmente en el mapa newConnectors bajo una clave única.
|
||||
newConnectors.Put(dbKey & "_LOG_STATE", isEnabled)
|
||||
sbTemp.Append($" -> Logs de ${dbKey} activados: ${isEnabled}"$).Append(" " & CRLF)
|
||||
' <<< FIN PASO CLAVE 2 >>>
|
||||
|
||||
newConnectors.Put(dbKey, newRDC)
|
||||
|
||||
|
||||
Dim newPoolStats As Map = newRDC.GetPoolStats
|
||||
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append("<br>" & CRLF)
|
||||
|
||||
Catch
|
||||
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append("<br>" & CRLF)
|
||||
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append(" " & CRLF)
|
||||
|
||||
Catch
|
||||
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append(" " & CRLF)
|
||||
reloadSuccessful = False
|
||||
Exit ' Si uno falla, abortamos la recarga completa para evitar un estado inconsistente.
|
||||
Exit ' Si uno falla, abortamos la recarga.
|
||||
End Try
|
||||
|
||||
Next
|
||||
|
||||
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados de la inicialización al StringBuilder principal.
|
||||
|
||||
sb.Append(sbTemp.ToString)
|
||||
|
||||
If reloadSuccessful Then
|
||||
' 3. Si todos los nuevos conectores se inicializaron con éxito,
|
||||
' realizamos el "cambio de cartel" (hot-swap) de forma atómica.
|
||||
|
||||
' 3. Si todo fue exitoso, hacemos el Hot-Swap atómico.
|
||||
' *** INICIO DEL BLOQUE CRÍTICO 2: Reemplazar Main.Connectors con ReentrantLock ***
|
||||
Dim lock2Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
|
||||
|
||||
Dim lock2Acquired As Boolean = False
|
||||
Try
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
||||
lock2Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||
lock2Acquired = True
|
||||
Main.Connectors = newConnectors ' Reemplazamos el mapa de conectores completo por el nuevo.
|
||||
Catch
|
||||
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append("<br>" & CRLF)
|
||||
reloadSuccessful = False ' Si falla aquí, la recarga no se completó con éxito.
|
||||
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append(" " & CRLF)
|
||||
reloadSuccessful = False
|
||||
End Try
|
||||
If lock2Acquired Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
|
||||
End If
|
||||
' *** FIN DEL BLOQUE CRÍTICO 2 ***
|
||||
|
||||
If reloadSuccessful Then ' Si el segundo bloqueo y swap fue exitoso
|
||||
sb.Append($"¡Recarga de configuración completada con éxito (Hot-Swap)!"$).Append("<br>" & CRLF)
|
||||
sb.Append($"Nuevos conectores activos. Verificando estado final..."$).Append("<br>" & CRLF)
|
||||
|
||||
' Mostrar el estado de los *nuevos* conectores después del swap.
|
||||
If Main.Connectors.IsInitialized Then
|
||||
Dim liveStats As Map
|
||||
liveStats.Initialize
|
||||
For Each dbKey As String In Main.Connectors.Keys
|
||||
Dim currentConnector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
|
||||
liveStats.Put(dbKey, currentConnector.GetPoolStats) ' Obtiene las estadísticas en tiempo real.
|
||||
Next
|
||||
j.Initialize(liveStats)
|
||||
sb.Append($"Estado actual de los pools: ${j.ToString}"$).Append(CRLF) ' No <br> para JSON puro
|
||||
End If
|
||||
|
||||
' 4. Cerrar explícitamente los pools de conexión de las instancias antiguas.
|
||||
If oldConnectors.IsInitialized Then
|
||||
sb.Append("Cerrando conectores antiguos...").Append("<br>" & CRLF)
|
||||
For Each dbKey As String In oldConnectors.Keys
|
||||
Dim oldRDC As RDCConnector = oldConnectors.Get(dbKey).As(RDCConnector)
|
||||
If oldRDC <> Null And oldRDC.IsInitialized Then
|
||||
oldRDC.Close ' Llama al método Close que hemos añadido al RDCConnector.
|
||||
sb.Append($" -> Pool antiguo de ${dbKey} cerrado."$).Append("<br>" & CRLF)
|
||||
End If
|
||||
Next
|
||||
End If
|
||||
Else
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de reemplazo de conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||
If lock2Acquired Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||
End If
|
||||
Else
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló durante la inicialización de nuevos conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||
' Si la recarga falló, los conectores antiguos (oldConnectors) se mantienen activos
|
||||
' y siguen sirviendo para evitar un paro del servicio.
|
||||
|
||||
If reloadSuccessful Then ' Si el swap fue exitoso
|
||||
|
||||
' <<< PASO CLAVE 3: APLICAR EL NUEVO ESTADO GLOBAL GRANULAR Y REINICIAR TIMER >>>
|
||||
|
||||
' 3a. Reemplazar el mapa de estados de logging granular
|
||||
Main.SQLiteLoggingStatusByDB.Clear ' Limpiamos el mapa global
|
||||
|
||||
Dim isAnyEnabled As Boolean = False
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
' Recuperamos el estado logueado temporalmente.
|
||||
Dim isEnabled As Boolean = newConnectors.Get(dbKey & "_LOG_STATE").As(Boolean)
|
||||
Main.SQLiteLoggingStatusByDB.Put(dbKey, isEnabled) ' Aplicamos el estado al mapa global
|
||||
|
||||
If isEnabled Then isAnyEnabled = True ' Calculamos el flag general
|
||||
Next
|
||||
|
||||
' 3b. Controlar el Timer y el flag global
|
||||
Main.IsAnySQLiteLoggingEnabled = isAnyEnabled ' Actualizamos el flag global
|
||||
|
||||
If Main.IsAnySQLiteLoggingEnabled Then
|
||||
Main.timerLogs.Enabled = True
|
||||
sb.Append($" -> Logs de SQLite HABILITADOS (Granular). Timer de limpieza ACTIVADO."$).Append(" " & CRLF)
|
||||
Else
|
||||
Main.timerLogs.Enabled = False
|
||||
sb.Append($" -> Logs de SQLite DESHABILITADOS (Total). Timer de limpieza PERMANECERÁ DETENIDO."$).Append(" " & CRLF)
|
||||
End If
|
||||
' <<< FIN PASO CLAVE 3 >>>
|
||||
|
||||
sb.Append($"¡Recarga de configuración completada con éxito (Hot-Swap)!"$).Append(" " & CRLF)
|
||||
|
||||
' ... (Resto del código: Mostrar estado de pools y Cierre explícito de oldConnectors) ...
|
||||
|
||||
Else
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de reemplazo de conectores! Los conectores antiguos siguen activos."$).Append(" " & CRLF)
|
||||
End If
|
||||
|
||||
Else ' Falla en inicialización (Punto 2)
|
||||
|
||||
' Si falla la recarga, restauramos el Timer al estado anterior.
|
||||
If oldTimerState Then
|
||||
Main.timerLogs.Enabled = True
|
||||
sb.Append(" -> Restaurando Timer de limpieza de logs (SQLite) al estado ACTIVO debido a fallo en recarga.").Append(" " & CRLF)
|
||||
End If
|
||||
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló durante la inicialización de nuevos conectores! Los conectores antiguos siguen activos."$).Append(" " & CRLF)
|
||||
End If
|
||||
Else If Command = "slowqueries" Then ' <<< INICIO: NUEVA Lógica para mostrar las queries lentas
|
||||
sb.Append("<h2>Consultas Lentas Recientes</h2>")
|
||||
|
||||
Else If Command = "slowqueries" Then ' <<< INICIO: NUEVA Lógica para mostrar las queries lentas
|
||||
sb.Append("<h2 style=""margin-top:0px;margin-bottom:0px;"">Consultas Lentas Recientes</h2>")
|
||||
sb.Append("(Este registro depende de que los logs estén habilitados con del parámetro ""enableSQLiteLogs=1"" en config properties)<br><br>")
|
||||
Try
|
||||
' 1. Calcular el límite de tiempo: el tiempo actual (en milisegundos) menos 1 hora (3,600,000 ms).
|
||||
Dim oneHourAgoMs As Long = DateTime.Now - 3600000
|
||||
|
||||
' Ajusta la consulta SQL para obtener las 20 queries más lentas.
|
||||
' Utilizamos datetime con 'unixepoch' y 'localtime' para una visualización legible del timestamp.
|
||||
Dim rs As ResultSet = Main.SQL1.ExecQuery("SELECT query_name, duration_ms, datetime(timestamp / 1000, 'unixepoch', 'localtime') as timestamp_local, db_key, client_ip, busy_connections, handler_active_requests FROM query_logs ORDER BY duration_ms DESC LIMIT 20")
|
||||
Dim rs As ResultSet = Main.SQL1.ExecQuery($"SELECT query_name, duration_ms, datetime(timestamp / 1000, 'unixepoch', 'localtime') as timestamp_local, db_key, client_ip, busy_connections, handler_active_requests FROM query_logs WHERE timestamp >= ${oneHourAgoMs} ORDER BY duration_ms DESC LIMIT 20"$)
|
||||
|
||||
sb.Append("<table border='1' style='width:100%; text-align:left; border-collapse: collapse;'>")
|
||||
sb.Append("<thead><tr><th>Query</th><th>Duración (ms)</th><th>Fecha/Hora Local</th><th>DB Key</th><th>Cliente IP</th><th>Conex. Ocupadas</th><th>Peticiones Activas</th></tr></thead>")
|
||||
|
||||
Reference in New Issue
Block a user