Ok,

Yes that would be hard to track down hooks "blocked" either directly or 
indirectly that way. Indirect blocking case are the hardest one, like a hook 
joining another thread that is blocked calling System.exit. This hook might 
really be blocked if joining again and again regardless of any join 
timeout/join interruption, and maybe just delayed because it joined with a 
timeout without retrying after a timeout: in both case, at some point, both 
logic (the blocked and not-so-blocked ones) have the same kind of stack and 
inter-thread join sequence. So scanning on join-chains is not reliable. So 
detecting blocked hooks is not either. I might have overlooked other solutions, 
but I think keeping the blocked threads alive and try to be smart at having the 
hook detected/handle them is a deadend.

Having a thread calling System.exit while System.exit is already in progress 
being able to "exit" itself (like pthread_exit for posix threads for exemple) 
would also solve those hook joining blocked thread scenario.

Having a thread being able to cease execution change what could be expected 
from the documented behavior. In term of execution flow, having the thread 
blocked indefinitely on a monitor enter or have it cease execution is the same 
: it won't run any other piece of code. Calling Thread.stop while on threads 
trying to monitor enter the synchronized block has no effect: they need to 
"enter" the monitor before being able to see the ThreadDeath error to throw. 
The thread running inside the synchronized block will never do a monitor exit 
since it calls halt within the synchronized boundaries. The difference between 
blocked versus cease is the garbage collector behavior: the blocked thread is 
still alive, still holds refs that cannot be GCed. the thread that cease 
execution allow objects to be GCed. Even if I don't think that would really be 
an issue, I still have to mention it.

By the way, the hook calling System.exit might be tricky to see/avoid. My demo 
code is simple but I got this more or less in this kind of execution flow
public static void main(String[] args) {
    Thread.setDefaultUncaughtExceptionHandler((th, trw)->{
        /* if trw is considered a fatal throwable then ask for a graceful JVM 
shutdown */ System.exit(1);
        /* OOM errors often screw too many things, leaving not-daemon thread 
hanging around, preventing JVM standard shutdown without either a System.exit 
or a Runtime.halt, so I think OOM errors are good candidates for being fatal 
throwables */
    });
    Runtime.getRuntime().addShutdownHook(new Thread("hook") {
        public void run() {
            // the hook does not need to do a hard work to throw an OOM, if 
running inside a saturated JVM, just a new String to log some message will do
            throw new OutOfMemoryError("error in hook");
        }
    });
}

Regards,
Remi


Orange Restricted

-----Message d'origine-----
De : David Holmes <david.hol...@oracle.com> 
Envoyé : mardi 26 avril 2022 01:24
À : CATHERINOT Rémi DTSI/PFS <remi.catheri...@orange.com>; 
core-libs-dev@openjdk.java.net
Objet : Re: System.exit deadlock if called within a hook

On 25/04/2022 11:08 pm, remi.catheri...@orange.com wrote:
> Hi,
> 
> Exemple code to deadlock the JVM. Only Runtime.halt from within the JVM or a 
> SIGKILL from outside stops it. Normal kills and Ctrl-C (which is a SIGTERM) 
> fail to do so.
> 
> public class ExitInHookDemo {
>              public static void main(String...args) {
>                          Runtime.getRuntime().addShutdownHook(new 
> Thread("hook") {
>                                      public void run() { 
> System.exit(1); } });
>              }
> }

This is a documented usage limitation of exit().

" All registered shutdown hooks, if any, are started in some unspecified order 
and allowed to run concurrently until they finish. Once this is done the 
virtual machine halts.

If this method is invoked after all shutdown hooks have already been run and 
the status is nonzero then this method halts the virtual machine with the given 
status code. Otherwise, this method blocks indefinitely. "

Here you have a hook that calls exit() before all hooks have been run, hence it 
blocks indefinitely. As the hook blocks indefinitely the running of hooks 
cannot finish as required and so the virtual machine does not halt.

Conceptually you are asking for the indefinitely blocked hook to be treated 
as-if "finished", but there is no actual mechanism to implement that. You don't 
need to "destroy" the thread to force it to be "finished", but you do need a 
way for exit() to check if the current thread is a hook and to adapt the logic 
so that thread.join() is not used to wait for true termination. It would be 
possible to construct something I think, but I don't know if such an 
enhancement would be considered worthwhile versus don't call exit() from a hook.

Cheers,
David
-----

