Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for applications which immediately exit #284

Closed
dmlloyd opened this issue Dec 13, 2018 · 41 comments
Closed

Add support for applications which immediately exit #284

dmlloyd opened this issue Dec 13, 2018 · 41 comments
Assignees
Labels
triage/out-of-date This issue/PR is no longer valid or relevant

Comments

@dmlloyd
Copy link
Member

dmlloyd commented Dec 13, 2018

Sometimes an application doesn't have any persistent services. Such applications should auto-exit after everything was "started".

A good way to implement this might be for main-method startup build steps to indicate symbolically if they are persistent. Only a handful will be:

  • Fixed thread pools like the scheduler
  • I/O event loop threads

Notably, regular thread pools should not be considered persistent.

If no persistent services are started, then the shutdownRequested flag should be immediately set, which will cause the main thread to exit cleanly (with no exit code). The shutdown hook will then run, but as the application was already stopped, it will immediately exit with no action taken.

@dmlloyd
Copy link
Member Author

dmlloyd commented Feb 2, 2019

@gsmet has pointed out that this would be a prerequisite for writing CLI apps.

@geoand
Copy link
Contributor

geoand commented Feb 4, 2019

Being able to easily create Quarkus CLIs might be a good way of keeping people for using Go for such tools

geoand added a commit to geoand/quarkus that referenced this issue Mar 7, 2019
geoand added a commit to geoand/quarkus that referenced this issue Mar 8, 2019
geoand added a commit to geoand/quarkus that referenced this issue Mar 9, 2019
geoand added a commit to geoand/quarkus that referenced this issue Mar 12, 2019
geoand added a commit to geoand/quarkus that referenced this issue Mar 13, 2019
@dmlloyd
Copy link
Member Author

dmlloyd commented May 10, 2019

Just as an update, I've been working on a design proposal for this which I hope to have ready sometime next week.

@dmlloyd
Copy link
Member Author

dmlloyd commented May 15, 2019

The design proposal is here and has been posted to the mailing list for review here.

@38leinaD
Copy link
Contributor

@dmlloyd Any update on the status of this? No pressure; just highly interested :-)

@dmlloyd
Copy link
Member Author

dmlloyd commented Aug 21, 2019

I've been on vacation for a few weeks and just got back. I intend to bring #2866 up to date with all the concerns addressed ASAP.

@misl
Copy link
Contributor

misl commented Sep 11, 2019

Any progress or timelines on this? I am highly interested as well.

Is there any example code somewhere of what this would look like?

@dmlloyd
Copy link
Member Author

dmlloyd commented Sep 11, 2019

No but I could spitball a few concepts to see what sticks.

The simplest to implement would be to have a user-provided implementation of the proposed ExecutionHandler interface, which would look something like this:

@ApplicationScoped // I assume we'll be able to do it this way...
public class MyMainClass implements ExecutionHandler {
    @Override
    public int run(ExecutionChain chain, ExecutionContext ctxt) throws Exception {
        List<String> args = ctxt.getArgumentsAsList();
        boolean ok;
        // do whatever with args, and decide if everything is OK
        // ultimately populate 'ok'
        return ok ? 0 : 1
    }
}

Another option would be to provide a "smart" annotation that could be applied to exactly one method on one application-scoped bean:

public class BeanClass {
    // ...
    @MainMethod
    public int myMain(List<String> args) {
        // we could allow int or void returns, args as array or list (or none), etc.
        doStuff();
        return 0; // ok
    }
}

Share other ideas if you have them!

@rpelisse
Copy link
Contributor

I prefer the second option, it seems more clear and coherent with an usual main class. Also, exposing xecutionChain and ExecutionContext is making it too complex. People with basic knowledge of Java may be confused, while there is nothing confusing with @mainmethod and the List args.

@remkop
Copy link

remkop commented Sep 13, 2019

