# An alle Cracks: Anwendung beenden mit ShutdownHook?



## aldi15 (20. Feb 2009)

Hallo Alle zusammen,
ich brauche mal Rat von einem alten Java-Hasen:
ich habe ein Programm, das in zyklischen Abständen einen Ordner auf Dateien checkt und wenn vorhanden, eine Bildverarbeitung darauf anwendet. Das Programm läuft in einer endlosen WHILE-Schleife mit einem Flag als Abbruchbedingung. Wenn das Programm beendet werden soll, wird das mit einem "kill" passieren müssen. Damit das Programm aber nicht mitten in einer Bildverarbeitung unterbrochen wird, benötigt es einen Schutz, damit wenigstens noch die aktuelle Datei fertig abgearbeitet wird.
Ich habe es mit einem ShutdownHook probiert, aber das macht nicht, was ich von ihm erwarte:

-Anlegen eines ShutdownHook in einer eingebetteten Klasse:

```
private class MyShutdownHook extends Thread {
        @Override
        public void run() {
            log.SetLogmessage("Die Anwendung wird jetzt heruntergefahren.", enumLoglevel.SEVERE);
            StopSearch(); //Setzt die Abbruchbedingung in der While-Schleife
            log.SetLogmessage("Abgeschaltet.", enumLoglevel.SEVERE);
            log.CloseLogger();
            //ruft den Garbage Collector auf
             this.finalize();
        }
    }
```
-Der Hook wird im Konstruktor der Hauptklasse initialisiert:

```
//Füge den Shutdown-Hook hinzu
        MyShutdownHook objShutdownhook = new MyShutdownHook();
        Runtime.getRuntime().addShutdownHook(objShutdownhook);
```
-Mein Gedanke hierbei: Die Aktion 'Programm Beenden' (das kill) wird vom ShutdownHook abgefangen; In der Klasse MyShutdownHook wird die Abbruchbedingung für die while-Schleife im Haupthread  gesetzt, die Schleife steigt nach einem KOMPLETTEN Durchlauf aus und die Anwendung kann beendet werden. Soweit die Theorie. Hier die Praxis:


Es scheint so, daß beim Beenden-Signal zuerst der Hauptthread (In dem Fall die While-Schleife) abgebrochen wird, bevor der ShutdownHook aktiviert wird (das Log zeigt keine Aktivitäten mehr in der Schleife). Wenn das der Fall ist, ist die ganze Überlegung falsch und es müsste ein anderes Konzept her.
Da die Initialisierung des ShutdownHook im Konstruktor stattfindet (wo auch der Haupthread angestoßen wird), wird beim Beenden wohl nochmal in den Konstruktor gegangen und der Code dort ausgeführt, d.h. ein 2. Haupthread gestartet. Möglicherweise hängt 1.) auch mit 2.) zusammen. Ich habe auch probiert, den Shutdownhook in einer separaten Funktion unterzubringen, die nichts mit dem Haupthread zu tun hat, aber dann ist der Hook gar nicht ausgeführt worden.
Hello World wird im Hook ausgeführt, aber die Bildverarbeitung wohl nicht. Ich habe gelesen, dass keine unfangreichen Aktionen mehr durch den Hook ausgeführt werden sollen. Evtl. ist das zu viel?

Ich bin leider nicht so thread safe  d.h. wenn jemand damit Erfahrung hat und helfen könnte, wäre das super. Evtl. gibts ja ein anderes Konzept, was hier besser passt?
Danke
Albrecht


----------



## Zed (20. Feb 2009)

Der ShutdownHook funktioniert so nicht. Wenn du in deinem Programm die System.exit() aufrufst dann wird der Hook ausgeführt. Wenn du dem Prozess die Gurgel mit Kill duchschneidest wird es nicht funktionieren. 

Alternativ wäre es eine Möglichkeit dem User die Möglichkeit per SystemTray Icon das Beenden des Programms zu erlauben. So ein Icon stört ja nicht wirklich.

Ich hab mal im Sun forum gelesen dass wenn man die VM abschiesst z.B. durch herrunterfahren des Rechners, der Hook auch ausgeführt wird.


----------



