Search code examples
javamicronautpicocli

How to start properly a micronaut webserver from picocli command?


I've a micronaut cli application.

Some command are long running process that I want to monitor with micronaut endpoints

Since when you are on a cli app, micronaut detect and don't launch the webserver.

In this long running command, I've added this to start the webserver :


        applicationContext
            .findBean(EmbeddedServer.class)
            .ifPresent(server -> {
                // start server
                long start = System.currentTimeMillis();
                server.start();

                log.info(
                    "Startup completed in {} ms. Server Running: {}",
                    System.currentTimeMillis() - start,
                    server.getURL()
                );

                // force exit
                if (server.isForceExit()) {
                    System.exit(0);
                }
            });

Most of the code come from Micronaut.java.

The full command source code:

package test;

import io.micronaut.configuration.picocli.PicocliRunner;
import io.micronaut.context.ApplicationContext;
import io.micronaut.runtime.server.EmbeddedServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;

import javax.inject.Inject;
import java.util.concurrent.Callable;

@CommandLine.Command(
    name = "test",
    mixinStandardHelpOptions = true,
    subcommands = {
        TestApp.TestServer.class,
    }
)
public class TestApp implements Callable<Object> {
    public static void main(String[] args) throws Exception {
        PicocliRunner.call(TestApp.class, args);
    }

    @Override
    public Object call() throws Exception {
        return PicocliRunner.call(TestApp.class, "--help");
    }

    @CommandLine.Command(
        name = "server",
        description = "Start a webserver"
    )
    public static class TestServer implements Runnable {
        @Inject
        ApplicationContext applicationContext;

        Logger log = LoggerFactory.getLogger(TestServer.class);

        @Override
        public void run() {
            applicationContext
                .findBean(EmbeddedServer.class)
                .ifPresent(server -> {
                    // start server
                    long start = System.currentTimeMillis();
                    server.start();

                    log.info(
                        "Startup completed in {} ms. Server Running: {}",
                        System.currentTimeMillis() - start,
                        server.getURL()
                    );

                    // force exit
                    if (server.isForceExit()) {
                        System.exit(0);
                    }
                });
        }
    }
}

The webserver is starting well. I've added 2 endpoints, logger and health. Logger endpoint works well, but the health endpoint (like most every endpoint) failed with with this stacktrace : Full Stacktrace

io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1f5e667f[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@1615aa7e[Wrapped task = io.micronaut.http.context.ServerRequestContext$$Lambda$653/0x00000008406af040@5f7931cf]] rejected from java.util.concurrent.ThreadPoolExecutor@33dd1064[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:367)
...
Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1f5e667f[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@1615aa7e[Wrapped task = io.micronaut.http.context.ServerRequestContext$$Lambda$653/0x00000008406af040@5f7931cf]] rejected from java.util.concurrent.ThreadPoolExecutor@33dd1064[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
....
    at io.micronaut.scheduling.instrument.InstrumentedExecutorService.submit(InstrumentedExecutorService.java:89)
    at io.micronaut.core.async.publisher.AsyncSingleResultPublisher$ExecutorServiceSubscription.request(AsyncSingleResultPublisher.java:98)
    at io.reactivex.internal.operators.flowable.FlowableFlatMap$InnerSubscriber.onSubscribe(FlowableFlatMap.java:656)
    at io.micronaut.core.async.publisher.AsyncSingleResultPublisher.subscribe(AsyncSingleResultPublisher.java:59)
...
    at io.reactivex.Flowable.subscribe(Flowable.java:14805)
    ... 38 more
Exception in thread "nioEventLoopGroup-1-4" io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1f5e667f[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@1615aa7e[Wrapped task = io.micronaut.http.context.ServerRequestContext$$Lambda$653/0x00000008406af040@5f7931cf]] rejected from java.util.concurrent.ThreadPoolExecutor@33dd1064[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
...
    at io.micronaut.http.context.ServerRequestTracingPublisher.lambda$subscribe$0(ServerRequestTracingPublisher.java:52)
    at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
    at io.micronaut.http.context.ServerRequestTracingPublisher.subscribe(ServerRequestTracingPublisher.java:52)
    at io.micronaut.core.async.publisher.Publishers.lambda$map$2(Publishers.java:133)
...
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1f5e667f[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@1615aa7e[Wrapped task = io.micronaut.http.context.ServerRequestContext$$Lambda$653/0x00000008406af040@5f7931cf]] rejected from java.util.concurrent.ThreadPoolExecutor@33dd1064[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    ... 38 more
2019-10-11 17:18:50,689 WARN  nioEventLoopGroup-1-4    i.n.u.c.AbstractEventExecutor        A task raised an exception. Task: io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker@7ebe5c10
java.lang.NullPointerException: Actually not, but can't throw other exceptions due to RS
    at io.reactivex.Flowable.subscribe(Flowable.java:14814)
...
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1f5e667f[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@1615aa7e[Wrapped task = io.micronaut.http.context.ServerRequestContext$$Lambda$653/0x00000008406af040@5f7931cf]] rejected from java.util.concurrent.ThreadPoolExecutor@33dd1064[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
    at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825)
    at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1355)
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:118)
    at io.micronaut.scheduling.instrument.InstrumentedExecutorService.submit(InstrumentedExecutorService.java:89)
    at io.micronaut.core.async.publisher.AsyncSingleResultPublisher$ExecutorServiceSubscription.request(AsyncSingleResultPublisher.java:98)
    at io.reactivex.internal.operators.flowable.FlowableFlatMap$InnerSubscriber.onSubscribe(FlowableFlatMap.java:656)
    at io.micronaut.core.async.publisher.AsyncSingleResultPublisher.subscribe(AsyncSingleResultPublisher.java:59)
    at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.onNext(FlowableFlatMap.java:163)
    at io.reactivex.internal.operators.flowable.FlowableFromIterable$IteratorSubscription.slowPath(FlowableFromIterable.java:236)
    at io.reactivex.internal.operators.flowable.FlowableFromIterable$BaseRangeSubscription.request(FlowableFromIterable.java:124)
    at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.onSubscribe(FlowableFlatMap.java:117)
    at io.reactivex.internal.operators.flowable.FlowableFromIterable.subscribe(FlowableFromIterable.java:69)
    at io.reactivex.internal.operators.flowable.FlowableFromIterable.subscribeActual(FlowableFromIterable.java:47)
    at io.reactivex.Flowable.subscribe(Flowable.java:14805)
    ... 38 common frames omitted

Any clue to do this ?


Solution

  • Thanks to Graeme Rocher that give the right direction here : https://github.com/micronaut-projects/micronaut-picocli/issues/9.

    Starting the webserver from a cli is the right way:

    applicationContext
                .findBean(EmbeddedServer.class)
                .ifPresent(server -> {
                    // start server
                    long start = System.currentTimeMillis();
                    server.start();
                 });
    
    

    But you need to take care that your Command must keep the whole logic on the main thread. In case of multithreaded application, the server will be shutdown if you are not waiting for all the thread in main command