Optimzed ShutdownManager for only one clean shutdown

This commit is contained in:
Niklas Aumueller 2026-03-06 17:47:08 +01:00
parent 0804b48d68
commit 6478e046b4
3 changed files with 69 additions and 49 deletions

View File

@ -2,13 +2,10 @@ package vassistent;
import vassistent.bootstrap.ApplicationContext; import vassistent.bootstrap.ApplicationContext;
import vassistent.bootstrap.ApplicationInitializer; import vassistent.bootstrap.ApplicationInitializer;
import vassistent.bootstrap.ApplicationShutdownManager;
import vassistent.controller.AppWindowController; import vassistent.controller.AppWindowController;
import vassistent.service.*;
import vassistent.util.Logger;
import javax.swing.*; import javax.swing.*;
import java.io.File;
/** /**
* Entry point for the virtual health assistant desktop application. * Entry point for the virtual health assistant desktop application.
@ -26,44 +23,13 @@ public class App {
ApplicationContext context = ApplicationContext context =
ApplicationInitializer.initialize(); ApplicationInitializer.initialize();
Runtime.getRuntime().addShutdownHook(new Thread(() -> { ApplicationShutdownManager shutdownManager =
new ApplicationShutdownManager(context);
Logger.info("SYSTEM", "Programm wird beendet"); shutdownManager.registerJvmShutdownHook();
deleteDatabase();
Logger.shutdown();
}));
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
new AppWindowController(context) new AppWindowController(context, shutdownManager)
.createAndShowWindow(); .createAndShowWindow();
}); });
} }
/**
* Deletes the runtime SQLite database file if it exists.
*/
private static void deleteDatabase() {
try {
File dbFile = new File("data/health.db");
if (dbFile.exists()) {
boolean deleted = dbFile.delete();
if (deleted) {
Logger.info("SYSTEM", "Datenbank gelöscht");
} else {
Logger.warn("SYSTEM", "Datenbank konnte nicht gelöscht werden");
}
}
} catch (Exception e) {
Logger.error("SYSTEM", "DB Löschung fehlgeschlagen", e);
}
}
} }

View File

@ -4,20 +4,36 @@ import vassistent.service.MqttClientService;
import vassistent.service.ProcessManagerService; import vassistent.service.ProcessManagerService;
import vassistent.util.Logger; import vassistent.util.Logger;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Coordinates graceful shutdown of MQTT and managed external processes. * Coordinates graceful shutdown of MQTT, managed external processes, and runtime artifacts.
*/ */
public class ApplicationShutdownManager { public class ApplicationShutdownManager {
private static final String RUNTIME_DB_FILE = "data/health.db";
private final ApplicationContext context; private final ApplicationContext context;
private final AtomicBoolean shutdownStarted = new AtomicBoolean(false);
private final AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
/** /**
* Creates a shutdown manager and registers a JVM shutdown hook. * Creates a shutdown manager for the provided application context.
* *
* @param context initialized application context * @param context initialized application context
*/ */
public ApplicationShutdownManager(ApplicationContext context) { public ApplicationShutdownManager(ApplicationContext context) {
this.context = context; this.context = context;
}
/**
* Registers the JVM shutdown hook once for this manager instance.
*/
public void registerJvmShutdownHook() {
if (!shutdownHookRegistered.compareAndSet(false, true)) {
return;
}
Runtime.getRuntime().addShutdownHook( Runtime.getRuntime().addShutdownHook(
new Thread(this::shutdown) new Thread(this::shutdown)
@ -28,11 +44,18 @@ public class ApplicationShutdownManager {
* Executes shutdown steps for managed integrations. * Executes shutdown steps for managed integrations.
*/ */
public void shutdown() { public void shutdown() {
if (!shutdownStarted.compareAndSet(false, true)) {
return;
}
Logger.info("SHUTDOWN", "Shutdown Sequencing gestartet"); Logger.info("SHUTDOWN", "Shutdown sequencing started");
disconnectMqtt(); disconnectMqtt();
stopExternalProcesses(); stopExternalProcesses();
deleteRuntimeDatabase();
Logger.info("SHUTDOWN", "Shutdown sequencing finished");
Logger.shutdown();
} }
/** /**
@ -47,9 +70,9 @@ public class ApplicationShutdownManager {
mqtt.disconnect(); mqtt.disconnect();
} }
Logger.info("SHUTDOWN", "MQTT getrennt"); Logger.info("SHUTDOWN", "MQTT disconnected");
} catch (Exception e) { } catch (Exception e) {
Logger.error("SHUTDOWN", "MQTT Shutdown Fehler", e); Logger.error("SHUTDOWN", "MQTT shutdown failed", e);
} }
} }
@ -66,10 +89,36 @@ public class ApplicationShutdownManager {
pm.shutdown(); pm.shutdown();
} }
Logger.info("SHUTDOWN", "Externe Prozesse beendet"); Logger.info("SHUTDOWN", "External processes stopped");
} catch (Exception e) { } catch (Exception e) {
Logger.error("SHUWTDOWN", "Process Shutdown Fehler", e); Logger.error("SHUTDOWN", "Process shutdown failed", e);
}
}
/**
* Deletes the runtime SQLite database file when present.
*/
private void deleteRuntimeDatabase() {
try {
File dbFile = new File(RUNTIME_DB_FILE);
if (!dbFile.exists()) {
return;
}
boolean deleted = dbFile.delete();
if (deleted) {
Logger.info("SHUTDOWN", "Runtime database deleted");
} else {
Logger.warn("SHUTDOWN", "Runtime database could not be deleted");
}
} catch (Exception e) {
Logger.error("SHUTDOWN", "Runtime database deletion failed", e);
} }
} }
} }

View File

@ -12,6 +12,7 @@ import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
import java.util.Objects;
/** /**
* Controls the main window lifecycle and synchronizes UI elements with app state. * Controls the main window lifecycle and synchronizes UI elements with app state.
@ -27,10 +28,14 @@ public class AppWindowController {
* Creates a controller for the main application window. * Creates a controller for the main application window.
* *
* @param context initialized application context with required services * @param context initialized application context with required services
* @param shutdownManager shutdown coordinator shared with application bootstrap
*/ */
public AppWindowController(ApplicationContext context) { public AppWindowController(
this.context = context; ApplicationContext context,
this.shutdownManager = new ApplicationShutdownManager(context); ApplicationShutdownManager shutdownManager
) {
this.context = Objects.requireNonNull(context);
this.shutdownManager = Objects.requireNonNull(shutdownManager);
subscribeAppState(); subscribeAppState();
} }