## aldi15 (20. Feb 2009)

Hallo Zed,
da muss ich widersprechen. Sowohl mit kill -15, als auch mit schließen des Kommandozeilen-Fensters wird der Hook ausgeführt - Allerdings nur mit den beschriebenen Einschränkungen. System Tray ist leider nicht, da ich unter Linux arbeite. 
Das Problem ist, dass ich einen Dienst draus machen muss und leider habe ich die Vorgabe ohne Fensterchen zu arbeiten. D.h. ich muss die Anwendung von stderr, stdin und stdout (außer logging) abkoppeln. Da geht dann nicht mehr viel.
Gruß
Albrecht


----------



## Geeeee (20. Feb 2009)

Über namedPipes, lockfiles oder einen nur lokal zulässigen port steuern. Dann musste ihn auch net killen.


----------



## Zed (20. Feb 2009)

Schon mal die API gelesen? 
http://java.sun.com/javase/6/docs/api/java/lang/Runtime.html#addShutdownHook(java.lang.Thread)



> In rare circumstances the virtual machine may _abort_, that is,  stop running without shutting down cleanly.  This occurs when the  virtual machine is terminated externally, for example with the  SIGKILL signal on Unix or the TerminateProcess call on  Microsoft Windows.  The virtual machine may also abort if a native  method goes awry by, for example, corrupting internal data structures or  attempting to access nonexistent memory.  If the virtual machine aborts  then no guarantee can be made about whether or not any shutdown hooks  will be run.


----------



## Spacerat (20. Feb 2009)

Startet man für Anwendungen, die als Dienst laufen sollen, nicht eigentlich einen Dämon-Thread in der Main-Methode? Im Groben Beispiel:

```
public class Service
{
  public static void main(String args[])
  {
    // Argumente parsen
    if("start".equals(arg[0])) {
      // Dienst über Socket abfragen...
      // Nicht vorhanden... starten
      // Vorhanden... Fehlermeldung
    } else if("stop".equals(arg[0])) {
      // Dienst über Socket abfragen...
      // Nicht vorhanden... Fehlermeldung
      // Vorhanden... stoppen
    }
    System.exit(0);
  }
}
```
Beim Starten startet man einen Dämon-Thread, der einen Messageport offen hält, über den man in wieder stoppen kann. Der Dämon-Thread läuft auch weiter, nachdem die Main-Methode schon wieder verlassen bzw. die Kontroll-Anwendung bereits beendet wurde.

@Edit: Sorry Geeeee, Hab' deinen Beitrag übersehen... wahr wohl zu kurz... So ists bissl ausführlicher.


----------



## ice-breaker (20. Feb 2009)

gibts dafür nicht den Apache Commons Daemon der darüber wacht, dass der Prozess auch wieder gestartet wird.

Der hat auch Events, die beim Beenden und Starten aufgerufen werden.


----------



## aldi15 (20. Feb 2009)

Danke erstmal. Deshalb mag ich dieses Forum: Kompetente Leute, Gute Beiträge, keine dummen Sprüche  (auch wenn ich mich nach der Umstellung etwas schwer zrechtfinde) OK, erstmal die Antworten sortieren:
@Zed: 





> Schon mal die API gelesen?


Ja, die api kenne ich. Die Angaben sind so schwammig, dass ich es selbst probieren musste. Wie schon gesagt: Beim testen mit 
	
	
	
	





```
System.out.println("Hello World")
```
 hat es funktioniert, nach dem Schließen der Kommandozeile wurde der Hook ausgeführt. also lag es nahe, das mit der richtigen Anwendung zu probieren, aber die wurde nicht mehr ausgeführt 

@Geee 





> Über namedPipes, lockfiles oder einen nur lokal zulässigen port steuern


Sagt mir jetzt nichts im Einzelnen, aber werd mich mal schlau machen zu den Schlagwörtern.

@Spacerat 





> Startet man für Anwendungen, die als Dienst laufen sollen, nicht eigentlich einen Dämon-Thread in der Main-Methode?