Would there be any interest in cooperation with the picocli project? Picocli has:

  • GraalVM native image support (see the picocli-codegen annotation processor)
  • an intuitive API with annotations for options and positional parameters, invoked with one line of code; all an application needs to do is implement Runnable/Callable and put the business logic there (see example).
  • usage help with ANSI colors
  • TAB autocompletion
  • subcommands (to any level)
  • advanced parsing features like repeating composite argument groups, e.g. <cmd> ([-a=<a> -b=<b> -c=<c>] (-x | -y | -z))...
  • extensive documentation (https://picocli.info)
  • is actively maintained

@geoand
Copy link
Contributor

geoand commented Sep 13, 2019

Hey @remkop

I think that ideally things would designed in a why that could make integrating libraries like picocli and aesh via a dedicated Quarkus extension very easy

@remkop
Copy link

remkop commented Sep 13, 2019

@geoand That makes sense.

Command line applications may want to use Quarkus dependency injection to inject services into their commands and subcommands.

Picocli is designed to integrate with dependency injection containers.

What I am proposing is to add an extension to Quarkus that provides a picocli IFactory implementation to get objects from the Quarkus dependency injection context.

Plus of course some documentation for users of the framework that are interested in creating standalone applications with Quarkus.

Something very similar to what Micronaut has done.

@geoand
Copy link
Contributor

geoand commented Sep 13, 2019

What I am proposing is to add an extension to Quarkus that provides a picocli IFactory implementation to get objects from the Quarkus dependency injection context, and some documentation for users of the framework that are interested in creating standalone applications with Quarkus.

👍
That looks very similar to what I had prototyped with Aesh a few months ago :)

@remkop
Copy link

remkop commented Sep 13, 2019

Would there be interest in a pull request for such a picocli extension?

@geoand
Copy link
Contributor

geoand commented Sep 13, 2019

@remkop Thanks a lot for the offer to help!
But first I think we would need to settle on the basics of the design that will allow creating applications that immediately exit

@remkop
Copy link

remkop commented Sep 13, 2019

Interesting! Isn't it enough to simply have a way to start and stop the context? E.g.:

class MyCommand implements Runnable {

    @Inject
    SomeService service;

    @Option(names = "-x")
    int x;

    public void run() { } // business logic here

    private static int execute(String[] args) {
        try (Context context = new ContextBuilder().create()) {
            IFactory factory = new QuarkusFactory(context);
            return new picocli.CommandLine(MyCommand.class, factory).execute(args);
        }
    }

    public static void main(String[] args) {
        int exitCode = execute(args);
        System.exit(exitCode); // optionally call system.exit
    }
}

@geoand
Copy link
Contributor

geoand commented Sep 13, 2019

Not exactly :)

For one thing, in Quarkus the main method (and class) is generated since there are a lot of things that need to happen at startup.

@remkop
Copy link

remkop commented Sep 13, 2019

Interesting! Perhaps it is an idea to push that startup work into ContextBuilder.build()?

@geoand
Copy link
Contributor

geoand commented Sep 13, 2019

That could be one idea perhaps. @dmlloyd has done all the heavy lifting in this area so he is the best person to comment :)

@remkop
Copy link

remkop commented Sep 13, 2019

Understood.

@dmlloyd Does the above look feasible/interesting?

An alternative approach is for Quarkus to provide Spring Boot-like CommandRunner and ExitCodeGenerator interfaces. That would also work: picocli has a picocli-spring-boot-starter containing a PicocliSpringFactory to integrate with Spring that way. It is just a bit more ceremony for applications.

@dmlloyd
Copy link
Member Author

dmlloyd commented Sep 13, 2019

I don't think it's really feasible for us to do it this way, just because the state of the system is very fragile when main is entered, and we need certain controls over the app lifecycle (especially for dev mode and testing, but likely for other things as well in the future). Also we may need to guarantee (in certain cases) that the setup code for extensions is always run on the main thread of the process.

So, the idea would be to get a user-provided main method running as early as is possible, with useful things injected based on what it needs, so that probably means implementing an interface or calling out a bean method once the system is established. The branch in development does essentially this, but it just didn't have the last bit (the user interface) implemented completely yet. Hence the idea of having an annotated bean method that can become the "main" method if given.

That said, the branch needs a little TLC to (hopefully) satisfy the plaintiffs who are not happy with it at present; it's on my list.

