464 lines
19 KiB
C#
464 lines
19 KiB
C#
/// <summary>
|
||
/// Host principale per l'applicazione Blazor su Windows Forms.
|
||
/// Questa classe rappresenta il punto di ingresso dell'applicazione desktop, integrando una vista Blazor WebAssembly
|
||
/// con servizi in background, monitoraggio della memoria e logica di riavvio automatico.
|
||
///
|
||
/// Responsabilità chiave:
|
||
/// - Inizializza e ospita una interfaccia Blazor WebAssembly tramite Windows Forms BlazorWebView.
|
||
/// - Gestionale eventi di vita: avvio, chiusura del form e arresto.
|
||
/// - Monitora l'uso della memoria .NET e della memoria sistema per prevenire perdita di memoria o crash a causa di esaurimento.
|
||
/// - Esegue verifiche periodiche per riavvio automatico in base alla configurazione e al comportamento del GC.
|
||
/// - Ascolta segnali esterni (ad esempio aggiornamenti di configurazione, richieste di riavvio o riavvio) da servizi.
|
||
/// - Arresta in modo graduale tutti i processi gestiti all'uscita o durante un riavvio.
|
||
/// </summary>
|
||
using IOB_MAN.Core.Services;
|
||
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using NLog;
|
||
using System.Diagnostics;
|
||
using System.Reflection;
|
||
|
||
namespace IOB_MAN
|
||
{
|
||
public partial class BlazorForm : Form
|
||
{
|
||
#region Public Constructors
|
||
|
||
/// <summary>
|
||
/// Crea una nuova istanza della classe BlazorForm.
|
||
/// Imposta il form, inizializza i servizi, configura la vista Blazor e avvia timer in background.
|
||
/// </summary>
|
||
public BlazorForm()
|
||
{
|
||
InitializeComponent();
|
||
ServiceInit();
|
||
ConfInit();
|
||
InitBlazorView();
|
||
StartTimer();
|
||
CheckMemory();
|
||
}
|
||
|
||
#endregion Public Constructors
|
||
|
||
#region Public Methods
|
||
|
||
/// <summary>
|
||
/// Forza il riavvio dell'applicazione con un ritardo opzionale.
|
||
///
|
||
/// Comportamento:
|
||
/// - Se 'force' è true, registra un riavvio forzato (ad esempio a causa di un errore critico).
|
||
/// - Altrimenti, attiva il riavvio a causa del superamento consecutivo di soglie di GC.
|
||
/// - Arresta tutti i processi gestiti tramite ACService.DoCloseAll().
|
||
/// - Introduce un ritardo di 5 secondi utilizzando cmd.exe per evitare un riavvio immediato.
|
||
/// - Utilizza Application.Exit() per terminare correttamente l'istanza corrente.
|
||
/// </summary>
|
||
/// <param name="force">Se true, esegue un riavvio forzato; altrimenti, attiva il riavvio a causa dell'esaurimento del GC.</param>
|
||
public static void RestartApplication(bool force)
|
||
{
|
||
if (force)
|
||
{
|
||
Log.Info($"Chiamata restart application forzato");
|
||
}
|
||
else
|
||
{
|
||
Log.Info($"Chiamata restart application x superamento soglia GC consecutivi");
|
||
}
|
||
|
||
string exePath = Application.ExecutablePath;
|
||
|
||
// Chiude tutti i servizi gestiti (ad esempio applicazioni esterne) prima del riavvio
|
||
ACService.DoCloseAll(false);
|
||
Log.Info("---------------------------------------");
|
||
Log.Info($"Completata chiusura processi x restart");
|
||
Log.Info("---------------------------------------");
|
||
|
||
// Ritardo di 10 secondi utilizzando cmd.exe per evitare un riavvio immediato
|
||
int restartDelay = 10;
|
||
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", $"/C timeout /t {restartDelay} & start \"\" \"{exePath}\"")
|
||
{
|
||
CreateNoWindow = true,
|
||
UseShellExecute = false
|
||
};
|
||
|
||
Process.Start(psi);
|
||
Log.Info("---------------------------------------");
|
||
Log.Info($"Avviato task di riavvio | delay {restartDelay}sec");
|
||
Log.Info("---------------------------------------");
|
||
|
||
// Termina l'istanza corrente dopo il ritardo
|
||
Application.Exit();
|
||
}
|
||
|
||
#endregion Public Methods
|
||
|
||
#region Protected Fields
|
||
|
||
/// <summary>
|
||
/// L'ora successiva prevista per il controllo automatico del riavvio (in base alla configurazione).
|
||
/// Utilizzata per determinare quando deve essere attivato il riavvio automatico.
|
||
/// </summary>
|
||
protected DateTime tOutAutocheck = DateTime.Now;
|
||
|
||
#endregion Protected Fields
|
||
|
||
#region Private Fields
|
||
|
||
/// <summary>
|
||
/// Istanza del logger per il logging strutturato durante l'intero ciclo di vita dell'applicazione.
|
||
/// </summary>
|
||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||
|
||
/// <summary>
|
||
/// Contatore che segnala il numero di raccolte GC consecutive.
|
||
/// Utilizzato per rilevare pressione di memoria prolungata e attivare un riavvio forzato se il limite è superato.
|
||
/// </summary>
|
||
private int currSeqGC = 0;
|
||
|
||
/// <summary>
|
||
/// Memoria gestita allocata dal runtime .NET (in byte).
|
||
/// Monitorata per rilevare pressione di memoria e attivare GC o riavvio.
|
||
/// </summary>
|
||
private long dotNetMemUsed = 0;
|
||
|
||
/// <summary>
|
||
/// Ora della ultima raccolta GC forzata.
|
||
/// Utilizzata per garantire raccolte periodiche anche se la memoria non è criticamente elevata.
|
||
/// </summary>
|
||
private DateTime LastForcedGC = DateTime.Now;
|
||
|
||
/// <summary>
|
||
/// Numero massimo di raccolte GC consecutive ammesse prima dell'attivazione del riavvio.
|
||
/// Predefinito a 5 — se superato, l'applicazione si riavvia per evitare esaurimento della memoria.
|
||
/// </summary>
|
||
private int maxConsGC = 5;
|
||
|
||
/// <summary>
|
||
/// Limite massimo di consumo di memoria ammesso (in MB) per l'applicazione.
|
||
/// Predefinito a 128 MB — se la memoria gestita o totale supera la metà di questo valore, si attiva GC o riavvio.
|
||
/// </summary>
|
||
private double maxMemoryLimitMb = 128.0;
|
||
|
||
/// <summary>
|
||
/// Generatore di numeri casuali utilizzato per introdurre ritardi leggeri nell'avvio dei timer.
|
||
/// Utilizzato per prevenire che tutti i timer inizino simultaneamente.
|
||
/// </summary>
|
||
private Random rand = new Random();
|
||
|
||
/// <summary>
|
||
/// Memoria privata utilizzata dal processo corrente (in byte).
|
||
/// Utilizzata per rilevare pressione di memoria al livello del sistema.
|
||
/// </summary>
|
||
private long totalMemUsed = 0;
|
||
|
||
#endregion Private Fields
|
||
|
||
#region Private Properties
|
||
|
||
/// <summary>
|
||
/// Servizio singleton per il controllo dell'applicazione (ad esempio gestione servizi esterni, configurazione, riavvii).
|
||
/// </summary>
|
||
private static AppControlService ACService { get; set; } = null!;
|
||
|
||
/// <summary>
|
||
/// Servizio singleton per la gestione delle richieste di log Flux e interazioni UI.
|
||
/// </summary>
|
||
private static FluxLogManService FLMService { get; set; } = null!;
|
||
|
||
/// <summary>
|
||
/// Converte la memoria gestita .NET (in byte) in MB per il logging e il display.
|
||
/// </summary>
|
||
private double dotNetMemUsedMb
|
||
{
|
||
get => dotNetMemUsed / 1024.0 / 1024.0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Salva la dimensione nota del form per mantenere una posizione coerente.
|
||
/// </summary>
|
||
private Size lastSize { get; set; } = new Size(1200, 700);
|
||
|
||
/// <summary>
|
||
/// Converte la memoria totale del processo (in byte) in MB per il logging e il display.
|
||
/// </summary>
|
||
private double totalMemUsedMb
|
||
{
|
||
get => totalMemUsed / 1024.0 / 1024.0;
|
||
}
|
||
|
||
#endregion Private Properties
|
||
|
||
#region Private Methods
|
||
|
||
/// <summary>
|
||
/// Gestisce un aggiornamento della configurazione fornito dal servizio AppControlService.
|
||
///
|
||
/// Azioni:
|
||
/// - Ferma e riavvia i timer per riflettere i nuovi intervalli di aggiornamento.
|
||
/// - Aggiorna il tempo previsto per il controllo del riavvio in base alla configurazione.
|
||
/// </summary>
|
||
private void ACService_EA_ConfigUpdated()
|
||
{
|
||
timerUI.Stop();
|
||
timerTask.Stop();
|
||
tOutAutocheck = ACService.VetoAutoCheck;
|
||
StartTimer();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gestisce una richiesta di riavvio fornita dal servizio AppControlService.
|
||
///
|
||
/// Azioni:
|
||
/// - Ferma tutti i timer e i servizi.
|
||
/// - Chiude tutti i processi esterni.
|
||
/// - Attiva un riavvio forzato.
|
||
/// </summary>
|
||
private void ACService_EA_RebootRequested()
|
||
{
|
||
Log.Info("---------------------------------------");
|
||
Log.Info("Starting Reboot Request");
|
||
Log.Info("---------------------------------------");
|
||
timerUI.Stop();
|
||
timerUI.Dispose();
|
||
timerTask.Stop();
|
||
timerTask.Dispose();
|
||
ACService.DoCloseAll(false);
|
||
RestartApplication(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Pulisce gli event handler e le risorse quando il form viene chiuso.
|
||
///
|
||
/// Azioni:
|
||
/// - Rimuove le sottoscrizioni degli eventi dai servizi.
|
||
/// - Ferma e dismette i timer.
|
||
/// - Chiude tutti i processi esterni.
|
||
/// </summary>
|
||
private void BlazorForm_FormClosing(object sender, FormClosingEventArgs e)
|
||
{
|
||
ACService.EA_ConfigUpdated -= ACService_EA_ConfigUpdated;
|
||
ACService.EA_RebootRequested -= ACService_EA_RebootRequested;
|
||
FLMService.EA_FluxLogReq -= FLMService_EA_FluxLogReq;
|
||
timerUI.Stop();
|
||
timerUI.Dispose();
|
||
timerTask.Stop();
|
||
timerTask.Dispose();
|
||
ACService.DoCloseAll(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Imposta la posizione del form al centro in basso dello spazio dello schermo.
|
||
/// Assicura un posizionamento coerente in diverse configurazioni di schermo.
|
||
/// </summary>
|
||
private void BlazorForm_Load(object sender, EventArgs e)
|
||
{
|
||
SetPosition();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Controlla periodicamente l'uso della memoria e attiva la raccolta di rifiuti se i limiti sono superati.
|
||
///
|
||
/// Logica:
|
||
/// - Misura la memoria gestita e la memoria totale.
|
||
/// - Se la memoria gestita supera la metà del limite massimo (maxMemoryLimitMb) o la memoria totale supera maxMemoryLimitMb:
|
||
/// - Forza la raccolta di rifiuti (GC.Collect).
|
||
/// - Aumenta il contatore di raccolte consecutive.
|
||
/// - Se il contatore supera maxConsGC → attiva il riavvio dell'applicazione.
|
||
/// - Se più di un'ora è passata dal momento della precedente raccolta forzata → attiva una raccolta periodica.
|
||
/// - Altrimenti, decrementa il contatore per evitare sovrastima.
|
||
/// </summary>
|
||
private void CheckMemory()
|
||
{
|
||
dotNetMemUsed = GC.GetTotalMemory(false);
|
||
Process currentProcess = Process.GetCurrentProcess();
|
||
totalMemUsed = currentProcess.PrivateMemorySize64;
|
||
|
||
Log.Info($"Memory Usage | {dotNetMemUsedMb:F2} / {totalMemUsedMb:F2} MB | Managed / Total");
|
||
|
||
if (dotNetMemUsedMb > maxMemoryLimitMb / 2 || totalMemUsedMb > maxMemoryLimitMb)
|
||
{
|
||
currSeqGC++;
|
||
Log.Info($"Chiamato forzatamente GC.Collect per superamento limite memoria | count: {currSeqGC}");
|
||
GC.Collect();
|
||
LastForcedGC = DateTime.Now;
|
||
if (currSeqGC > maxConsGC)
|
||
{
|
||
Log.Info("---------------------------------------");
|
||
Log.Info($"Superato limite GC consecutivi | count: {currSeqGC}");
|
||
RestartApplication(false);
|
||
}
|
||
}
|
||
else if (DateTime.Now.Subtract(LastForcedGC).TotalHours > 1)
|
||
{
|
||
Log.Info($"Chiamato forzatamente GC.Collect per scadenza periodo");
|
||
GC.Collect();
|
||
LastForcedGC = DateTime.Now;
|
||
}
|
||
else
|
||
{
|
||
if (currSeqGC > 0)
|
||
{
|
||
currSeqGC--;
|
||
Log.Info($"Riduzione contatore GC consecutivi | count: {currSeqGC}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inizializza i valori di configurazione dal servizio AppControlService.
|
||
///
|
||
/// In particolare: imposta maxMemoryLimitMb dal valore di configurazione (ad esempio MaxMemGc).
|
||
/// </summary>
|
||
private void ConfInit()
|
||
{
|
||
maxMemoryLimitMb = ACService.MaxMemGc;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gestisce una richiesta di apertura del form di editing dei log Flux.
|
||
///
|
||
/// Azione:
|
||
/// - Crea e mostra un nuovo form FluxLogData con il codice IOB fornito.
|
||
/// - Consente all'utente di modificare o visualizzare i log di flusso per un IOB specifico.
|
||
/// </summary>
|
||
/// <param name="codIOB">Il codice IOB associato al log richiesto.</param>
|
||
private void FLMService_EA_FluxLogReq(string codIOB)
|
||
{
|
||
FluxLogData FldForm = new FluxLogData(FLMService, codIOB);
|
||
FldForm.Show();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inizializza la vista Blazor WebView con i servizi necessari e i componenti principali.
|
||
///
|
||
/// Passi:
|
||
/// - Imposta una ServiceCollection con le dipendenze richieste.
|
||
/// - Registra AppControlService e FluxLogManService come servizi singleton.
|
||
/// - Configura la BlazorWebView per caricare la pagina index.html.
|
||
/// - Aggiunge il componente MainBlazor al nodo radice.
|
||
/// </summary>
|
||
private void InitBlazorView()
|
||
{
|
||
Stopwatch sw = new Stopwatch();
|
||
sw.Start();
|
||
try
|
||
{
|
||
Log.Trace($"TrayMenu OK | {sw.ElapsedMilliseconds}ms");
|
||
var services = new ServiceCollection();
|
||
Log.Trace($"Add ServiceCollection | {sw.ElapsedMilliseconds}ms");
|
||
services.AddWindowsFormsBlazorWebView();
|
||
Log.Trace($"Add AddWindowsFormsBlazorWebView | {sw.ElapsedMilliseconds}ms");
|
||
services.AddSingleton<AppControlService>(ACService);
|
||
services.AddSingleton<FluxLogManService>(FLMService);
|
||
Log.Trace($"Add Singleton Services | {sw.ElapsedMilliseconds}ms");
|
||
blazorWebView1.HostPage = "wwwroot\\index.html";
|
||
blazorWebView1.Services = services.BuildServiceProvider();
|
||
blazorWebView1.RootComponents.Add<MainBlazor>("#app");
|
||
Log.Trace($"Add MainBlazor | {sw.ElapsedMilliseconds}ms");
|
||
}
|
||
catch (Exception exc)
|
||
{
|
||
Log.Error($"Exception during startup{Environment.NewLine}{exc}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inizializza i servizi principali (AppControlService, FluxLogManService).
|
||
///
|
||
/// Registra eventi per:
|
||
/// - Aggiornamento della configurazione
|
||
/// - Richieste di riavvio, ricarica e riavvio
|
||
/// - Richieste di log Flux
|
||
/// </summary>
|
||
private void ServiceInit()
|
||
{
|
||
ACService = new AppControlService(true);
|
||
FLMService = new FluxLogManService();
|
||
ACService.EA_ConfigUpdated += ACService_EA_ConfigUpdated;
|
||
ACService.EA_RebootRequested += ACService_EA_RebootRequested;
|
||
FLMService.EA_FluxLogReq += FLMService_EA_FluxLogReq;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Centra il form nella parte inferiore a sinistra dell'area di lavoro dello schermo.
|
||
/// Garantisce una posizione UI coerente indipendentemente dalla risoluzione dello schermo.
|
||
/// </summary>
|
||
private void SetPosition()
|
||
{
|
||
Rectangle workArea = Screen.GetWorkingArea(this);
|
||
this.Location = new Point((workArea.Right - lastSize.Width) / 2, (workArea.Bottom - lastSize.Height) / 2);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Avvia i timer in background in base ai valori di configurazione forniti dal servizio AppControlService.
|
||
///
|
||
/// Timer:
|
||
/// - timerUI: esegue uno scansionamento dello stato dell'applicazione ogni intervallo (RefreshPeriod).
|
||
/// - timerTask: verifica le condizioni per il riavvio automatico ogni intervallo (CheckRestartPeriod).
|
||
/// - timerMemCheck: monitora l'uso della memoria ogni intervallo (CheckMemoryPeriod).
|
||
///
|
||
/// Nota: Introduce un ritardo di 20–80 ms per evitare che tutti i timer inizino contemporaneamente.
|
||
/// </summary>
|
||
private void StartTimer()
|
||
{
|
||
timerUI.Interval = (ACService.RefreshPeriod);
|
||
timerTask.Interval = (ACService.CheckRestartPeriod);
|
||
timerMemCheck.Interval = (ACService.CheckMemoryPeriod);
|
||
|
||
timerUI.Start();
|
||
Thread.Sleep(rand.Next(20, 80));
|
||
timerTask.Start();
|
||
Thread.Sleep(rand.Next(20, 80));
|
||
timerMemCheck.Start();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Evento del timer periodico per il controllo della memoria.
|
||
///
|
||
/// Attiva CheckMemory() per monitorare l'uso della memoria e forza la raccolta GC se necessario.
|
||
/// Inoltre, registra statistiche dei servizi per il debug.
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void timerMemCheck_Tick(object sender, EventArgs e)
|
||
{
|
||
CheckMemory();
|
||
ACService.PrintStats();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Evento del timer periodico per le attività di verifica.
|
||
///
|
||
/// Azioni:
|
||
/// - Ferma il timer per evitare sovrapposizioni.
|
||
/// - Verifica le condizioni per un riavvio automatico dei servizi esterni.
|
||
/// - Riavvia il timer per mantenere il monitoraggio continuo.
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void timerTask_Tick(object sender, EventArgs e)
|
||
{
|
||
timerTask.Stop();
|
||
ACService.DoAutoRestart(false);
|
||
timerTask.Start();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Evento del timer periodico per lo scansionamento dell'interfaccia utente.
|
||
///
|
||
/// Azioni:
|
||
/// - Ferma il timer per evitare sovrapposizioni.
|
||
/// - Esegue ACService.DoScan() per verificare lo stato dell'applicazione.
|
||
/// - Riattiva il timer per mantenere il monitoraggio continuo.
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private async void timerUI_Tick(object sender, EventArgs e)
|
||
{
|
||
timerUI.Stop();
|
||
await ACService.DoScan();
|
||
timerUI.Start();
|
||
}
|
||
|
||
#endregion Private Methods
|
||
}
|
||
} |