mirror of
https://github.com/KeymonSoft/jRDC-MultiDB-Hikari.git
synced 2026-04-17 21:06:23 +00:00
- VERSION 5.10.27
- feat(arquitectura): Consolidación de estabilidad y diagnóstico. - refactor: Arquitectura de base de datos local y políticas de logs. - arch(sqlite): Aislamiento total de las conexiones SQLite en SQL_Auth y SQL_Logs. Esto protege las operaciones de autenticación críticas de la alta carga de I/O generada por el subsistema de logs. - feat(logs): Implementación de modo de almacenamiento flexible para logs (disco o en memoria), mejorando la capacidad de testing. - refactor(logs): Se estandariza el límite de retención de registros a 10,000 para todas las tablas de logs, y se renombra la subrutina de limpieza a borraArribaDe10000Logs.
This commit is contained in:
492
Manager.bas
492
Manager.bas
@@ -4,34 +4,30 @@ ModulesStructureVersion=1
|
||||
Type=Class
|
||||
Version=10.3
|
||||
@EndOfDesignText@
|
||||
' Módulo de clase: Manager
|
||||
' Este handler proporciona un panel de administración web para el servidor jRDC2-Multi.
|
||||
' Permite monitorear el estado del servidor, recargar configuraciones de bases de datos,
|
||||
' ver estadísticas de rendimiento, reiniciar servicios externos, y gestionar la autenticación de usuarios.
|
||||
' Class module: Manager
|
||||
' This handler provides a web administration panel for the jRDC2-Multi server.
|
||||
' It allows monitoring server status, reloading database configurations,
|
||||
' viewing performance statistics, restarting external services, and managing user authentication.
|
||||
|
||||
Sub Class_Globals
|
||||
' Objeto para generar respuestas JSON. Se utiliza para mostrar mapas de datos de forma legible.
|
||||
' Object to generate JSON responses. Used to display data maps legibly.
|
||||
Dim j As JSONGenerator
|
||||
' La clase BCrypt no se usa directamente en este módulo, pero se mantiene si hubiera planes futuros.
|
||||
' The BCrypt class is not used directly in this module, but is kept for any future plans.
|
||||
' Private bc As BCrypt
|
||||
End Sub
|
||||
|
||||
' Subrutina de inicialización de la clase. Se llama cuando se crea un objeto de esta clase.
|
||||
' Class initialization subroutine. Called when an object of this class is created.
|
||||
Public Sub Initialize
|
||||
' No se requiere inicialización específica para esta clase en este momento.
|
||||
' No specific initialization is required for this class at this time.
|
||||
End Sub
|
||||
|
||||
' Método principal que maneja las peticiones HTTP para el panel de administración.
|
||||
' req: El objeto ServletRequest que contiene la información de la petición entrante.
|
||||
' resp: El objeto ServletResponse para construir y enviar la respuesta al cliente.
|
||||
' Módulo de clase: Manager
|
||||
' ... (tu código de Class_Globals e Initialize se queda igual) ...
|
||||
|
||||
' Método principal que maneja las peticiones HTTP para el panel de administración.
|
||||
' Refactorizado para funcionar como una API con un frontend estático.
|
||||
' Main method that handles HTTP requests for the administration panel.
|
||||
' req: The ServletRequest object containing incoming request information.
|
||||
' resp: The ServletResponse object for building and sending the response to the client.
|
||||
' Refactored to work as an API with a static frontend.
|
||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
|
||||
' --- 1. Bloque de Seguridad ---
|
||||
' Security Block
|
||||
If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then
|
||||
resp.SendRedirect("/login")
|
||||
Return
|
||||
@@ -39,7 +35,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
|
||||
Dim Command As String = req.GetParameter("command")
|
||||
|
||||
' --- 2. Servidor de la Página Principal ---
|
||||
' Main Page Server
|
||||
If Command = "" Then
|
||||
Try
|
||||
resp.ContentType = "text/html; charset=utf-8"
|
||||
@@ -50,10 +46,10 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
Return
|
||||
End If
|
||||
|
||||
' --- 3. Manejo de Comandos como API ---
|
||||
' API Command Handling
|
||||
Select Command.ToLowerCase
|
||||
|
||||
' --- Comandos que devuelven JSON (Métricas del Pool) ---
|
||||
|
||||
' Commands that return JSON (Pool Metrics)
|
||||
Case "getstatsold"
|
||||
resp.ContentType = "application/json; charset=utf-8"
|
||||
Dim allPoolStats As Map
|
||||
@@ -63,49 +59,107 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
If connector.IsInitialized Then
|
||||
allPoolStats.Put(dbKey, connector.GetPoolStats)
|
||||
Else
|
||||
allPoolStats.Put(dbKey, CreateMap("Error": "Conector no inicializado"))
|
||||
allPoolStats.Put(dbKey, CreateMap("Error": "Connector not initialized"))
|
||||
End If
|
||||
Next
|
||||
j.Initialize(allPoolStats)
|
||||
resp.Write(j.ToString)
|
||||
Return
|
||||
|
||||
|
||||
Case "getstats"
|
||||
resp.ContentType = "application/json; charset=utf-8"
|
||||
Dim allPoolStats As Map
|
||||
|
||||
' Leemos del caché global actualizado por el Timer SSE
|
||||
|
||||
' We read from the global cache updated by the SSE Timer
|
||||
allPoolStats = Main.LatestPoolStats
|
||||
|
||||
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
If allPoolStats.ContainsKey(dbKey) = False Then
|
||||
allPoolStats.Put(dbKey, CreateMap("Error": "Métricas no disponibles/Pool no inicializado"))
|
||||
allPoolStats.Put(dbKey, CreateMap("Error": "Metrics unavailable/Pool not initialized"))
|
||||
End If
|
||||
Next
|
||||
|
||||
|
||||
j.Initialize(allPoolStats)
|
||||
resp.Write(j.ToString)
|
||||
Return
|
||||
|
||||
|
||||
Case "slowqueries"
|
||||
|
||||
resp.ContentType = "application/json; charset=utf-8"
|
||||
Dim results As List
|
||||
results.Initialize
|
||||
|
||||
|
||||
Try
|
||||
' Verifica la existencia de la tabla de logs antes de consultar
|
||||
Dim tableExists As Boolean = Main.SQL1.ExecQuerySingleResult($"SELECT name FROM sqlite_master WHERE type='table' AND name='query_logs';"$) <> Null
|
||||
|
||||
' --- 1. VALIDACIÓN Y PARSEO DEFENSIVO DE PARÁMETROS ---
|
||||
|
||||
' Capturamos los parámetros numéricos como Strings primero para chequear si están vacíos.
|
||||
Dim limitStr As String = req.GetParameter("limit")
|
||||
Dim minutesStr As String = req.GetParameter("minutes")
|
||||
|
||||
Dim limit As Int = 0
|
||||
Dim minutes As Int = 0
|
||||
|
||||
' 1a. Parseo seguro de 'limit'. Evita NumberFormatException si el string es "" o Null.
|
||||
If limitStr <> Null And limitStr.Trim.Length > 0 Then
|
||||
limit = limitStr.As(Int)
|
||||
End If
|
||||
|
||||
' 1b. Parseo seguro de 'minutes'.
|
||||
If minutesStr <> Null And minutesStr.Trim.Length > 0 Then
|
||||
minutes = minutesStr.As(Int)
|
||||
End If
|
||||
|
||||
' Parámetros de ordenamiento (ya son strings, no necesitan chequeo de NFE)
|
||||
Dim sortby As String = req.GetParameter("sortby").ToLowerCase.Trim ' Columna para ordenar (Default: duration_ms)
|
||||
Dim sortorder As String = req.GetParameter("sortorder").ToUpperCase.Trim ' Orden (Default: DESC)
|
||||
|
||||
' Establecer Defaults (ahora esta lógica funciona porque limit y minutes son 0 si el parámetro estaba vacío o ausente)
|
||||
If limit = 0 Then limit = 20 ' Límite de registros (Default: 20)
|
||||
If minutes = 0 Then minutes = 60 ' Filtro de tiempo en minutos (Default: 60 - última hora)
|
||||
|
||||
' --- Lista Blanca (Whitelist) para prevención de Inyección SQL en ORDER BY ---
|
||||
Dim allowedSortColumns As List
|
||||
allowedSortColumns.Initialize
|
||||
allowedSortColumns.AddAll(Array As Object("duration_ms", "timestamp", "db_key", "client_ip", "busy_connections", "handler_active_requests", "query_name"))
|
||||
|
||||
If allowedSortColumns.IndexOf(sortby) = -1 Then
|
||||
sortby = "duration_ms" ' Usar default seguro si no se encuentra
|
||||
End If
|
||||
|
||||
If sortorder <> "ASC" And sortorder <> "DESC" Then
|
||||
sortorder = "DESC"
|
||||
End If
|
||||
|
||||
' --- 2. PREPARACIÓN DE LA CONSULTA SQL ---
|
||||
|
||||
' We use SQL_Logs instance to check for the table existence.
|
||||
Dim tableExists As Boolean = Main.SQL_Logs.ExecQuerySingleResult($"SELECT name FROM sqlite_master WHERE type='table' AND name='query_logs'"$) <> Null
|
||||
|
||||
If tableExists = False Then
|
||||
j.Initialize(CreateMap("message": "La tabla de logs ('query_logs') no existe. Habilita 'enableSQLiteLogs=1' en la configuración."))
|
||||
j.Initialize(CreateMap("message": "La tabla de logs ('query_logs') no existe. Habilite 'enableSQLiteLogs=1' en la configuración."))
|
||||
resp.Write(j.ToString)
|
||||
Return
|
||||
End If
|
||||
|
||||
' Consulta las 20 queries más lentas de la última hora
|
||||
Dim oneHourAgoMs As Long = DateTime.Now - 3600000
|
||||
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"$)
|
||||
|
||||
|
||||
' Calcular el tiempo de corte (flexible)
|
||||
Dim cutOffTimeMs As Long = DateTime.Now - (minutes * 60000)
|
||||
|
||||
Dim sqlQuery As String = $"
|
||||
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 >= ${cutOffTimeMs}
|
||||
ORDER BY ${sortby} ${sortorder}
|
||||
LIMIT ${limit}"$
|
||||
|
||||
Dim rs As ResultSet = Main.SQL_Logs.ExecQuery(sqlQuery) ' --- Execute query on SQL_Logs instance
|
||||
|
||||
Do While rs.NextRow
|
||||
Dim row As Map
|
||||
row.Initialize
|
||||
@@ -118,33 +172,39 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
row.Put("Peticiones_Activas", rs.GetInt("handler_active_requests"))
|
||||
results.Add(row)
|
||||
Loop
|
||||
|
||||
rs.Close
|
||||
|
||||
|
||||
Dim root As Map
|
||||
root.Initialize
|
||||
root.Put("data", results)
|
||||
|
||||
' Añadir meta información para diagnóstico
|
||||
Dim meta As Map = CreateMap("limit_applied": limit, "sorted_by": sortby, "sort_order": sortorder, "minutes_filter": minutes)
|
||||
root.Put("meta", meta)
|
||||
|
||||
j.Initialize(root)
|
||||
resp.Write(j.ToString)
|
||||
|
||||
|
||||
Catch
|
||||
Log("Error CRÍTICO al obtener queries lentas en Manager API: " & LastException.Message)
|
||||
Log("CRITICAL Error getting slow queries in Manager API: " & LastException.Message)
|
||||
resp.Status = 500
|
||||
|
||||
Dim root As Map
|
||||
root.Initialize
|
||||
root.Put("data", results)
|
||||
j.Initialize(root)
|
||||
j.Initialize(CreateMap("message": "Error interno al procesar logs. Detalle: " & LastException.Message))
|
||||
resp.Write(j.ToString)
|
||||
End Try
|
||||
|
||||
Return
|
||||
|
||||
|
||||
Case "logs", "totalrequests", "totalblocked"
|
||||
resp.ContentType = "application/json; charset=utf-8"
|
||||
Dim mp As Map
|
||||
If Command = "logs" And GlobalParameters.mpLogs.IsInitialized Then mp = GlobalParameters.mpLogs
|
||||
If Command = "totalrequests" And GlobalParameters.mpTotalRequests.IsInitialized Then mp = GlobalParameters.mpTotalRequests
|
||||
If Command = "totalblocked" And GlobalParameters.mpBlockConnection.IsInitialized Then mp = GlobalParameters.mpBlockConnection
|
||||
|
||||
|
||||
If mp.IsInitialized Then
|
||||
j.Initialize(mp)
|
||||
resp.Write(j.ToString)
|
||||
@@ -152,108 +212,107 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
resp.Write("{}")
|
||||
End If
|
||||
Return
|
||||
|
||||
' --- Comandos que devuelven TEXTO PLANO ---
|
||||
|
||||
' Commands that return PLAIN TEXT
|
||||
Case "ping"
|
||||
resp.ContentType = "text/plain"
|
||||
resp.Write($"Pong ($DateTime{DateTime.Now})"$)
|
||||
Return
|
||||
|
||||
|
||||
Case "reload"
|
||||
resp.ContentType = "text/plain; charset=utf-8"
|
||||
Dim sbTemp As StringBuilder
|
||||
sbTemp.Initialize
|
||||
|
||||
' ***** LÓGICA DE RECARGA GRANULAR/SELECTIVA *****
|
||||
Dim dbKeyToReload As String = req.GetParameter("db").ToUpperCase ' Leer parámetro 'db' opcional (ej: /manager?command=reload&db=DB3)
|
||||
Dim targets As List ' Lista de DBKeys a recargar.
|
||||
|
||||
Dim dbKeyToReload As String = req.GetParameter("db").ToUpperCase ' Read optional 'db' parameter (e.g., /manager?command=reload&db=DB3)
|
||||
Dim targets As List ' List of DBKeys to reload.
|
||||
targets.Initialize
|
||||
|
||||
' 1. Determinar el alcance de la recarga (selectiva o total)
|
||||
|
||||
' 1. Determine the scope of the reload (selective or total)
|
||||
If dbKeyToReload.Length > 0 Then
|
||||
' Recarga selectiva
|
||||
' Selective reload
|
||||
If Main.listaDeCP.IndexOf(dbKeyToReload) = -1 Then
|
||||
resp.Write($"ERROR: DBKey '${dbKeyToReload}' no es válida o no está configurada."$)
|
||||
resp.Write($"ERROR: DBKey '${dbKeyToReload}' is not valid or not configured."$)
|
||||
Return
|
||||
End If
|
||||
targets.Add(dbKeyToReload)
|
||||
sbTemp.Append($"Iniciando recarga selectiva de ${dbKeyToReload} (Hot-Swap)..."$).Append(" " & CRLF)
|
||||
sbTemp.Append($"Starting selective reload of ${dbKeyToReload} (Hot-Swap)..."$).Append(" " & CRLF)
|
||||
Else
|
||||
' Recarga completa (comportamiento por defecto)
|
||||
' Full reload (default behavior)
|
||||
targets.AddAll(Main.listaDeCP)
|
||||
sbTemp.Append($"Iniciando recarga COMPLETA de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append(" " & CRLF)
|
||||
sbTemp.Append($"Starting COMPLETE configuration reload (Hot-Swap) ($DateTime{DateTime.Now})"$).Append(" " & CRLF)
|
||||
End If
|
||||
|
||||
' 2. Deshabilitar el Timer de logs (si es necesario)
|
||||
|
||||
' 2. Disable the log Timer (if necessary)
|
||||
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)
|
||||
sbTemp.Append(" -> Log cleanup timer (SQLite) stopped temporarily.").Append(" " & CRLF)
|
||||
End If
|
||||
|
||||
|
||||
Dim reloadSuccessful As Boolean = True
|
||||
Dim oldConnectorsToClose As Map ' Guardaremos los conectores antiguos aquí.
|
||||
Dim oldConnectorsToClose As Map ' We will store the old connectors here.
|
||||
oldConnectorsToClose.Initialize
|
||||
|
||||
' 3. Procesar solo los conectores objetivos
|
||||
|
||||
' 3. Process only the target connectors
|
||||
For Each dbKey As String In targets
|
||||
sbTemp.Append($" -> Procesando recarga de ${dbKey}..."$).Append(CRLF)
|
||||
sbTemp.Append($" -> Processing reload of ${dbKey}..."$).Append(CRLF)
|
||||
Dim newRDC As RDCConnector
|
||||
|
||||
|
||||
Try
|
||||
' Crear el nuevo conector con la configuración fresca
|
||||
' Create the new connector with the fresh configuration
|
||||
newRDC.Initialize(dbKey)
|
||||
|
||||
' Adquirimos el lock para el reemplazo atómico
|
||||
|
||||
' Acquire the lock for atomic replacement
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||
|
||||
' Guardamos el conector antiguo (si existe)
|
||||
|
||||
' Save the old connector (if it exists)
|
||||
Dim oldRDC As RDCConnector = Main.Connectors.Get(dbKey)
|
||||
|
||||
' Reemplazo atómico en el mapa global compartido
|
||||
|
||||
' Atomic replacement in the shared global map
|
||||
Main.Connectors.Put(dbKey, newRDC)
|
||||
|
||||
' Liberamos el bloqueo inmediatamente
|
||||
|
||||
' Release the lock immediately
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||
|
||||
' Si había un conector antiguo, lo guardamos para cerrarlo después
|
||||
|
||||
' If there was an old connector, save it to close later
|
||||
If oldRDC.IsInitialized Then
|
||||
oldConnectorsToClose.Put(dbKey, oldRDC)
|
||||
End If
|
||||
|
||||
' 4. Actualizar el estado de logs (Granular)
|
||||
' 4. Update log status (Granular)
|
||||
Dim enableLogsSetting As Int = newRDC.config.GetDefault("enableSQLiteLogs", 0)
|
||||
Dim isEnabled As Boolean = (enableLogsSetting = 1)
|
||||
Main.SQLiteLoggingStatusByDB.Put(dbKey, isEnabled)
|
||||
sbTemp.Append($" -> ${dbKey} recargado. Logs (config): ${isEnabled}"$).Append(CRLF)
|
||||
|
||||
sbTemp.Append($" -> ${dbKey} reloaded. Logs (config): ${isEnabled}"$).Append(CRLF)
|
||||
|
||||
Catch
|
||||
' Si falla la inicialización del pool, no actualizamos Main.Connectors
|
||||
|
||||
' ¡CRÍTICO! Aseguramos que el lock se libere si hubo excepción antes de liberar.
|
||||
' If pool initialization fails, we don't update Main.Connectors
|
||||
|
||||
' Ensure the lock is released if an exception occurred before release.
|
||||
If Main.MainConnectorsLock.RunMethod("isHeldByCurrentThread", Null).As(Boolean) Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||
End If
|
||||
|
||||
sbTemp.Append($" -> ERROR CRÍTICO al inicializar conector para ${dbKey}: ${LastException.Message}"$).Append(" " & CRLF)
|
||||
|
||||
sbTemp.Append($" -> CRITICAL ERROR initializing connector for ${dbKey}: ${LastException.Message}"$).Append(" " & CRLF)
|
||||
reloadSuccessful = False
|
||||
Exit
|
||||
End Try
|
||||
Next
|
||||
|
||||
' 5. Cerrar los pools antiguos liberados (FUERA del Lock)
|
||||
|
||||
' 5. Close the old released pools (OUTSIDE the Lock)
|
||||
If reloadSuccessful Then
|
||||
For Each dbKey As String In oldConnectorsToClose.Keys
|
||||
Dim oldRDC As RDCConnector = oldConnectorsToClose.Get(dbKey)
|
||||
oldRDC.Close ' Cierre limpio del pool C3P0
|
||||
sbTemp.Append($" -> Pool antiguo de ${dbKey} cerrado limpiamente."$).Append(" " & CRLF)
|
||||
oldRDC.Close ' Clean closure of the C3P0 pool
|
||||
sbTemp.Append($" -> Old pool for ${dbKey} closed cleanly."$).Append(" " & CRLF)
|
||||
Next
|
||||
|
||||
' 6. Re-evaluar el estado global de Logs (CRÍTICO: debe revisar TODAS las DBs)
|
||||
|
||||
' 6. Re-evaluate the global Log status (must check ALL DBs)
|
||||
Main.IsAnySQLiteLoggingEnabled = False
|
||||
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
' Revisamos el estado de log de CADA conector activo
|
||||
' We check the log status of EACH active connector
|
||||
If Main.SQLiteLoggingStatusByDB.GetDefault(dbKey, False) Then
|
||||
Main.IsAnySQLiteLoggingEnabled = True
|
||||
Exit
|
||||
@@ -261,19 +320,19 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
Next
|
||||
If Main.IsAnySQLiteLoggingEnabled Then
|
||||
Main.timerLogs.Enabled = True
|
||||
sbTemp.Append($" -> Timer de limpieza de logs ACTIVADO (estado global: HABILITADO)."$).Append(" " & CRLF)
|
||||
sbTemp.Append($" -> Log cleanup timer ACTIVATED (global status: ENABLED)."$).Append(" " & CRLF)
|
||||
Else
|
||||
Main.timerLogs.Enabled = False
|
||||
sbTemp.Append($" -> Timer de limpieza de logs DESHABILITADO (estado global: DESHABILITADO)."$).Append(" " & CRLF)
|
||||
sbTemp.Append($" -> Log cleanup timer DISABLED (global status: DISABLED)."$).Append(" " & CRLF)
|
||||
End If
|
||||
sbTemp.Append($"¡Recarga de configuración completada con éxito!"$).Append(" " & CRLF)
|
||||
sbTemp.Append($"Configuration reload completed successfully!"$).Append(" " & CRLF)
|
||||
Else
|
||||
' Si falló, restauramos el estado del timer anterior.
|
||||
' If it failed, restore the previous timer state.
|
||||
If oldTimerState Then
|
||||
Main.timerLogs.Enabled = True
|
||||
sbTemp.Append(" -> Restaurando Timer de limpieza de logs al estado ACTIVO debido a fallo en recarga.").Append(" " & CRLF)
|
||||
sbTemp.Append(" -> Restoring Log cleanup timer to ACTIVE state due to reload failure.").Append(" " & CRLF)
|
||||
End If
|
||||
sbTemp.Append($"¡ERROR: La recarga de configuración falló! Los conectores antiguos siguen activos."$).Append(" " & CRLF)
|
||||
sbTemp.Append($"ERROR: Configuration reload failed! Old connectors are still active."$).Append(" " & CRLF)
|
||||
End If
|
||||
resp.Write(sbTemp.ToString)
|
||||
Return
|
||||
@@ -281,57 +340,57 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
resp.ContentType = "text/plain; charset=utf-8"
|
||||
Dim sb As StringBuilder
|
||||
sb.Initialize
|
||||
sb.Append("--- INICIANDO PRUEBA DE CONECTIVIDAD A TODOS LOS POOLS CONFIGURADOS ---").Append(CRLF).Append(CRLF)
|
||||
|
||||
' Iteramos sobre la lista de DB Keys cargadas al inicio (DB1, DB2, etc.)
|
||||
sb.Append("--- STARTING CONNECTIVITY TEST TO ALL CONFIGURED POOLS ---").Append(CRLF).Append(CRLF)
|
||||
|
||||
' We iterate over the list of DB Keys loaded at startup (DB1, DB2, etc.)
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
Dim success As Boolean = False
|
||||
Dim errorMsg As String = ""
|
||||
Dim con As SQL ' Conexión para la prueba
|
||||
|
||||
Dim con As SQL ' Connection for the test
|
||||
|
||||
Try
|
||||
' 1. Obtener el RDCConnector para esta DBKey
|
||||
' 1. Get the RDCConnector for this DBKey
|
||||
Dim connector As RDCConnector = Main.Connectors.Get(dbKey)
|
||||
|
||||
|
||||
If connector.IsInitialized = False Then
|
||||
errorMsg = "Conector no inicializado (revisa logs de AppStart)"
|
||||
errorMsg = "Connector not initialized (check AppStart logs)"
|
||||
Else
|
||||
' 2. Forzar la adquisición de una conexión del pool C3P0
|
||||
' 2. Force acquisition of a connection from the C3P0 pool
|
||||
con = connector.GetConnection(dbKey)
|
||||
|
||||
If con.IsInitialized Then
|
||||
' 3. Si la conexión es válida, la cerramos inmediatamente para devolverla al pool
|
||||
' 3. If the connection is valid, close it immediately to return it to the pool
|
||||
con.Close
|
||||
success = True
|
||||
Else
|
||||
errorMsg = "La conexión devuelta no es válida (SQL.IsInitialized = False)"
|
||||
errorMsg = "Returned connection is not valid (SQL.IsInitialized = False)"
|
||||
End If
|
||||
End If
|
||||
|
||||
|
||||
Catch
|
||||
' Capturamos cualquier excepción (ej. fallo de JDBC, timeout de C3P0)
|
||||
' We catch any exception (e.g., JDBC failure, C3P0 timeout)
|
||||
errorMsg = LastException.Message
|
||||
End Try
|
||||
|
||||
|
||||
If success Then
|
||||
sb.Append($"* ${dbKey}: Conexión adquirida y liberada correctamente."$).Append(CRLF)
|
||||
sb.Append($"* ${dbKey}: Connection acquired and released successfully."$).Append(CRLF)
|
||||
Else
|
||||
' Si falla, registramos el error para el administrador.
|
||||
Main.LogServerError("ERROR", "Manager.TestCommand", $"Falló la prueba de conectividad para ${dbKey}: ${errorMsg}"$, dbKey, "test_command", req.RemoteAddress)
|
||||
sb.Append($"[FALLO] ${dbKey}: ERROR CRÍTICO al obtener conexión. Mensaje: ${errorMsg}"$).Append(CRLF)
|
||||
' If it fails, log the error for the administrator.
|
||||
Main.LogServerError("ERROR", "Manager.TestCommand", $"Connectivity test failed for ${dbKey}: ${errorMsg}"$, dbKey, "test_command", req.RemoteAddress)
|
||||
sb.Append($"[FAILED] ${dbKey}: CRITICAL ERROR getting connection. Message: ${errorMsg}"$).Append(CRLF)
|
||||
End If
|
||||
Next
|
||||
|
||||
sb.Append(CRLF).Append("--- FIN DE PRUEBA DE CONEXIONES ---").Append(CRLF)
|
||||
|
||||
' Mantenemos la lista original de archivos de configuración cargados (esto es informativo)
|
||||
sb.Append(CRLF).Append("Archivos de configuración cargados:").Append(CRLF)
|
||||
|
||||
sb.Append(CRLF).Append("--- END OF CONNECTION TEST ---").Append(CRLF)
|
||||
|
||||
' We keep the original list of loaded config files (this is informational)
|
||||
sb.Append(CRLF).Append("Loaded configuration files:").Append(CRLF)
|
||||
For Each item As String In Main.listaDeCP
|
||||
Dim configName As String = "config"
|
||||
If item <> "DB1" Then configName = configName & "." & item
|
||||
sb.Append($" -> Usando ${configName}.properties"$).Append(CRLF)
|
||||
sb.Append($" -> Using ${configName}.properties"$).Append(CRLF)
|
||||
Next
|
||||
|
||||
|
||||
resp.Write(sb.ToString)
|
||||
Return
|
||||
Case "rsx", "rpm2", "revivebow", "restartserver"
|
||||
@@ -341,161 +400,154 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
Case "rsx": batFile = "start.bat"
|
||||
Case "rpm2": batFile = "reiniciaProcesoPM2.bat"
|
||||
Case "reviveBow": batFile = "reiniciaProcesoBow.bat"
|
||||
Case "restartserver": batFile = "restarServer.bat" ' Nota: este bat no estaba definido, se usó el nombre del comando
|
||||
Case "restartserver": batFile = "restarServer.bat" ' Note: this bat was not defined, command name was used
|
||||
End Select
|
||||
|
||||
Log($"Ejecutando ${File.DirApp}\${batFile}"$)
|
||||
|
||||
Log($"Executing ${File.DirApp}\${batFile}"$)
|
||||
Try
|
||||
Dim shl As Shell
|
||||
shl.Initialize("shl","cmd", Array("/c", File.DirApp & "\" & batFile & " " & Main.srvr.Port))
|
||||
shl.WorkingDirectory = File.DirApp
|
||||
shl.Run(-1)
|
||||
resp.Write($"Comando '${Command}' ejecutado. Script invocado: ${batFile}"$)
|
||||
resp.Write($"Command '${Command}' executed. Script invoked: ${batFile}"$)
|
||||
Catch
|
||||
resp.Write($"Error al ejecutar el script para '${Command}': ${LastException.Message}"$)
|
||||
resp.Write($"Error executing script for '${Command}': ${LastException.Message}"$)
|
||||
End Try
|
||||
Return
|
||||
|
||||
|
||||
Case "paused", "continue"
|
||||
resp.ContentType = "text/plain; charset=utf-8"
|
||||
resp.ContentType = "text/plain; charset=utf-ab"
|
||||
If Command = "paused" Then
|
||||
GlobalParameters.IsPaused = 1
|
||||
resp.Write("Servidor pausado.")
|
||||
resp.Write("Server paused.")
|
||||
Else
|
||||
GlobalParameters.IsPaused = 0
|
||||
resp.Write("Servidor reanudado.")
|
||||
resp.Write("Server resumed.")
|
||||
End If
|
||||
Return
|
||||
|
||||
|
||||
Case "block", "unblock"
|
||||
resp.ContentType = "text/plain; charset=utf-8"
|
||||
Dim ip As String = req.GetParameter("IP")
|
||||
|
||||
|
||||
If ip = "" Then
|
||||
resp.Write("Error: El parámetro IP es requerido.")
|
||||
resp.Write("Error: The IP parameter is required.")
|
||||
Return
|
||||
End If
|
||||
|
||||
|
||||
If GlobalParameters.mpBlockConnection.IsInitialized Then
|
||||
If Command = "block" Then
|
||||
GlobalParameters.mpBlockConnection.Put(ip, ip)
|
||||
resp.Write($"IP bloqueada: ${ip}"$)
|
||||
resp.Write($"IP blocked: ${ip}"$)
|
||||
Else
|
||||
GlobalParameters.mpBlockConnection.Remove(ip)
|
||||
resp.Write($"IP desbloqueada: ${ip}"$)
|
||||
resp.Write($"IP unblocked: ${ip}"$)
|
||||
End If
|
||||
Else
|
||||
resp.Write("Error: El mapa de bloqueo no está inicializado.")
|
||||
resp.Write("Error: The block map is not initialized.")
|
||||
End If
|
||||
Return
|
||||
Case "getconfiginfo"
|
||||
|
||||
|
||||
resp.ContentType = "text/plain; charset=utf-8"
|
||||
Dim sbInfo As StringBuilder
|
||||
sbInfo.Initialize
|
||||
|
||||
|
||||
Dim allKeys As List
|
||||
allKeys.Initialize
|
||||
allKeys.AddAll(Main.listaDeCP)
|
||||
|
||||
|
||||
sbInfo.Append("======================================================================").Append(CRLF)
|
||||
sbInfo.Append($"=== CONFIGURACIÓN jRDC2-Multi V$1.2{Main.VERSION} (ACTIVA) ($DateTime{DateTime.Now}) ==="$).Append(CRLF)
|
||||
sbInfo.Append($"=== jRDC2-Multi V$1.2{Main.VERSION} CONFIGURATION (ACTIVE) ($DateTime{DateTime.Now}) ==="$).Append(CRLF)
|
||||
sbInfo.Append("======================================================================").Append(CRLF).Append(CRLF)
|
||||
|
||||
' ***** GLOSARIO DE PARÁMETROS CONFIGURABLES *****
|
||||
sbInfo.Append("### GLOSARIO DE PARÁMETROS PERMITIDOS EN CONFIG.PROPERTIES (HIKARICP) ###").Append(CRLF)
|
||||
|
||||
sbInfo.Append("### GLOSSARY OF ALLOWED PARAMETERS IN CONFIG.PROPERTIES (HIKARICP) ###").Append(CRLF)
|
||||
sbInfo.Append("--------------------------------------------------").Append(CRLF)
|
||||
sbInfo.Append("DriverClass: Clase del driver JDBC (ej: oracle.jdbc.driver.OracleDriver).").Append(CRLF)
|
||||
sbInfo.Append("JdbcUrl: URL de conexión a la base de datos (IP, puerto, servicio).").Append(CRLF)
|
||||
sbInfo.Append("User/Password: Credenciales de acceso a la BD.").Append(CRLF)
|
||||
sbInfo.Append("ServerPort: Puerto de escucha del servidor B4J (solo lo toma de config.properties).").Append(CRLF)
|
||||
sbInfo.Append("Debug: Si es 'true', los comandos SQL se recargan en cada petición (DESHABILITADO, USAR COMANDO RELOAD).").Append(CRLF)
|
||||
sbInfo.Append("parameterTolerance: Define si se recortan (1) o se rechazan (0) los parámetros SQL sobrantes a los requeridos por el query.").Append(CRLF)
|
||||
sbInfo.Append("enableSQLiteLogs: Control granular. Habilita (1) o deshabilita (0) la escritura de logs en users.db para esta DB.").Append(CRLF)
|
||||
|
||||
' --- Parámetros de HIKARICP (Foco en el mínimo set de tuning) ---
|
||||
sbInfo.Append("pool.hikari.maximumPoolSize: Máximo de conexiones simultáneas permitido. (Recomendado N*Cores DB),").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.minimumIdle: Mínimo de conexiones inactivas. Recomendado igual a maximumPoolSize para pool de tamaño fijo,").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.maxLifetime (ms): Tiempo máximo de vida de una conexión. CRÍTICO: Debe ser menor que el timeout del firewall/DB,").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.connectionTimeout (ms): Tiempo máximo de espera del cliente por una conexión disponible (Default: 30000 ms),").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.idleTimeout (ms): Tiempo inactivo antes de retirar la conexión (ms). Solo aplica si minimumIdle < maximumPoolSize,").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.leakDetectionThreshold (ms): Umbral (ms) para detectar conexiones no devueltas (fugas).").Append(CRLF)
|
||||
|
||||
sbInfo.Append("DriverClass: JDBC driver class (e.g., oracle.jdbc.driver.OracleDriver).").Append(CRLF)
|
||||
sbInfo.Append("JdbcUrl: Database connection URL (IP, port, service).").Append(CRLF)
|
||||
sbInfo.Append("User/Password: DB access credentials.").Append(CRLF)
|
||||
sbInfo.Append("ServerPort: B4J server listening port (only taken from config.properties).").Append(CRLF)
|
||||
sbInfo.Append("Debug: If 'true', SQL commands are reloaded on each request (DISABLED, USE RELOAD COMMAND).").Append(CRLF)
|
||||
sbInfo.Append("parameterTolerance: Defines whether to trim (1) or reject (0) SQL parameters exceeding those required by the query.").Append(CRLF)
|
||||
sbInfo.Append("enableSQLiteLogs: Granular control. Enables (1) or disables (0) writing logs to users.db for this DB.").Append(CRLF)
|
||||
|
||||
sbInfo.Append("pool.hikari.maximumPoolSize: Maximum simultaneous connections allowed. (Recommended N*Cores DB),").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.minimumIdle: Minimum idle connections. Recommended equal to maximumPoolSize for fixed-size pool,").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.maxLifetime (ms): Maximum lifetime of a connection. CRITICAL: Must be less than firewall/DB timeout,").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.connectionTimeout (ms): Maximum time client waits for an available connection (Default: 30000 ms),").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.idleTimeout (ms): Idle time before retiring the connection (ms). Only applies if minimumIdle < maximumPoolSize,").Append(CRLF)
|
||||
sbInfo.Append("pool.hikari.leakDetectionThreshold (ms): Threshold (ms) to detect unreturned connections (leaks).").Append(CRLF)
|
||||
|
||||
sbInfo.Append(CRLF)
|
||||
|
||||
|
||||
For Each dbKey As String In allKeys
|
||||
|
||||
' --- COMIENZA EL DETALLE POR CONECTOR ---
|
||||
|
||||
Dim connector As RDCConnector = Main.Connectors.Get(dbKey)
|
||||
|
||||
|
||||
sbInfo.Append("--------------------------------------------------").Append(CRLF).Append(CRLF)
|
||||
sbInfo.Append($"---------------- ${dbKey} ------------------"$).Append(CRLF).Append(CRLF)
|
||||
|
||||
|
||||
If connector.IsInitialized Then
|
||||
Dim configMap As Map = connector.config
|
||||
|
||||
' Obtener las métricas y la configuración REAL aplicada por HikariCP
|
||||
|
||||
' Get metrics and REAL configuration applied by HikariCP
|
||||
Dim poolStats As Map = connector.GetPoolStats
|
||||
|
||||
|
||||
sbInfo.Append($"DriverClass: ${configMap.GetDefault("DriverClass", "N/A")}"$).Append(CRLF)
|
||||
sbInfo.Append($"JdbcUrl: ${configMap.GetDefault("JdbcUrl", "N/A")}"$).Append(CRLF)
|
||||
sbInfo.Append($"User: ${configMap.GetDefault("User", "N/A")}"$).Append(CRLF)
|
||||
sbInfo.Append($"ServerPort: ${configMap.GetDefault("ServerPort", "N/A")}"$).Append(CRLF).Append(CRLF)
|
||||
|
||||
sbInfo.Append("--- CONFIGURACIÓN DEL POOL (HIKARICP - Valores Aplicados) ---").Append(CRLF)
|
||||
|
||||
sbInfo.Append($"MaximumPoolSize (Aplicado): ${poolStats.GetDefault("MaxPoolSize", 10).As(Int)}"$).Append(CRLF)
|
||||
sbInfo.Append($"MinimumIdle (Aplicado): ${poolStats.GetDefault("MinPoolSize", 10).As(Int)}"$).Append(CRLF)
|
||||
|
||||
' Reportamos los timeouts en Milisegundos (ms)
|
||||
|
||||
sbInfo.Append("--- POOL CONFIGURATION (HIKARICP - Applied Values) ---").Append(CRLF)
|
||||
|
||||
sbInfo.Append($"MaximumPoolSize (Applied): ${poolStats.GetDefault("MaxPoolSize", 10).As(Int)}"$).Append(CRLF)
|
||||
sbInfo.Append($"MinimumIdle (Applied): ${poolStats.GetDefault("MinPoolSize", 10).As(Int)}"$).Append(CRLF)
|
||||
|
||||
' Report timeouts in Milliseconds (ms)
|
||||
sbInfo.Append($"MaxLifetime (ms): ${poolStats.GetDefault("MaxLifetime", 1800000).As(Long)}"$).Append(CRLF)
|
||||
sbInfo.Append($"ConnectionTimeout (ms): ${poolStats.GetDefault("ConnectionTimeout", 30000).As(Long)}"$).Append(CRLF)
|
||||
sbInfo.Append($"IdleTimeout (ms): ${poolStats.GetDefault("IdleTimeout", 600000).As(Long)}"$).Append(CRLF)
|
||||
sbInfo.Append($"LeakDetectionThreshold (ms): ${poolStats.GetDefault("LeakDetectionThreshold", 0).As(Long)}"$).Append(CRLF).Append(CRLF)
|
||||
|
||||
|
||||
' *** NUEVA SECCIÓN: PROPIEDADES ESPECÍFICAS DEL DRIVER ***
|
||||
|
||||
If connector.driverProperties.IsInitialized And connector.driverProperties.Size > 0 Then
|
||||
|
||||
sbInfo.Append("--- PROPIEDADES DE RENDIMIENTO DEL DRIVER JDBC (Optimización de Sentencias) ---").Append(CRLF)
|
||||
|
||||
|
||||
sbInfo.Append("--- JDBC DRIVER PERFORMANCE PROPERTIES (Statement Optimization) ---").Append(CRLF)
|
||||
|
||||
For Each propKey As String In connector.driverProperties.Keys
|
||||
Dim propValue As Object = connector.driverProperties.Get(propKey)
|
||||
sbInfo.Append($"[Driver] ${propKey}: ${propValue}"$).Append(CRLF)
|
||||
Next
|
||||
sbInfo.Append(CRLF)
|
||||
End If
|
||||
' *** FIN DE LA NUEVA SECCIÓN ***
|
||||
|
||||
' Reportamos métricas de runtime del pool (si están disponibles).
|
||||
sbInfo.Append("--- ESTADO DE RUNTIME (Métricas Dinámicas) ---").Append(CRLF)
|
||||
sbInfo.Append("--- RUNTIME STATUS (Dynamic Metrics) ---").Append(CRLF)
|
||||
sbInfo.Append($"Total Connections: ${poolStats.GetDefault("TotalConnections", "N/A")}"$).Append(CRLF)
|
||||
sbInfo.Append($"Busy Connections: ${poolStats.GetDefault("BusyConnections", "N/A")}"$).Append(CRLF)
|
||||
sbInfo.Append($"Idle Connections: ${poolStats.GetDefault("IdleConnections", "N/A")}"$).Append(CRLF)
|
||||
sbInfo.Append($"Handler Active Requests: ${poolStats.GetDefault("HandlerActiveRequests", "N/A")}"$).Append(CRLF).Append(CRLF)
|
||||
|
||||
sbInfo.Append("--- COMPORTAMIENTO ---").Append(CRLF)
|
||||
sbInfo.Append($"Debug (Recarga Queries - DESHABILITADO): ${configMap.GetDefault("Debug", "false")}"$).Append(CRLF)
|
||||
|
||||
|
||||
sbInfo.Append("--- BEHAVIOR ---").Append(CRLF)
|
||||
sbInfo.Append($"Debug (Reload Queries - DISABLED): ${configMap.GetDefault("Debug", "false")}"$).Append(CRLF)
|
||||
|
||||
Dim tolerance As Int = configMap.GetDefault("parameterTolerance", 0).As(Int)
|
||||
sbInfo.Append($"ParameterTolerance: ${tolerance} (0=Estricto, 1=Habilitado)"$).Append(CRLF)
|
||||
|
||||
sbInfo.Append($"ParameterTolerance: ${tolerance} (0=Strict, 1=Enabled)"$).Append(CRLF)
|
||||
|
||||
Dim isLogsEnabledRuntime As Boolean = Main.SQLiteLoggingStatusByDB.GetDefault(dbKey, False).As(Boolean)
|
||||
Dim logsEnabledRuntimeInt As Int = 0
|
||||
If isLogsEnabledRuntime Then
|
||||
logsEnabledRuntimeInt = 1
|
||||
End If
|
||||
sbInfo.Append($"EnableSQLiteLogs: ${logsEnabledRuntimeInt} (0=Deshabilitado, 1=Habilitado)"$).Append(CRLF)
|
||||
sbInfo.Append($"EnableSQLiteLogs: ${logsEnabledRuntimeInt} (0=Disabled, 1=Enabled)"$).Append(CRLF)
|
||||
sbInfo.Append(CRLF)
|
||||
|
||||
|
||||
Else
|
||||
|
||||
sbInfo.Append($"ERROR: Conector ${dbKey} no inicializado o falló al inicio."$).Append(CRLF).Append(CRLF)
|
||||
|
||||
|
||||
sbInfo.Append($"ERROR: Connector ${dbKey} not initialized or failed at startup."$).Append(CRLF).Append(CRLF)
|
||||
|
||||
End If
|
||||
|
||||
|
||||
Next
|
||||
|
||||
|
||||
resp.Write(sbInfo.ToString)
|
||||
Return
|
||||
|
||||
@@ -505,24 +557,22 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
Dim dbKeyToChange As String = req.GetParameter("db").ToUpperCase
|
||||
Dim status As String = req.GetParameter("status")
|
||||
If Main.listaDeCP.IndexOf(dbKeyToChange) = -1 Then
|
||||
resp.Write($"ERROR: DBKey '${dbKeyToChange}' no es válida."$)
|
||||
resp.Write($"ERROR: DBKey '${dbKeyToChange}' is not valid."$)
|
||||
Return
|
||||
End If
|
||||
|
||||
|
||||
Dim isEnabled As Boolean = (status = "1")
|
||||
Dim resultMsg As String
|
||||
|
||||
' *** 1. Adquisición del Lock (CRÍTICO) ***
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null)
|
||||
|
||||
Try
|
||||
' 2. Lógica Crítica de Modificación de Estado (Protegida)
|
||||
Main.SQLiteLoggingStatusByDB.Put(dbKeyToChange, isEnabled)
|
||||
|
||||
Private hab As String = "DESHABILITADOS"
|
||||
If isEnabled Then hab = "HABILITADOS"
|
||||
resultMsg = $"Logs de ${dbKeyToChange} ${hab} en caliente."$
|
||||
' 3. Re-evaluación del estado global
|
||||
|
||||
Private hab As String = "DISABLED"
|
||||
If isEnabled Then hab = "ENABLED"
|
||||
resultMsg = $"Logs for ${dbKeyToChange} ${hab} on-the-fly."$
|
||||
|
||||
Main.IsAnySQLiteLoggingEnabled = False
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
If Main.SQLiteLoggingStatusByDB.GetDefault(dbKey, False) Then
|
||||
@@ -531,27 +581,21 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
End If
|
||||
Next
|
||||
|
||||
' 4. Ajustar el Timer
|
||||
If Main.IsAnySQLiteLoggingEnabled Then
|
||||
If Main.timerLogs.Enabled = False Then Main.timerLogs.Enabled = True
|
||||
resultMsg = resultMsg & " Timer de limpieza ACTIVADO."
|
||||
resultMsg = resultMsg & " Cleanup timer ACTIVATED."
|
||||
Else
|
||||
Main.timerLogs.Enabled = False
|
||||
resultMsg = resultMsg & " Timer de limpieza DESHABILITADO globalmente."
|
||||
resultMsg = resultMsg & " Cleanup timer DISABLED globally."
|
||||
End If
|
||||
|
||||
' ** LIBERACIÓN EN CASO DE ÉXITO **
|
||||
' En el camino de éxito, liberamos inmediatamente antes de que la subrutina termine.
|
||||
If Main.MainConnectorsLock.RunMethod("isHeldByCurrentThread", Null).As(Boolean) Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||
End If
|
||||
|
||||
Catch
|
||||
' 5. Manejo de Excepción y ** LIBERACIÓN EN CASO DE FALLO **
|
||||
resultMsg = $"ERROR CRÍTICO al modificar el estado de logs: ${LastException.Message}"$
|
||||
|
||||
' ¡ESTE ES EL EQUIVALENTE AL FINALLY EN B4X!
|
||||
' Verificamos si este hilo retiene el lock y, si es así, lo liberamos de inmediato.
|
||||
resultMsg = $"CRITICAL ERROR modifying log status: ${LastException.Message}"$
|
||||
|
||||
If Main.MainConnectorsLock.RunMethod("isHeldByCurrentThread", Null).As(Boolean) Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null)
|
||||
End If
|
||||
@@ -559,10 +603,10 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
|
||||
resp.Write(resultMsg)
|
||||
Return
|
||||
|
||||
|
||||
Case Else
|
||||
resp.ContentType = "text/plain; charset=utf-8"
|
||||
resp.SendError(404, $"Comando desconocido: '{Command}'"$)
|
||||
resp.SendError(404, $"Unknown command: '{Command}'"$)
|
||||
Return
|
||||
End Select
|
||||
End Sub
|
||||
End Sub
|
||||
|
||||
Reference in New Issue
Block a user