Windows shutdown hook på java applikation køre fra et flagermus script

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg har et flagermus script, der kører en java applikation. Hvis jeg trykker ctrl + c på den, afslutter ansøgningen yndefuldt og påberåber alle shutdown kroge. Men hvis jeg bare lukker cmd-vinduet i flagermusskriptet, bliver nedlukningskrogene aldrig påkaldt.


Er der en måde at løse dette på? Måske er der en måde at fortælle flagermusskriptet om, hvordan man opsiger de påberåbte applikationer, når vinduet er lukket?

Bedste reference


Fra addShutdownHook dokumentation: [24]



  I sjældne tilfælde kan den virtuelle maskine afbryde, dvs. stoppe med at løbe uden at lukke rent ned. Dette sker, når den virtuelle maskine afsluttes eksternt, for eksempel med SIGKILL-signalet på Unix eller TerminateProcess-opkaldet på Microsoft Windows.



Så jeg tror ikke noget at gøre her, desværre.





CTRL-CLOSE-signal i Windows Console. Synes ikke-tweakable. [25]


Citerer ovenstående link:



  Systemet genererer et CTRL+CLOSE signal, når brugeren lukker en konsol. Alle processer, der er knyttet til konsollen, modtager signalet, hvilket giver hver proces mulighed for at rydde op inden afslutningen. Når en proces modtager dette signal, kan håndteringsfunktionen tage en af ​​følgende handlinger efter at have udført oprydningsoperationer:




  • Ring til ExitProcess for at afslutte processen.

  • Returner FALSE. Hvis ingen af ​​de registrerede håndteringsfunktioner returnerer TRUE, afslutter standardhandleren processen.

  • Returner TRUE. I dette tilfælde kaldes ingen andre håndteringsfunktioner, og en pop op-dialogboks beder brugeren om at afslutte processen. Hvis brugeren vælger ikke at afslutte processen, lukker systemet ikke konsollen, før processen endelig ophører.






UPD . Hvis native tweaks er acceptable for dig, åbner WinAPI SetConsoleCtrlHandler funktionen mulighed for at undertrykke standardadfærd.


UPD2 . Åbenbaringer om Java-signalhåndtering og afslutning af relativt gammel artikel, men sektionen Skrivning af Java-signalhåndteringsprogrammer kan virkelig indeholde hvad du har brug for. [26]





UPD3 .
Jeg har forsøgt Java signalhåndteringsprogrammer fra artiklen ovenfor. Det virker med SIGINT pænt, men det er ikke det, vi har brug for, og jeg besluttede at bære det med SetConsoleCtrlHandler. Resultatet er lidt kompliceret og kan ikke være værd at implementere i dit projekt. Det kan alligevel hjælpe andre.


Så ideen var:



  1. Hold henvisning til shutdown handler tråd.

  2. Angiv brugerdefineret native console handler rutine med JNI.

  3. Ring til brugerdefineret Java-metode på CTRL+CLOSE signal.

  4. Kald shutdown handler fra den metode.



Java kode:


public class TestConsoleHandler {

    private static Thread hook;

    public static void main(String[] args) {
        System.out.println("Start");
        hook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(hook);
        replaceConsoleHandler(); // actually not "replace" but "add"

        try {
            Thread.sleep(10000); // You have 10 seconds to close console
        } catch (InterruptedException e) {}
    }

    public static void shutdown() {
        hook.run();
    }

    private static native void replaceConsoleHandler();

    static {
        System.loadLibrary("TestConsoleHandler");
    }
}