Das ist prinzipiell eine gute Idee, aber kann ich auch noch Eingaben machen, nachdem die Konsole geschlossen wurde? http://barelyenough.org/blog/2005/03/java-daemon/ In diesem Blog wird davon gesprochen, daß Syste.out und System.err geschlossen werden müssen, damit man die Konsole nach dem Aufruf schließen kann. Wenn das reicht, würde es gehen die Konsole zu schließen. Wenn System.in auch noch geschlossen werden müsste, wird es schwierig (aber das habe ich noch nicht getestet, ob das wirklich nötig ist).

@ice-breaker 





> gibts dafür nicht den Apache Commons Daemon der darüber wacht, dass der Prozess auch wieder gestartet wird.


Jep, habe ich auch schon gesehen. Da gibts auch noch weitere ähnliche Frameworks, z.B. http://wrapper.tanukisoftware.org/doc/english/integrate-simple-nix.html oder http://linux.softpedia.com/get/Utilities/Java-Service-Wrapper-1575.shtml. Ich dachte allerdings, dass diese Frameworks trotzdem den ShutdownHook benötigen, um sauber abzuschließen - hier aus dem tanukisoftware: 





> If the application has registered its own shutdown hook, it will be invoked, giving the application a chance to shutdown cleanly. If on the other hand, a shutdown hook is not registered, then the application will suddenly exit.


 Hast du damit Erfahrungen gemacht? Ist das beim Apache-commons daemon genauso, oder ist der intelligenter? Das würde nur dann gehen, wenn der wrapper nicht selbst terminiert, sondern nur das Signal aussendet, mit dem ich dann die Abbruchbedingung setzen kann.
Schöne Grüße
Albrecht


----------



## Spacerat (20. Feb 2009)

Ich bin jetzt nicht Sicher, aber ich nehme mal stark an, das System.err, System.out und System.in die direkte Verbindung zur Konsole sind. Das bedeutet, das sie geschlossen sein müssten, wenn der User das Terminal schliesst, unabhängig davon, ob der Dämon-Thread noch läuft. Einfach mal 'nen DT erstellen, einen FileStreamLogger, in dem man Exceptions reinpinselt, Daemon-Thread zwischendurch mal was in die Konsole kritzeln lassen, Kontroll-Anwendung beenden, Konsole schliessen Datei des FileSteamLogger auslesen bzw. beobachten... (Hoffe das ist verständlich genug)


----------



## Murray (20. Feb 2009)

Zed hat gesagt.:


> Schon mal die API gelesen?
> http://java.sun.com/javase/6/docs/api/java/lang/Runtime.html#addShutdownHook(java.lang.Thread)



Das bezieht sich ja auf "irreguläres" Beenden durch Absturz oder hartes Abschießen per SIGKILL (kill -9).  Dagegen kann man wohl auch wenig tun - wenn die VM (z.B. wegen eines Fehlers im native Code) abstürzt, dann ist sie eben weg und kann ncihts mehr tun. Und SIGKILL können Prozesse m.W. nicht abfangen.

Wird die VM normal beendet (also z.B. per SIGTERM (kill 15)), dann wird der Shutdown-Hook auch ausgeführt.

Hier könnte das Problem vielleicht darin bestehen, dass die Aktion zu lange dauert; möglicherweise tritt aber in der Hauptschleife ja auch beim Beenden eine Exception auf.


----------



## Murray (20. Feb 2009)

Murray hat gesagt.:


> Hier könnte das Problem vielleicht darin bestehen, dass die Aktion zu lange dauert



Eigentlich eher doch nicht, wie man an diesem Beispiel sieht:

```
public class Sticky {
    
    public static void main( String[] args) throws Exception {
        
        //--- "boesen" Shutdown-Hook einhängen
        Runtime.getRuntime().addShutdownHook(new Thread() {
      public void run() {
        System.out.println("Blockierender Shutdown-Hook!!");
        while(true);
      }
    });
    
    //--- endlos warten
      while ( true) Thread.sleep(100);
  }
}
```
Man sieht also, dass der Shutdown-Hook ruhig lange (hier: ewig) laufen kann; das Beenden der VM erfolgt erst dann, wenn der Shutdown-Hook beendet ist. In diesem Fall mit dem Resultat, dass sich die Anwendung nicht mehr regulär beenden lässt.