@remkop
Copy link

remkop commented Sep 14, 2019

Thanks for the clarification.

Looking at your comment from a few days earlier where you mentioned some concepts, I like the simplicity of the @MainMethod annotation. Some questions:

  • Who will be responsible for calling System.exit? One idea is to let Quarkus exit the JVM if the @MainMethod-annotated method returns an int, and avoid calling System.exit if the @MainMethod-annotated method returns void, but that doesn’t cater for cases where the application dynamically decides whether System.exit should be called (perhaps based on the value of a command line argument like --no-exit). Or would it be okay for the application to call System.exit, and Quarkus does cleanup in a shutdown hook?

  • Can the bean context be injected in the bean? (Picocli will need it when processing the command line arguments.)

  • What happens when an application has multiple methods with this annotation? Spring allows multiple CommandRunners and multiple ExitCodeProviders, but supporting that raises the question of how applications can control the order in which they are run, etc. Maybe easier to support just one initially. (Would this be a compile-time or a runtime error?)

@dmlloyd
Copy link
Member Author

dmlloyd commented Sep 16, 2019

Thanks for the clarification.

Looking at your comment from a few days earlier where you mentioned some concepts, I like the simplicity of the @MainMethod annotation. Some questions:

  • Who will be responsible for calling System.exit? One idea is to let Quarkus exit the JVM if the @MainMethod-annotated method returns an int, and avoid calling System.exit if the @MainMethod-annotated method returns void, but that doesn’t cater for cases where the application dynamically decides whether System.exit should be called (perhaps based on the value of a command line argument like --no-exit).

This is basically exactly what the patch proposes. We generally don't want users calling System.exit because that can cause difficulties with unit testing (for example), and also prevents applications from returning an abnormal status if a problem happens during shutdown (which you may sometimes want in the case of a command line application).

  • Can the bean context be injected in the bean? (Picocli will need it when processing the command line arguments.)

Do you mean the bean manager? I think we have some limitations on what you can do there, but maybe a CDI expert like @mkouba should weigh in here...

  • What happens when an application has multiple methods with this annotation? Spring allows multiple CommandRunners and multiple ExitCodeProviders, but supporting that raises the question of how applications can control the order in which they are run, etc. Maybe easier to support just one initially. (Would this be a compile-time or a runtime error?)

We'd want it to give an error at compile time if more than one main method is present I think.

@remkop
Copy link

remkop commented Sep 16, 2019

Understood. What about expanding on that and also recognizing java.lang.Integer as a return value from the @MainMethod-annotated method (in addition to void or primitive int). This would allow applications to decide at runtime: when an application has a @MainMethod-annotated method that returns a java.lang.Integer, they can optionally return null to signal to Quarkus to not call System.exit. What do you think?

Understood about raising a compile error if more than one @MainMethod-annotated method exists. Makes sense.

Can the bean context be injected in the bean? (Picocli will need it when processing the command line arguments.)
Do you mean the bean manager?

Sorry if I'm not familiar with the terminology. What I mean is the Quarkus equivalent of the ApplicationContext in Spring. The reason we need it is: when picocli parses the command line arguments, it will instantiate subcommands when it recognizes a subcommand. Such subcommands may have Quarkus services injected, so picocli must not call Constructor.newInstance, but instead it should get the instance from the Quarkus bean manager. Then we can do something like this:

Update: I found the docs and it looks like applications will be able to do CDI.current().getBeanManager(), so the bean manager does not need to be injected.

This is what I imagine a Quarkus&picocli-powered CLI app with subcommands could look like:

@Command(name = "foo", subcommands = Bar.class)
class Foo implements Callable<Integer> {

    @Inject SomeService service;
    @Option(names = "-x") int x;

    public Integer call() { } // business logic here

    @MainMethod
    public int myMain(String[] args) {
        BeanManager beanManager = CDI.current().getBeanManager();
        picocli.CommandLine.IFactory factory = new QuarkusFactory(beanManager);
        int exitCode = new picocli.CommandLine(Foo.class, factory).execute(args);
        return exitCode;
    }
}

