Should System.exit be controlled by a Scope Local?

David Holmes david.holmes at oracle.com
Mon Feb 28 00:02:47 UTC 2022


On 28/02/2022 8:20 am, Ethan McCue wrote:
> My understanding is that when you System.exit all threads associated 
> with the JVM process are killed. That's what I meant by "nuclear 
> Thread.interrupt".

The process is terminated, the threads are not individually "killed". 
All Thread.interrupt does is set a flag and unpark blocked threads (in 
some specific cases). There's really no comparison at all.

David
-----

> It's the same issue as was raised about System.exit implicitly ending 
> control flow or implicitly closing open file handles - a process could 
> be relying on the behavior of implicitly killing all threads and not 
> have another cleanup mechanism
> 
> On Sun, Feb 27, 2022, 5:16 PM David Holmes <david.holmes at oracle.com 
> <mailto:david.holmes at oracle.com>> wrote:
> 
>     On 28/02/2022 3:20 am, Ethan McCue wrote:
>      > I think continuations could work for the single threaded case,
>     depending on
>      > their behavior with "finally" blocks. I'm sure there are more
>     caveats once
>      > we add another thread to the mix though. System.exit is a nuclear
>      > Thread.interrupt, so replicating that behavior might be a bit
>     daunting
> 
>     What has Thread.interrupt got to do with System.exit ?
> 
>     David
> 
>      >      private static final class ExitCode {
>      >          volatile Integer code = null;
>      >      }
>      >
>      >      private final ScopeLocal<ExitCode> EXIT_CODE =
>     ScopeLocal.newInstance();
>      >
>      >      public void overridingExitBehavior(IntConsumer exit,
>     Runnable run) {
>      >          var exitCode = new ExitCode();
>      >          ScopeLocal.with(EXIT_CODE, exitCode).run(() -> {
>      >              // by whatever syntax
>      >              var _ = inContinuation(run);
>      >              if (exitCode.code != null) {
>      >                  exit.accept(code.exitCode)
>      >              }
>      >          });
>      >      }
>      >
>      >      public void exit(int status) {
>      >          if (EXIT_CODE.isBound()) {
>      >               EXIT_CODE.get().code = status;
>      >               Continuation.yield();
>      >          }
>      >          else {
>      >              Shutdown.exit(status);
>      >          }
>      >      }
>      >
>      >
>      >
>      > On Sun, Feb 27, 2022 at 10:41 AM Glavo <zjx001202 at gmail.com
>     <mailto:zjx001202 at gmail.com>> wrote:
>      >
>      >> I think there is a problem with this: `System.exit` contains
>     semantics to
>      >> interrupt the flow of control and exit, and if you implement it
>     that way,
>      >> you might have some program abnormally execute parts of it that
>     should
>      >> never be executed.
>      >>
>      >> Of course, using exceptions like this should solve part of the
>     problem:
>      >>
>      >> class Exit extends Error {
>      >>      final int exitCode;
>      >>      public Exit(int exitCode) {
>      >>          this.exitCode = exitCode;
>      >>      }
>      >>
>      >>      @Override
>      >>      public synchronized Throwable fillInStackTrace() {
>      >>          return this;
>      >>      }
>      >> }
>      >>
>      >> try {
>      >>    Runtime.getRuntime().overridingExitBehavior(exitCode ->
>     {throw new
>      >> Exit(exitCode);}, ...);
>      >> } catch (Exit exit) {
>      >>    ...
>      >> }
>      >>
>      >> However, the calling method may catch this exception
>     unexpectedly, and
>      >> there may be unexpected behavior under multithreading.
>      >> Of course, this part of the problem also exists for the security
>     manager.
>      >> But, if possible, it would be better to have a solution for these
>      >> situations.
>      >> (`Continuation` might help us?)
>      >>
>      >>
>      >>
>      >> On Sun, Feb 27, 2022 at 11:07 PM Ethan McCue <ethan at mccue.dev
>     <mailto:ethan at mccue.dev>> wrote:
>      >>
>      >>> That undermines my point some, but I think the overall shape of
>     the use
>      >>> case still makes sense
>      >>>
>      >>> On Sun, Feb 27, 2022 at 8:01 AM Remi Forax <forax at univ-mlv.fr
>     <mailto:forax at univ-mlv.fr>> wrote:
>      >>>
>      >>>> Hi Ethan,
>      >>>> there is a far simpler solution, call org.apache.ivy.run(args,
>     true)
>      >>>> instead of org.apache.ivy.main(args) in your tool provider.
>      >>>>
>      >>>> regards,
>      >>>> Rémi
>      >>>>
>      >>>> ----- Original Message -----
>      >>>>> From: "Ethan McCue" <ethan at mccue.dev <mailto:ethan at mccue.dev>>
>      >>>>> To: "core-libs-dev" <core-libs-dev at openjdk.java.net
>     <mailto:core-libs-dev at openjdk.java.net>>
>      >>>>> Sent: Saturday, February 26, 2022 11:14:19 PM
>      >>>>> Subject: Should System.exit be controlled by a Scope Local?
>      >>>>
>      >>>>> I have a feeling this has been considered and I might just be
>      >>>> articulating
>      >>>>> the obvious - but:
>      >>>>>
>      >>>>> As called out in JEP 411, one of the remaining legitimate
>     uses of the
>      >>>>> Security Manager is to intercept calls to System.exit. This seems
>      >>> like a
>      >>>>> decent use case for the Scope Local mechanism.
>      >>>>>
>      >>>>>
>      >>>>>     public class Runtime {
>      >>>>>         ...
>      >>>>>         private final ScopeLocal<IntConsumer> EXIT =
>      >>>>> ScopeLocal.newInstance();
>      >>>>>
>      >>>>>         ...
>      >>>>>
>      >>>>>         public void overridingExitBehavior(IntConsumer exit,
>     Runnable
>      >>>> run) {
>      >>>>>             ScopeLocal.with(EXIT, exit).run(run);
>      >>>>>         }
>      >>>>>
>      >>>>>         ...
>      >>>>>
>      >>>>>         public void exit(int status) {
>      >>>>>             if (EXIT.isBound()) {
>      >>>>>                 EXIT.get().accept(status);
>      >>>>>             }
>      >>>>>             else {
>      >>>>>                 Shutdown.exit(status);
>      >>>>>             }
>      >>>>>         }
>      >>>>>     }
>      >>>>>
>      >>>>>
>      >>>>> One of the likely minor benefits in the scope of things, but
>     related
>      >>> to
>      >>>> the
>      >>>>> parts of the ecosystem I am doodling with so I'll mention it,
>     is that
>      >>> it
>      >>>>> would become possible to wrap "naive" cli programs with the
>      >>> ToolProvider
>      >>>>> SPI without rewriting their code if this System.out, and
>     System.err
>      >>> all
>      >>>>> became reliably configurable.
>      >>>>>
>      >>>>> For instance, Apache Ivy's CLI has a main class that looks
>     like this
>      >>>>>
>      >>>>>
>      >>>>
>      >>>
>     https://github.com/apache/ant-ivy/blob/424fa89419147f50a41b4bdc665d8ea92b5da516/src/java/org/apache/ivy/Main.java
>     <https://github.com/apache/ant-ivy/blob/424fa89419147f50a41b4bdc665d8ea92b5da516/src/java/org/apache/ivy/Main.java>
>      >>>>>
>      >>>>>     package org.apache.ivy;
>      >>>>>
>      >>>>>     public final class Main {
>      >>>>>         ...
>      >>>>>
>      >>>>>         public static void main(String[] args) throws Exception {
>      >>>>>             try {
>      >>>>>                 run(args, true);
>      >>>>>                 System.exit(0);
>      >>>>>             } catch (ParseException ex) {
>      >>>>>                 System.err.println(ex.getMessage());
>      >>>>>                 System.exit(1);
>      >>>>>             }
>      >>>>>         }
>      >>>>>      }
>      >>>>>
>      >>>>> Making these otherwise static parts of the system
>     configurable would
>      >>>> enable
>      >>>>> a third party library to write
>      >>>>>
>      >>>>>     public final class IvyToolProvider implements ToolProvider {
>      >>>>>         @Override
>      >>>>>         public String name() {
>      >>>>>             return "ivy";
>      >>>>>         }
>      >>>>>
>      >>>>>         @Override
>      >>>>>         public int run(PrintWriter out, PrintWriter err,
>     String...
>      >>> args) {
>      >>>>>             var exit = new AtomicInteger(0);
>      >>>>>           
>       Runtime.getRuntime().overridingExitBehavior(exit::set, ()
>      >>> -> {
>      >>>>>                 System.overridingOut(out, () -> {
>      >>>>>                      System.overridingErr(err, Main::main);
>      >>>>>                 }
>      >>>>>             };
>      >>>>>             return exit.get();
>      >>>>>         }
>      >>>>>     }
>      >>>>>
>      >>>>> Whether that would be enough to make it so that people other than
>      >>>> Christian
>      >>>>> Stein use the mechanism is anyone's guess, but might be worth
>     a shot.
>      >>>>>
>      >>>>> https://grep.app/search?q=java.util.spi.ToolProvider
>     <https://grep.app/search?q=java.util.spi.ToolProvider>
>      >>>>
>      >>>
>      >>
> 


More information about the core-libs-dev mailing list