Should System.exit be controlled by a Scope Local?

Ethan McCue ethan at mccue.dev
Mon Feb 28 00:37:59 UTC 2022


K.

On Sun, Feb 27, 2022, 7:03 PM David Holmes <david.holmes at oracle.com> wrote:

> 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