@Command(name = "bar", description = "This is a subcommand of `foo`")
class Bar implements Callable<Integer> {

    @Inject SomeOtherService service;
    @Option(names = "-y") int y;

    public Integer call() { } // business logic here
}

@dmlloyd
Copy link
Member Author

dmlloyd commented Sep 17, 2019

Understood. What about expanding on that and also recognizing java.lang.Integer as a return value from the @MainMethod-annotated method (in addition to void or primitive int). This would allow applications to decide at runtime: when an application has a @MainMethod-annotated method that returns a java.lang.Integer, they can optionally return null to signal to Quarkus to not call System.exit. What do you think?

I don't know that I'd use null for that, but I think I see what you mean: you want a way to let Quarkus stay up instead of exiting, sort of a "continue in the background" kind of thing?

@remkop
Copy link

remkop commented Sep 17, 2019

Yes. To be honest I don't have a concrete use case. Perhaps some applications, depending on the command line arguments, need to either exit immediately with some exit status, or start some long running service in the background and leave the JVM up...

@dmlloyd
Copy link
Member Author

dmlloyd commented Sep 17, 2019

The most natural integration I can think of would be to allow the main method to return some kind of Future or Single and make it reactive. The best thing about such an approach though: we can do it later 😁

@remkop
Copy link

remkop commented Sep 17, 2019

Fair enough. Supporting just void (don’t exit) and primitive int (exit with returned status code) should be sufficient initially.

@38leinaD
Copy link
Contributor

Excited to see that there already is discussion on how an integration with picocli could work.
Actually, i am awaiting the quarkus cli-support to move a micronautfw CLI application to a more standard platform. Micronaut has helped me to get some initial prototype up an running but before sharing the tool with my coworkers or extendeding on it, i want to see it moved to a standard like Java/JakartaEE.
I will keep monitoring this closely...

@danielpetisme
Copy link
Contributor

How can I help? Any stuff I could do to make it happen faster?
Can't wait to see picocli+Quarkus!!!

@remkop
Copy link

remkop commented Oct 30, 2019

@dmlloyd Now that Quarkus 0.27 was released, will you be able to spend time on this?

@dmlloyd
Copy link
Member Author

dmlloyd commented Oct 30, 2019

I'm almost finished with my current task, and this one will be next.

@dodalovic
Copy link
Contributor

@dmlloyd So, we can expect having picocli based solution? Thanks

@dmlloyd
Copy link
Member Author

dmlloyd commented Nov 4, 2019

Doubtful, but I would expect that the Quarkus solution would be able to support picocli based applications.

@rpelisse
Copy link
Contributor

rpelisse commented Nov 4, 2019

As long as NOT using Picocli is not based solely on the "not invented here syndrom", I'm fine with it ;) !

@maxandersen
Copy link
Member

@rpelisse this issue is about enabling CLI styled apps, wether piccoli or other cli fwks are used for quarkus cli is a different topic (one we'll start once cli style support is available :)

@metacosm
Copy link
Contributor

metacosm commented Nov 7, 2019

I'm interested in helping with this… been in the go "dark" side for a while and have to admit that creating CLIs with things like cobra is quite pleasant and fast compared to what I've seen in the Java world last time I looked.

@remkop
Copy link

remkop commented Nov 8, 2019

@metacosm Slightly off-topic, but if you have any tips on improving picocli based on your experience with cobra I am always interested!

@metacosm
Copy link
Contributor

metacosm commented Nov 8, 2019

@remkop sure, I'd need to take another look at picocli, first, though as it's been a while! 😄

@dmlloyd
Copy link
Member Author

dmlloyd commented Jan 10, 2020

Superceded by #6499.

@dmlloyd dmlloyd closed this as completed Jan 10, 2020
@gsmet gsmet added the triage/out-of-date This issue/PR is no longer valid or relevant label Jan 20, 2020
@remkop
Copy link

remkop commented Mar 29, 2020

Superceded by #7681

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage/out-of-date This issue/PR is no longer valid or relevant
Projects
None yet
Development

No branches or pull requests