----------



## Zed (21. Feb 2009)

Dein Code oben der hat am Ende eine Endlosschleife der Hook wird nie ausgefüht weil das Programm sich nicht beendet. Ohne die Endlosschleife läuft der Hook bei mir auch endlos.

Ich hab mal dein Endlosprogramm mit:
taskkill /IM javaw.exe
FEHLER: Der Prozess "javaw.exe" mit PID 3548 konnte nicht beendet werden.
Grund: Die Beendigung dieses Prozesses muss erzwungen werden (mit der Option /F)

und dann mit 
taskkill /F /IM javaw.exe

Der Hook wurde nicht ausgeführt.


----------



## Murray (21. Feb 2009)

Zed hat gesagt.:


> Dein Code oben der hat am Ende eine Endlosschleife der Hook wird nie ausgefüht weil das Programm sich nicht beendet.


Richtig - darum ging es doch: ein Programm, welches in einer Endlosschleife läuft und von außen mit SIGTERM abgebrochen werden soll



Zed hat gesagt.:


> Ich hab mal dein Endlosprogramm mit:
> taskkill /IM javaw.exe
> FEHLER: Der Prozess "javaw.exe" mit PID 3548 konnte nicht beendet werden.
> Grund: Die Beendigung dieses Prozesses muss erzwungen werden (mit der Option /F)


Wie gesagt: der Endlos-Hook führt dazu, dass sich der Task nicht mehr regulär beenden lässt - Ctrl+C im Konsolen bewirkt nichts mehr, und beim Herunterfahren von Windows würde gemeldet, dass das Programm nicht mehr reagiert.



Zed hat gesagt.:


> und dann mit
> taskkill /F /IM javaw.exe
> 
> Der Hook wurde nicht ausgeführt.


Woraus wir erkennen können, dass die /F-Option offenbar wie das "Prozess beenden" im Task-Manager funktioniert. Auch das lässt sich aus gutem Grund nicht abfangen, daher kann die VM nicht reagieren und den Shutdown-Hook nicht mehr ausführen.


----------



## aldi15 (27. Feb 2009)

Hallo zusammen,
tut mir leid, dass etwas Zeit vergangen ist, seit meinem letzten Posting. Ich weiß nicht, ob das jetzt noch jemanden interessiert, aber ich kann inzwischen etwas Licht hier hinein bringen.
Mein Fehler war wohl der, dass ich den ShutdownHook nicht in der Einstiegsklasse deklariert hatte, sondern im Konstruktor einer Nachfolge-Klasse . Weiterhin war ein Fehler, daß die Methode aus einer weiteren nachfolgenden Klasse, die im ShutdownHook aufgerufen wurde, nicht statisch war. Dadurch hat der vom ShutdownHook initialisierte Thread von dieser Klasse einen neuen Thread angelegt, was absolut falsch war, denn der erste Thread wurde dadurch nicht terminiert!
OK. jetzt siehts so aus: der Hook wird in der Einstiegsklasse deklariert und in  





> public static void main(String[] args)


 initialisiert. Die Methode aus der Nachfolgeklasse ist jetzt statisch, so dass kein zusätzlicher Thread erzeugt wird, sondern auf dem Hauptthread operiert wird. Ich habe das mal in diesem Posting codemäßig skizziert.
Wenn ich jetzt entweder ein "kill pid" oder ein  "kill -15 pid" mache, oder wenn ich das Eingabefenster schließe, wartet die VM auf die geregelte Beendigung durch den ShutdownHook. Der nachfolgende Code wird weiter ausgeführt. Lediglich wenn ich ein "kill -9 pid" mache, wird augenblicklich die VM gekillt ohne Rücksicht auf den Hook. Ich habe hier übrigens die Lösung aus diesem Blog gewählt. So weit so gut.
Leider habe ich jetzt noch ein kleines Problemchen: Wenn ich den Prozess beende, wird sobald der ShutdownHook den Flag im Haupthread verändert dort nichts mehr geloggt (s. Link von oben). Komisch. Ich dachte die java logging api ist thread save .
Schöne Grüße
Albrecht


----------

