Un mémo qui expérimentait si un thread qui était hors de contrôle dans une boucle infinie en Java pouvait être arrêté de force de l'extérieur

Conclusion

Conclusion au 21/03/2018:

Environnement d'expérimentation

Expérimenté avec deux types de CentOS6 / 7 (les deux versions x86_64). Commencez avec Compute Engine de GCP et installez les packages suivants.

sudo yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel
sudo yum groupinstall -y "Development tools"

Détails de la version Java (lors de la vérification du 21/03/2018, il était affiché de la même manière que l'environnement CentOS 6/7)

$ java -version
openjdk version "1.8.0_161"
OpenJDK Runtime Environment (build 1.8.0_161-b14)
OpenJDK 64-Bit Server VM (build 25.161-b14, mixed mode)

$ javac -version
javac 1.8.0_161

Exemple de boucle infinie: InfiniteLoop.java

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class InfiniteLoop {
    static class Looper implements Runnable {
        @Override
        public void run() {
            int cnt = 0;
            while (true) {
                //Boucle infinie ignorant les interruptions
                try {
                    System.out.println("InifiniteLoop-Looper count " + cnt);
                    Thread.sleep(2000);
                } catch (InterruptedException ignored) {
                }
                cnt++;
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        Future<?> f = pool.submit(new Looper());
        try {
            // Future.get()Attendre la fin de la tâche
            f.get();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

compiler:

$ javac InfiniteLoop.java

Expérience 1: attacher avec jdb et terminer le thread

référence:

Exécutez la classe InfiniteLoop avec l'option JVM pour attacher plus tard avec jdb.

$ java -Xrunjdwp:transport=dt_socket,address=9000,server=y,suspend=n InfiniteLoop

Obtenez le PID java d'un autre terminal et vérifiez la trace de la pile Java avec jstack.

$ pidof java
13667

$ jstack 13667
2018-03-21 07:52:59
Full thread dump OpenJDK 64-Bit Server VM (25.161-b14 mixed mode):
(...)

"pool-1-thread-1" #10 prio=5 os_prio=0 tid=0x00007f0d840f3b30 nid=0x356f waiting on condition [0x00007f0d6d8c8000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at InfiniteLoop$Looper.run(InfiniteLoop.java:14)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

(...)

À partir de la trace de la pile, nous avons pu confirmer que le thread nommé pool-1-thread-1 était dans une boucle infinie.

Maintenant que nous connaissons le nom du thread, démarrez jdb, attachez-le à la JVM et terminez le thread dans la boucle.

$ jdb -attach 9000
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...

(Afficher la liste des fils)
> threads
Group system:
  (java.lang.ref.Reference$ReferenceHandler)0x1b9 Reference Handler cond. waiting
  (java.lang.ref.Finalizer$FinalizerThread)0x1ba  Finalizer         cond. waiting
  (java.lang.Thread)0x1bb                         Signal Dispatcher running
Group main:
  (java.lang.Thread)0x1                           main              cond. waiting
  (java.lang.Thread)0x1bd                         pool-1-thread-1   sleeping

(Nom du fil"pool-1-thread-1",Assurez-vous que 0x1bd est le thread dans la boucle.)

(Fil de discussion que vous souhaitez arrêter:Suspendre 0x1bd et tuer tout en lançant une exception après avoir progressé)
> thread 0x1bd
pool-1-thread-1[1] suspend 0x1bd
pool-1-thread-1[1] step
>
Step completed: "thread=pool-1-thread-1", InfiniteLoop$Looper.run(), line=16 bci=33
16                    }

pool-1-thread-1[1] kill 0x1bd new java.lang.Exception("kill from jdb")
killing thread: pool-1-thread-1
pool-1-thread-1[1] instance of java.lang.Thread(name='pool-1-thread-1', id=445) killed

(Quittez le débogueur après le redémarrage avec cont)
pool-1-thread-1[1] cont
> exit

Le résultat de l'exécution de Infinite Loop a été affecté par l'opération de jdb comme suit.

$ java -Xrunjdwp:transport=dt_socket,address=9000,server=y,suspend=n InfiniteLoop
(...)
InifiniteLoop-Looper count 32
InifiniteLoop-Looper count 33
InifiniteLoop-Looper count 34
(Exécuter suspend depuis jdb->l'affichage du compte s'arrête)
(Lors de la suppression à partir de jdb, ce qui suit s'affiche)
java.util.concurrent.ExecutionException: java.lang.Exception: kill from jdb
        at java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.util.concurrent.FutureTask.get(FutureTask.java:192)
        at InfiniteLoop.main(InfiniteLoop.java:27)
Caused by: java.lang.Exception: kill from jdb
        at InfiniteLoop$Looper.run(InfiniteLoop.java:16)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
(Lorsque vous quittez jdb, vous verrez ce qui suit)
Listening for transport dt_socket at address: 9000
(Ctrl-Terminer par C)

On peut voir que la tâche Runnable est terminée en tuant depuis jdb, et l'attente Future.get () est libérée en lançant une ExecutionException. Vous pouvez également voir que la cause de l'ExecutionException est l'exception levée en tuant jdb.

Expérience 2: Attachez avec gdb et terminez le thread avec pthread_kill (2)

référence:

Exécutez la classe InfiniteLoop sans aucune option de débogage spéciale.

$ java InfiniteLoop

Vérifiez l'emplacement de l'exécutable java:

$ which java
/usr/bin/java
$ ls -l /usr/bin/java
(...) /usr/bin/java -> /etc/alternatives/java
$ ls -l /etc/alternatives/java
(...) /etc/alternatives/java -> /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java
$ ls -l /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java
(...) /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Vérifiez le PID Java et obtenez une trace de la pile.

$ jstack `pidof java`
2018-03-21 08:28:38
Full thread dump OpenJDK 64-Bit Server VM (25.161-b14 mixed mode):
(...)

"pool-1-thread-1" #8 prio=5 os_prio=0 tid=0x00007ffb500ed000 nid=0x6a05 waiting on condition [0x00007ffb54146000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at InfiniteLoop$Looper.run(InfiniteLoop.java:14)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
(...)

Sous Linux, nid = 0x ... des informations de thread affichées par jstack est l'ID LWP spécifié par pthread_kill (2).

Attachez-le avec le nom de fichier exécutable gdb PID et exécutez la commande ʻinfo threads`.

$ gdb /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java `pidof java`

(gdb) info threads
(...)
  4 Thread 0x7ffb54248700 (LWP 27140)  0x0000003e2020ba5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
  3 Thread 0x7ffb54147700 (LWP 27141)  0x0000003e2020ba5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
  2 Thread 0x7ffb40fff700 (LWP 27164)  0x0000003e2020eb2d in accept () from /lib64/libpthread.so.0
* 1 Thread 0x7ffb57342700 (LWP 27131)  0x0000003e202082fd in pthread_join () from /lib64/libpthread.so.0

Le résultat de l'exécution de la commande ʻinfo threadsavec gdb est l'affichage décimal. Si vous recherchez27141, qui est le nombre décimal de nid = 0x6a05` du thread de la boucle infinie affichée cette fois, le troisième thread en partant du bas lui correspond.

  3 Thread 0x7ffb54147700 (LWP 27141)  0x0000003e2020ba5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0

Nous appelons donc ici call pthread_kill (pthread_t, int) ... ** Que dois-je spécifier pour le premier argument? ** ʻinfo threadsLaquelle des lignes correspondantes du résultat de l'exécution doit être spécifiée? ** Aussi, que faut-il spécifier pour que le signal du deuxième argument soit équivalent àThread.stop ()` de Java? ** **

** En premier lieu, comment puis-je appeler exactement le même comportement que lorsqu'un thread se termine en Java à partir du processus attaché par gdb? ** **

→ C'est une impasse totale. Même si j'essayais de l'appeler correctement, le processus Java s'est terminé par SIGSEGV, et bien que ce ne soit pas très bon, il n'a pas abouti à "isoler et terminer uniquement le thread emballé".

Même si gdb peut reproduire le processus de lever une exception de l'intérieur comme lors de la fin avec jdb en lisant le code source Java ou en analysant l'appel système avec strace, c'est probablement une implémentation interne considérable. N'est-il pas dépendant et non stable et facilement reproductible dans un environnement d'exploitation réel?

Résumé au 21/03/2018

Ce n'était pas bien organisé, mais c'est tout.

Recommended Posts

Un mémo qui expérimentait si un thread qui était hors de contrôle dans une boucle infinie en Java pouvait être arrêté de force de l'extérieur
Un programme (Java) qui génère la somme des nombres pairs et impairs dans un tableau
Exemple de programme qui renvoie la valeur de hachage d'un fichier en Java
L'histoire selon laquelle le servlet n'a pas pu être chargé dans l'application Web Java
Je ne savais pas que les classes internes pouvaient être définies dans l'interface [Java]