class ShutdownHook extends Thread {
    public void run() {
        try {
            // do some visible work
            new File("d:/shutdown.mark").createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Shutdown");
    }
}


Indfødt replaceConsoleHandler:


JNIEXPORT void JNICALL Java\_TestConsoleHandler\_replaceConsoleHandler(JNIEnv *env, jclass clazz) {
    env->GetJavaVM(&jvm);
    SetConsoleCtrlHandler(&HandlerRoutine, TRUE);
}


Og handler selv:


BOOL WINAPI HandlerRoutine(\_\_in DWORD dwCtrlType) {
    if (dwCtrlType == CTRL\_CLOSE\_EVENT) {
        JNIEnv *env;
        jint res =  jvm->AttachCurrentThread((void **)(&env), &env);
        jclass cls = env->FindClass("TestConsoleHandler");
        jmethodID mid = env->GetStaticMethodID(cls, "shutdown", "()V");
        env->CallStaticVoidMethod(cls, mid);
        jvm->DetachCurrentThread();
        return TRUE;
    }
    return FALSE;
}


Og det virker. I JNI-kode udelades alle fejlkontroller til godkendelse. Shutdown Handler opretter en tom fil "d:shutdown.mark" for at indikere korrekt afbrydelse.


Komplette kilder med kompilerede testbinarier her. [27]

Andre referencer 1


Ud over ovenstående svar ved brug af SetConsoleCtrlHandler kan du også gøre dette ved hjælp af JNA i stedet for at skrive din egen indbyggerkode.


Du kan oprette din egen grænseflade på kernel32, hvis du vil have det, eller brug den, der findes i denne fremragende ramme: https://gitlab.com/axet/desktop[28]


Nogle eksempler kode:


import com.github.axet.desktop.os.win.GetLastErrorException;
import com.github.axet.desktop.os.win.handle.HANDLER\_ROUTINE;
import com.github.axet.desktop.os.win.libs.Kernel32Ex;
...
private static HANDLER\_ROUTINE handler =
  new HANDLER\_ROUTINE()
  {
    @Override
    public long callback(long dwCtrlType) {
      if ((int)dwCtrlType == CTRL\_CLOSE\_EVENT) {
        // *** do your shutdown code here ***
        return 1;
      }
      return 0;
    }
  };

public static void assignShutdownHook() {
  if (!Kernel32Ex.INSTANCE.SetConsoleCtrlHandler(handler, true))
    throw new GetLastErrorException();
}


Bemærk, at jeg tildelte den anonyme klasse til et felt. Jeg definerede det oprindeligt i opkaldet til SetConsoleCtrlHandler, men jeg tror, ​​at det blev hentet af JVM.


Rediger 4/9/17: Opdateret link fra github til gitlab.

Andre referencer 2


Selvom batchfilen kan opsiges, har konsollen (vinduet) batchfilen kørt i, blive åben, afhængigt af operativsystemet, kommandoprocessoren og hvordan batchfilens udførelse blev startet (fra en kommandoprompt eller gennem en genvej).


taskkill er en god kommando for at afslutte et program, der distribueres med Windows (Jeg antager, at du vil stoppe et forskelligt program, ikke selve batchfilen).


Det er muligt at afslutte programmer ved proces id, eksekverbart navn, vinduetitel, status (dvs. ikke reagerer), DLL navn eller servicenavn.


Her er nogle eksempler baseret på, hvordan du kan bruge dette i din batchfil:


Force 'program.exe' for at stoppe (ofte er/f 'force' -flaget nødvendigt for at tvinge programmet til at stoppe, bare kontrollere om det er nødvendigt for din ansøgning ved forsøg og fejl):


taskkill /f /im program.exe 


Stop alle ikke-lydhøre programmer:


taskkill /fi "Status eq NOT RESPONDING"


Stop et program baseret på dets vinduetitel (* wildcard er tilladt her for at matche noget):


taskkill /fi "WindowTitle eq Please Login"

taskkill /fi "WindowTitle eq Microsoft*"


Du kan endda bruge det til at stoppe et program på en anden computer på dit netværk (selvom det er en god ide at skrive adgangskoden til en konto i en batchfil).


taskkill /s JimsPC /u Jim /p James\_007 /im firefox.exe


Et andet alternativ, der også distribueres med Windows, er tskill. Selvom det ikke har næsten lige så mange muligheder, er kommandoerne lidt enklere.


REDIGERE:


Jeg er ikke sikker på, at der er. Jeg tror at lukke cmd-vinduet er beslægtet med force-closing appen, dvs. øjeblikkelig opsigelse uden yderligere varsel. Dette har adfærd fra mange applikationer, at de, når de bliver bedt om at force-close, tager lang tid at endelig afslutte. Dette skyldes, at i OS 's bestræbelser på at frigive alle ressourcer, må nogle ressourcer (især visse I/O - og/eller filressourcer) ikke slippes med det samme.


IMO, der er intet Java kunne endda gøre om det, hvis det ville. En force-close sker på OS-niveau, hvor det frigiver hukommelses-, fil- og I/O-håndtag i brug mv. Java gør det ikke ( der er heller ikke noget andet program) har kontrol over en force-close. På dette tidspunkt tager OS operativsystemet.


Nogen retter mig, hvis jeg er forkert.