> I've tried to more or less do a pure java patch in java.lang.Shutdown 
> but the only way to have a real patch would be to have Thread.destroy 
> still there and implemented or anything allowing Shutdown to destroy a 
> thread. In essence, System.exit is a no return method call. So using 
> Thread.stop which raise a ThreadDeath error is not enough (the method 
> go back to the caller with the error). Only Thread.destroy has the 
> contract of being a no return call. Yes the documentation said it is 
> unsafe because it does not release locks & co, but right now that is 
> exactly what is happening, so Thread.destroy limitations are 
> acceptable. Throwing something that is not catchable, something legit 
> to throw but not legit to catch, even by a catch Throwable, is not 
> enough because it would still run finally blocks, which cannot happen 
> for no return methods like System.exit and Runtime.halt: try { 
> System.exit(0); /* code that should never be reached */ } catch 
> (Throwable t) { /* code that should never be reached */ } finally { /* 
> code that should also never be reached */ }
> 
> I've searched the known/documented bugs but I either missed the fact it was 
> already reported, or I only got entries about removing Thread.destroy.
> 
> By the way, I'm not really asking to have Thread.destroy implemented were it 
> is currently defined (a public method inside Thread). I mean at least 
> java.lang.Shutdown should have a way to destroy Threads, even a private one. 
> Threads destroyed that way should make threads stuck joining the destroyed 
> one must be released.
> 
> I got the bug on java 1.8, oracle and openjdk ones. Not tested it one >=9 
> ones.
> 
> Here is what the approximation of a patch using pure java, but I'm no jdk 
> expert and had no access to thread destruction, so it is not perfect. The 
> code is from java.lang.Shutdown
>      static void exit(int status) {
>          boolean wasRunning = false; // < - - new var
>          boolean runMoreFinalizers = false;
>          synchronized (lock) {
>              switch (state) {
>              case RUNNING:       /* Initiate shutdown */
>                  state = HOOKS;
>                  wasRunning = true; // < - - first call to System.exit, the 
> caller will run all of what is needed -hooks, maybe finalizers, etc.
>                  if (status != 0) runFinalizersOnExit = false; // < - - also 
> moved the runFinalizersOnExit block to avoid having it changed/altered by 
> several threads trying to exit with different status code
>                  break;
>              case HOOKS:         /* Stall and halt */
>                  break;
>              case FINALIZERS:
>                  if (status != 0) {
>                      /* Halt immediately on nonzero status */
>                      halt(status);
>                  } else {
>                      /* Compatibility with old behavior:
>                       * Run more finalizers and then halt
>                       */
>                      runMoreFinalizers = runFinalizersOnExit;
>                  }
>                  break;
>              }
>          }
>          if (!wasRunning) Thread.currentThread().stop(); // < - - should be a 
> destroy rather than a stop. This thread is not the 1st who called 
> System.exit, and since it should never return, it must die asap. We are 
> outside of the synchronized block so if any lock remains acquired by this 
> thread, they are acquired by the caller which should have released them 
> before calling exit on the 1st place. If the caller called us while having 
> locks, it is a caller bug, not a Shutdown.exit one. Current implementation 
> since it never returns (and yield the stuck bug I report) also never release 
> any of the locks it has too
>          // < - - beyond this line nothing has been modified
>          if (runMoreFinalizers) {
>              runAllFinalizers();
>              halt(status);
>          }
>          synchronized (Shutdown.class) {
>              /* Synchronize on the class object, causing any other thread
>               * that attempts to initiate shutdown to stall indefinitely
>               */
>              sequence();
>              halt(status);
>          }
>      }
> 
> 
> Regards,
> Ogrom.
> 
> ______________________________________________________________________
> ___________________________________________________
> 
> Ce message et ses pieces jointes peuvent contenir des informations 
> confidentielles ou privilegiees et ne doivent donc pas etre diffuses, 
> exploites ou copies sans autorisation. Si vous avez recu ce message 
> par erreur, veuillez le signaler a l'expediteur et le detruire ainsi que les 
> pieces jointes. Les messages electroniques etant susceptibles d'alteration, 
> Orange decline toute responsabilite si ce message a ete altere, deforme ou 
> falsifie. Merci.
> 
> This message and its attachments may contain confidential or 
> privileged information that may be protected by law; they should not be 
> distributed, used or copied without authorisation.
> If you have received this email in error, please notify the sender and delete 
> this message and its attachments.
> As emails may be altered, Orange is not liable for messages that have been 
> modified, changed or falsified.
> Thank you.
> 

_________________________________________________________________________________________________________________________

Ce message et ses pieces jointes peuvent contenir des informations 
confidentielles ou privilegiees et ne doivent donc
pas etre diffuses, exploites ou copies sans autorisation. Si vous avez recu ce 
message par erreur, veuillez le signaler
a l'expediteur et le detruire ainsi que les pieces jointes. Les messages 
electroniques etant susceptibles d'alteration,
Orange decline toute responsabilite si ce message a ete altere, deforme ou 
falsifie. Merci.

This message and its attachments may contain confidential or privileged 
information that may be protected by law;
they should not be distributed, used or copied without authorisation.
If you have received this email in error, please notify the sender and delete 
this message and its attachments.
As emails may be altered, Orange is not liable for messages that have been 
modified, changed or falsified.
Thank you.

Reply via email to