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

quarkus scheduler does not await termination of scheduledExecutor #16833

Closed
vsevel opened this issue Apr 27, 2021 · 28 comments · Fixed by #38478
Closed

quarkus scheduler does not await termination of scheduledExecutor #16833

vsevel opened this issue Apr 27, 2021 · 28 comments · Fixed by #38478
Labels
area/scheduler kind/bug Something isn't working
Milestone

Comments

@vsevel
Copy link
Contributor

vsevel commented Apr 27, 2021

Describe the bug

I execute the following code inspired from https://quarkus.io/guides/scheduler:

@ApplicationScoped
public class MyTimer {

    public static Logger log = Logger.getLogger(MyTimer.class);

    private final String start = new Date().toString();

    private final AtomicInteger count = new AtomicInteger();

    @Scheduled(every = "10S")
    public void run() {
        for (int i = 0; i < 10; i++) {
            log.info("execute " + start + " => " + count.incrementAndGet());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log.error("timer interrupted", e);
                break;
            }
        }
    }
}

When hot code replace happens in dev mode, the scheduler extension does not wait for the scheduledExecutor to terminate.
as a result, the old timer continues executing the tasks that are in progress in parallel with the new tasks scheduler by the new timer.
Eventually, the old code finishes up, and only the new timer remains.

For instance, here is how it looks like with the previous example:

...
2021-04-27 14:29:32,002 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:17 CEST 2021 => 16
2021-04-27 14:29:33,003 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:17 CEST 2021 => 17
2021-04-27 14:29:34,004 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:17 CEST 2021 => 18
2021-04-27 14:29:35,004 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:17 CEST 2021 => 19
2021-04-27 14:29:36,005 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:17 CEST 2021 => 20
2021-04-27 14:29:37,000 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 21
2021-04-27 14:29:38,000 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 22
2021-04-27 14:29:39,001 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 23
2021-04-27 14:29:40,001 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 24
2021-04-27 14:29:41,002 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 25
2021-04-27 14:29:42,002 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 26
2021-04-27 14:29:42,509 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-10) File change detected: E:\tmp\quarkus\test_timer\getting-started\src\main\resources\application.properties
2021-04-27 14:29:42,540 INFO  [io.quarkus] (Quarkus Main Thread) getting-started stopped in 0.027s
2021-04-27 14:29:43,003 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 27
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-04-27 14:29:43,859 INFO  [io.quarkus] (Quarkus Main Thread) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.2.Final) started in 1.315s. Listening on: http://localhost:8081
2021-04-27 14:29:43,860 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-04-27 14:29:43,860 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, scheduler]
2021-04-27 14:29:43,865 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-10) Hot replace total time: 1.366s
2021-04-27 14:29:44,002 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:44 CEST 2021 => 1
2021-04-27 14:29:44,004 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 28
2021-04-27 14:29:45,010 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:44 CEST 2021 => 2
2021-04-27 14:29:45,011 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 29
2021-04-27 14:29:46,010 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:44 CEST 2021 => 3
2021-04-27 14:29:46,011 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 14:29:17 CEST 2021 => 30
2021-04-27 14:29:47,011 INFO  [org.acm.get.sta.MyTimer] (executor-thread-1) execute Tue Apr 27 14:29:44 CEST 2021 => 4
2021-04-27 14:29:47,012 ERROR [io.qua.sch.run.SimpleScheduler] (executor-thread-198) Error occured while executing task for trigger IntervalTrigger [id=1_org.acme.getting.started.MyTimer_ScheduledInvoker_run_72e66771a77415a7284d3ae42331659c186071de, interval=10000]:
 javax.enterprise.context.ContextNotActiveException
        at io.quarkus.arc.impl.ClientProxies.getDelegate(ClientProxies.java:40)
        at io.quarkus.arc.runtime.devconsole.InvocationTree_ClientProxy.arc$delegate(InvocationTree_ClientProxy.zig:42)
        at io.quarkus.arc.runtime.devconsole.InvocationTree_ClientProxy.invocationCompleted(InvocationTree_ClientProxy.zig:128)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:74)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
        at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
        at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
        at org.acme.getting.started.MyTimer_Subclass.run(MyTimer_Subclass.zig:147)
        at org.acme.getting.started.MyTimer_ClientProxy.run(MyTimer_ClientProxy.zig:126)
        at org.acme.getting.started.MyTimer_ScheduledInvoker_run_72e66771a77415a7284d3ae42331659c186071de.invokeBean(MyTimer_ScheduledInvoker_run_72e66771a77415a7284d3ae42331659c186071de.zig:46)
        at io.quarkus.arc.runtime.BeanInvoker.invoke(BeanInvoker.java:20)
        at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask$1.run(SimpleScheduler.java:204)
        at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:231)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at java.base/java.lang.Thread.run(Thread.java:834)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)

Note that this behavior is less visible, but happens also in production mode: in Graceful shutdown, we should also wait for the termination of tasks that are in progress.

I think here is the relevant code.

Expected behavior

Stopping the timer, awaiting termination, then restart the application.

Actual behavior

Old timers keep running until they finish up normally.
If we do not await termination, another option would be to interrupt the threads.

To Reproduce

Link to a small reproducer (preferably a Maven project if the issue is not Gradle-specific).
Execute the above code in a scheduler application

Configuration

# Add your application.properties here, if applicable.

Screenshots

(If applicable, add screenshots to help explain your problem.)

Environment (please complete the following information):

Output of uname -a or ver

Output of java -version

GraalVM version (if different from Java)

Quarkus version or git rev

Quarkus 1.13.2.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Additional context

(Add any other context about the problem here.)

@vsevel vsevel added the kind/bug Something isn't working label Apr 27, 2021
@quarkus-bot
Copy link

quarkus-bot bot commented Apr 27, 2021

/cc @mkouba

@mkouba
Copy link
Contributor

mkouba commented Apr 27, 2021

Stopping the timer, awaiting termination, then restart the application.

So first of all I don't think this is an acceptable solution - a scheduled task may take a long time to complete and the whole point of the hot replacement is to redeploy the app immediately.

You're right that we don't wait for actively executing tasks. In fact, the scheduled tasks are executed on the default blocking Quarkus ExecutorService and not the ScheduledExecutorService which is only used to check the triggers and execute the tasks when needed.

My understanding of the default settings for the blocking executor, i.e. quarkus.thread-pool.shutdown-interrupt and friends, is that the threads should be interrupted after 10 seconds. But @dmlloyd will know more...

@dmlloyd
Copy link
Member

dmlloyd commented Apr 27, 2021

I believe you are correct. It might be interesting/useful to set it to interrupt immediately in dev mode though?

@vsevel
Copy link
Contributor Author

vsevel commented Apr 27, 2021

a scheduled task may take a long time to complete and the whole point of the hot replacement is to redeploy the app immediately.

I agree. and at the same time it is very confusing.

My understanding of the default settings for the blocking executor, i.e. quarkus.thread-pool.shutdown-interrupt and friends, is that the threads should be interrupted after 10 seconds.

strange. with that code:

    private final String start = new Date().toString();

    private final AtomicInteger run = new AtomicInteger();

    @Scheduled(every = "50S")
    public void run() {
        int runId = run.incrementAndGet();
        for (int i = 0; i < 40; i++) {
            log.info("execute " + start + " => " + runId +" iter " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log.error("timer interrupted", e);
                break;
            }
        }
    }

and interrupting just after starting run 3, I see:

2021-04-27 19:51:37,004 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 19:49:57 CEST 2021 => 3 iter 0
2021-04-27 19:51:38,005 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 19:49:57 CEST 2021 => 3 iter 1
2021-04-27 19:51:39,005 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 19:49:57 CEST 2021 => 3 iter 2
2021-04-27 19:51:39,462 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-4) Changed source files detected, recompiling [E:\tmp\quarkus\test_timer\getting-started\src\main\java\org\acme\getting\started\MyTimer.java]
2021-04-27 19:51:39,612 INFO  [io.quarkus] (Quarkus Main Thread) getting-started stopped in 0.020s
2021-04-27 19:51:40,006 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 19:49:57 CEST 2021 => 3 iter 3
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2021-04-27 19:51:40,538 INFO  [io.quarkus] (Quarkus Main Thread) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.2.Final) started in 0.922s. Listening on: http://localhost:8081
2021-04-27 19:51:40,539 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-04-27 19:51:40,541 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, scheduler]
2021-04-27 19:51:40,542 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-4) Hot replace total time: 1.083s 
2021-04-27 19:51:41,003 INFO  [org.acm.get.sta.MyTimer] (executor-thread-201) execute Tue Apr 27 19:51:41 CEST 2021 => 1 iter 0
2021-04-27 19:51:41,021 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 19:49:57 CEST 2021 => 3 iter 4
...
2021-04-27 19:52:16,040 INFO  [org.acm.get.sta.MyTimer] (executor-thread-198) execute Tue Apr 27 19:49:57 CEST 2021 => 3 iter 39
2021-04-27 19:52:17,026 INFO  [org.acm.get.sta.MyTimer] (executor-thread-201) execute Tue Apr 27 19:51:41 CEST 2021 => 1 iter 36
2021-04-27 19:52:17,041 ERROR [io.qua.sch.run.SimpleScheduler] (executor-thread-198) Error occured while executing task for trigger IntervalTrigger [id=1_org.acme.getting.started.MyTimer_ScheduledInvoker_run_72e66771a77415a7284d3ae42331659c186071de, interval=50000]:
 javax.enterprise.context.ContextNotActiveException
        at io.quarkus.arc.impl.ClientProxies.getDelegate(ClientProxies.java:40)
        at io.quarkus.arc.runtime.devconsole.InvocationTree_ClientProxy.arc$delegate(InvocationTree_ClientProxy.zig:42)
        at io.quarkus.arc.runtime.devconsole.InvocationTree_ClientProxy.invocationCompleted(InvocationTree_ClientProxy.zig:128)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:74)

I don't see executor-thread-198 being interrupted at all.

it might be interesting/useful to set it to interrupt immediately in dev mode though?

yes that would be a pragmatic approach.
the only drawback is that you rely on the app code, and any library it is using to correctly handle the interrupted exception (and not swallow it).
in our jboss apps we had developed a method to fetch the status of the server, and timer tasks were required to check it whenever they would be doing some processing in a loop, so that we would not rely solely on the interrupted exception mechanism. do we have something like this in quarkus? or may it is too far off, and interrupting immediately is the right solution.

now it does not solve the graceful issue. and may be it should be treated in a different PR. but basically you would want the to await termination up to quarkus.shutdown.timeout seconds ideally before interrupting the timer tasks.

@mkouba
Copy link
Contributor

mkouba commented Apr 28, 2021

I don't see executor-thread-198 being interrupted at all.

Indeed, the thread is not interrupted at all. I'll need to debug the io.quarkus.runtime.ExecutorRecorder.createShutdownTask(ThreadPoolConfig, EnhancedQueueExecutor).

now it does not solve the graceful issue.

I've created #16875

@mkouba mkouba self-assigned this Apr 28, 2021
@mkouba
Copy link
Contributor

mkouba commented Apr 29, 2021

Ah, that shutdown task is not used in the dev mode at all. It seems that in the dev mode we do not shutdown the main executor for blocking tasks. @stuartwdouglas do you happen to know if there's still a need for this: https://github.com/quarkusio/quarkus/blob/main/core/runtime/src/main/java/io/quarkus/runtime/ExecutorRecorder.java#L29-L33 ?

@stuartwdouglas
Copy link
Member

Probably not, lets see what CI says: #16920

@mkouba
Copy link
Contributor

mkouba commented May 4, 2021

So with Stuart's PR and my fix the current behavior should be as follows:

  • the default threadpool config is applied in the dev mode as well
  • the thread pool starts to interrupt tasks after 10 seconds (configurable via quarkus.thread-pool.shutdown-interrupt)
  • the shutdown timeout can be specified via quarkus.thread-pool.shutdown-timeout; the default value is 1 minute

@vsevel Does it sound like an acceptable solution?

@vsevel
Copy link
Contributor Author

vsevel commented May 4, 2021

the thread pool starts to interrupt tasks after 10 seconds (configurable via quarkus.thread-pool.shutdown-interrupt)

so I suppose the compromise here is that we do not do an interrupt immediately on all managed threads?
but if the users wanted that behavior, they could always lower the shutdown-interrupt?
if so, I agree. is the quarkus.thread-pool.shutdown-interrupt used in other situations that the dev mode reboot?

the shutdown timeout can be specified via quarkus.thread-pool.shutdown-timeout; the default value is 1 minute

here, the compromise is that we do not honor quarkus.shutdown.timeout but quarkus.thread-pool.shutdown-timeout instead?
but it will essentially carry the same graceful shutdown semantic? that is: we wait up to x seconds for the tasks to finish normally, and after that we force them to terminate?
if so, I think it is acceptable. it would deserve some additional documentation stating that graceful shutdown is supported for the scheduler extension (and may be others that rely on the thread pool?) through the quarkus.thread-pool.shutdown-timeout property.
would that be hard (or even make sense) to honor the quarkus.shutdown.timeout as well when shutting down the pool? for instance by taking up to max(quarkus.shutdown.timeout, quarkus.thread-pool.shutdown-timeout)seconds before shutting it down? that would be easier (and more consistent) to explain and configure graceful shutdown.

@mkouba
Copy link
Contributor

mkouba commented May 4, 2021

if so, I agree. is the quarkus.thread-pool.shutdown-interrupt used in other situations that the dev mode reboot?

Yes, and it shouldn't be.

but it will essentially carry the same graceful shutdown semantic? that is: we wait up to x seconds for the tasks to finish normally, and after that we force them to terminate?

I'm not sure the semantics is exactly the same but in general yes, after the timeout quarkus does java.util.concurrent.ExecutorService.shutdownNow().

if so, I think it is acceptable. it would deserve some additional documentation

+1. Would you care to contribute something?

would that be hard (or even make sense) to honor the quarkus.shutdown.timeout as well when shutting down the pool?

I'm not sure TBH. max(quarkus.shutdown.timeout, quarkus.thread-pool.shutdown-timeout) might make sense..

@vsevel
Copy link
Contributor Author

vsevel commented May 4, 2021

+1. Would you care to contribute something?

sure.

I'm not sure TBH. max(quarkus.shutdown.timeout, quarkus.thread-pool.shutdown-timeout) might make sense..

it is either one of these 3 options:

  • quarkus.thread-pool.shutdown-timeout only (we disregard quarkus.shutdown.timeout whether it is defined or not). people wanting to emulate graceful shutdown for the scheduler extension should align quarkus.thread-pool.shutdown-timeout and quarkus.shutdown.timeout; or
  • you take quarkus.thread-pool.shutdown-timeout, unless a quarkus.shutdown.timeout has been defined - then you would assume that the user wants quarkus.shutdown.timeout across all functionalities (that is his one timeout when it comes to shutdown), even if quarkus.thread-pool.shutdown-timeout > quarkus.shutdown.timeout, or
  • max of both values (when quarkus.shutdown.timeout is defined), because if quarkus.shutdown.timeout < quarkus.thread-pool.shutdown-timeout, may be we still want to wait up to quarkus.thread-pool.shutdown-timeout. but I tend to agree with you on that one this one.

my preference would be to go for option 2.

if we go for option 1, then this will complicate the documentation and the user experience:

if you want graceful shutdown and you have a scheduler and http endpoints, then you should probably align quarkus.thread-pool.shutdown-timeout for the former and quarkus.shutdown.timeout for the later.

it looks like you are leaking an implementation detail. let me know what you decide, and I will make a proposal for the doc.

@mkouba
Copy link
Contributor

mkouba commented May 5, 2021

I prefer option 2, i.e. quarkus.shutdown.timeout takes precedence if specified...

Also note that we should not only talk about scheduler - the default thread pool is used for many other services (e.g. async CDI events).

@machi1990 @stuartwdouglas WDYT?

@machi1990
Copy link
Member

+1. Would you care to contribute something?

sure.

I'm not sure TBH. max(quarkus.shutdown.timeout, quarkus.thread-pool.shutdown-timeout) might make sense..

it is either one of these 3 options:

  • quarkus.thread-pool.shutdown-timeout only (we disregard quarkus.shutdown.timeout whether it is defined or not). people wanting to emulate graceful shutdown for the scheduler extension should align quarkus.thread-pool.shutdown-timeout and quarkus.shutdown.timeout; or
  • you take quarkus.thread-pool.shutdown-timeout, unless a quarkus.shutdown.timeout has been defined - then you would assume that the user wants quarkus.shutdown.timeout across all functionalities (that is his one timeout when it comes to shutdown), even if quarkus.thread-pool.shutdown-timeout > quarkus.shutdown.timeout, or
  • max of both values (when quarkus.shutdown.timeout is defined), because if quarkus.shutdown.timeout < quarkus.thread-pool.shutdown-timeout, may be we still want to wait up to quarkus.thread-pool.shutdown-timeout. but I tend to agree with you on that one this one.

my preference would be to go for option 2.

if we go for option 1, then this will complicate the documentation and the user experience:

if you want graceful shutdown and you have a scheduler and http endpoints, then you should probably align quarkus.thread-pool.shutdown-timeout for the former and quarkus.shutdown.timeout for the later.

it looks like you are leaking an implementation detail. let me know what you decide, and I will make a proposal for the doc.

+1 on options (2) which sounds reasonable to me and easy to document.

@vsevel
Copy link
Contributor Author

vsevel commented May 5, 2021

Also note that we should not only talk about scheduler - the default thread pool is used for many other services (e.g. async CDI events).

yes definitely. hopefully all use cases are known, and can adhere to quarkus.shutdown.timeout taking precedence if defined. if not, may be there should be 2 pools. 1 that honors graceful shutdown configuration, and one that does not (for good reasons).

@mkouba mkouba removed their assignment May 24, 2021
@mkouba
Copy link
Contributor

mkouba commented Dec 14, 2023

@vsevel Is this still a problem? I do not see any PR as a result of the discussion so I wonder if this issue is either outdated or just forgotten ;-).

@mkouba
Copy link
Contributor

mkouba commented Jan 30, 2024

I'm going to close this issue as it seems to be out of date. Feel free to reopen if it's still a problem.

@mkouba mkouba closed this as not planned Won't fix, can't repro, duplicate, stale Jan 30, 2024
@vsevel
Copy link
Contributor Author

vsevel commented Jan 30, 2024

hello @mkouba sorry for the delay to answer.
the issue is still there with 3.6.8

2024-01-30 12:13:24,193 INFO  [org.acm.MyTimer] (vert.x-worker-thread-9) execute Tue Jan 30 12:11:14 CET 2024 => 132
2024-01-30 12:13:25,006 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Tue Jan 30 12:11:14 CET 2024 => 133
2024-01-30 12:13:26,008 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Tue Jan 30 12:11:14 CET 2024 => 134
2024-01-30 12:13:27,010 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Tue Jan 30 12:11:14 CET 2024 => 135
2024-01-30 12:13:27,616 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (Aesh InputStream Reader) Restarting as requested by the user.
2024-01-30 12:13:27,647 INFO  [io.quarkus] (Quarkus Main Thread) scheduler stopped in 0.030s
2024-01-30 12:13:28,012 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Tue Jan 30 12:11:14 CET 2024 => 136
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2024-01-30 12:13:28,970 INFO  [io.quarkus] (Quarkus Main Thread) scheduler 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.6.8) started in 1.314s. Listening on: http://localhost:8092

2024-01-30 12:13:28,971 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2024-01-30 12:13:28,979 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy-reactive, scheduler, smallrye-context-propagation, vertx]
2024-01-30 12:13:28,980 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (Aesh InputStream Reader) Live reload total time: 1.375s
2024-01-30 12:13:29,004 INFO  [org.acm.MyTimer] (vert.x-worker-thread-9) execute Tue Jan 30 12:13:29 CET 2024 => 1
2024-01-30 12:13:29,019 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Tue Jan 30 12:11:14 CET 2024 => 137
2024-01-30 12:13:30,016 INFO  [org.acm.MyTimer] (vert.x-worker-thread-9) execute Tue Jan 30 12:13:29 CET 2024 => 2
2024-01-30 12:13:30,032 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Tue Jan 30 12:11:14 CET 2024 => 138
2024-01-30 12:13:31,020 INFO  [org.acm.MyTimer] (vert.x-worker-thread-9) execute Tue Jan 30 12:13:29 CET 2024 => 3
2024-01-30 12:13:31,049 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Tue Jan 30 12:11:14 CET 2024 => 139
2024-01-30 12:13:32,022 INFO  [org.acm.MyTimer] (vert.x-worker-thread-9) execute Tue Jan 30 12:13:29 CET 2024 => 4
2024-01-30 12:13:32,054 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Tue Jan 30 12:11:14 CET 2024 => 140

for the graceful shutdown use case, I do not think this is the expected behavior as well.

When CTLR+C, I see:

2024-01-30 12:16:38,006 INFO  [org.acm.MyTimer] (executor-thread-1) execute Tue Jan 30 12:16:18 CET 2024 => 21
2024-01-30 12:16:39,012 INFO  [org.acm.MyTimer] (executor-thread-1) execute Tue Jan 30 12:16:18 CET 2024 => 22
2024-01-30 12:16:39,092 ERROR [org.acm.MyTimer] (executor-thread-1) timer interrupted [Error Occurred After Shutdown]: java.lang.InterruptedException: sleep interrupted
        at java.base/java.lang.Thread.sleep(Native Method)
        at org.acme.MyTimer.run(MyTimer.java:25)
        at org.acme.MyTimer_ClientProxy.run(Unknown Source)
        at org.acme.MyTimer_ScheduledInvoker_run_72e66771a77415a7284d3ae42331659c186071de.invokeBean(Unknown Source)
        at io.quarkus.scheduler.common.runtime.DefaultInvoker.invoke(DefaultInvoker.java:24)
        at io.quarkus.scheduler.common.runtime.StatusEmitterInvoker.invoke(StatusEmitterInvoker.java:35)
        at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask.doInvoke(SimpleScheduler.java:430)
        at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask$2.call(SimpleScheduler.java:412)
        at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask$2.call(SimpleScheduler.java:409)
        at io.vertx.core.impl.ContextBase.lambda$executeBlocking$0(ContextBase.java:167)
        at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:277)
        at io.vertx.core.impl.ContextBase.lambda$internalExecuteBlocking$2(ContextBase.java:199)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:587)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:833)

2024-01-30 12:16:39,111 INFO  [io.quarkus] (main) scheduler stopped in 0.058s

I would expect the ongoing activity to finish gracefully, but not restart new ones, as opposed to immediately interrupt threads.

@mkouba
Copy link
Contributor

mkouba commented Jan 30, 2024

I took a look again and it's quite convoluted.

Dev mode

Blocking scheduled methods are usually executed on a Vert.x working thread (unless @RunOnVirtualThread is used). In the prod mode a Vert.x working thread is backed by the main executor for blocking tasks. However, in the dev mode it's different - a separate ExecutorService is configured and attached to the the Vertx instance; and, what is more important, the Vertx instance is reused across restarts. And so is this ExecutorService instance. In other words, the ExecutorService used for the scheduled methods is not shut down when the app is restarted in the dev mode.

I don't think there's an easy way to stop/interrupt an action submitted with Vertx.executeBlocking(). @cescoffier Any idea?

Prod mode

And in the prod mode it's again connected to the fact that when the Vertx instance is closed the worker thread pool is closed as well and it calls shutdownNow() on the underlying ExecutorService before the logic from the ExecutorRecorder.createShutdownTask() is used. And so the quarkus.thread-pool.shutdown-interrupt config property is ignored in the prod mode if the quarkus-vertx extension is present.

In theory, we could wrap the ExecutorService passed to the Vertx instance and make the shutdown()/shutdownNow() methods no-ops and let the ExecutorRecorder.createShutdownTask() handle the shutdown correctly... I can try this locally and let's see if it breaks anything..

NOTE: I know that it would be a very ugly workaround but I see no better solution ATM 🤷

@mkouba mkouba reopened this Jan 30, 2024
mkouba added a commit to mkouba/quarkus that referenced this issue Jan 30, 2024
- in the prod mode we wrap the ExecutorService and the shutdown() and
shutdownNow() are deliberately not delegated
- this is a workaround to solve the problem described in
quarkusio#16833 (comment)
- the Vertx instance is closed before
io.quarkus.runtime.ExecutorRecorder.createShutdownTask() is used
- and when it's closed the underlying worker thread pool (which is in
the prod mode backed by the ExecutorBuildItem) is closed as well
- as a result the quarkus.thread-pool.shutdown-interrupt config property
and logic defined in ExecutorRecorder.createShutdownTask() is completely
ignored
@mkouba
Copy link
Contributor

mkouba commented Jan 30, 2024

In theory, we could wrap the ExecutorService passed to the Vertx instance and make the shutdown()/shutdownNow() methods no-ops and let the ExecutorRecorder.createShutdownTask() handle the shutdown correctly... I can try this locally and let's see if it breaks anything..

NOTE: I know that it would be a very ugly workaround but I see no better solution ATM 🤷

Here's the draft PR: #38478. It seems to work. But of course it's not ideal.

@mkouba
Copy link
Contributor

mkouba commented Jan 30, 2024

In theory, we could wrap the ExecutorService passed to the Vertx instance and make the shutdown()/shutdownNow() methods no-ops and let the ExecutorRecorder.createShutdownTask() handle the shutdown correctly... I can try this locally and let's see if it breaks anything..
NOTE: I know that it would be a very ugly workaround but I see no better solution ATM 🤷

Here's the draft PR: #38478. It seems to work. But of course it's not ideal.

I think that we can do a similar trick for the dev mode - this time pass an "exchangeable" ExecutorService that would allow use to replace the old ExecutorService and shutdown the old one at the same time.

@vsevel
Copy link
Contributor Author

vsevel commented Jan 30, 2024

functionally, the most important part to me is a good support for graceful shutdown in prod mode.
the dev mode still kicking after the application has stopped is not visually pretty, but it goes away after some time. that would not be something that prevents people from developing.

@cescoffier
Copy link
Member

I don't think there's an easy way to stop/interrupt an action submitted with Vertx.executeBlocking(). @cescoffier Any idea?

No, you cannot clear the execution queue.

mkouba added a commit to mkouba/quarkus that referenced this issue Jan 31, 2024
- in the prod mode we wrap the ExecutorService and the shutdown() and
shutdownNow() are deliberately not delegated
- this is a workaround to solve the problem described in
quarkusio#16833 (comment)
- the Vertx instance is closed before
io.quarkus.runtime.ExecutorRecorder.createShutdownTask() is used
- and when it's closed the underlying worker thread pool (which is in
the prod mode backed by the ExecutorBuildItem) is closed as well
- as a result the quarkus.thread-pool.shutdown-interrupt config property
and logic defined in ExecutorRecorder.createShutdownTask() is completely
ignored
mkouba added a commit to mkouba/quarkus that referenced this issue Jan 31, 2024
- in dev mode we use a special executor for the worker pool where
 the underlying executor can be shut down and then replaced with a new re-initialized executor
- this is a workaround to solve the problem described in quarkusio#16833 (comment)
- the Vertx instance is reused between restarts but we must attempt to shut down this executor,
 for example to cancel/interrupt the scheduled methods
@mkouba
Copy link
Contributor

mkouba commented Jan 31, 2024

the dev mode still kicking after the application has stopped is not visually pretty, but it goes away after some time. that would not be something that prevents people from developing.

Actually, it's not only a visual thing. Those tasks may block the threads from the worker pool and make the app unresponsive...

mkouba added a commit to mkouba/quarkus that referenced this issue Jan 31, 2024
- in the prod mode we wrap the ExecutorService and the shutdown() and
shutdownNow() are deliberately not delegated
- this is a workaround to solve the problem described in
quarkusio#16833 (comment)
- the Vertx instance is closed before
io.quarkus.runtime.ExecutorRecorder.createShutdownTask() is used
- and when it's closed the underlying worker thread pool (which is in
the prod mode backed by the ExecutorBuildItem) is closed as well
- as a result the quarkus.thread-pool.shutdown-interrupt config property
and logic defined in ExecutorRecorder.createShutdownTask() is completely
ignored
mkouba added a commit to mkouba/quarkus that referenced this issue Jan 31, 2024
- in dev mode we use a special executor for the worker pool where
 the underlying executor can be shut down and then replaced with a new re-initialized executor
- this is a workaround to solve the problem described in quarkusio#16833 (comment)
- the Vertx instance is reused between restarts but we must attempt to shut down this executor,
 for example to cancel/interrupt the scheduled methods
mkouba added a commit to mkouba/quarkus that referenced this issue Feb 1, 2024
- in the prod mode we wrap the ExecutorService and the shutdown() and
shutdownNow() are deliberately not delegated
- this is a workaround to solve the problem described in
quarkusio#16833 (comment)
- the Vertx instance is closed before
io.quarkus.runtime.ExecutorRecorder.createShutdownTask() is used
- and when it's closed the underlying worker thread pool (which is in
the prod mode backed by the ExecutorBuildItem) is closed as well
- as a result the quarkus.thread-pool.shutdown-interrupt config property
and logic defined in ExecutorRecorder.createShutdownTask() is completely
ignored
mkouba added a commit to mkouba/quarkus that referenced this issue Feb 1, 2024
- in dev mode we use a special executor for the worker pool where
 the underlying executor can be shut down and then replaced with a new re-initialized executor
- this is a workaround to solve the problem described in quarkusio#16833 (comment)
- the Vertx instance is reused between restarts but we must attempt to shut down this executor,
 for example to cancel/interrupt the scheduled methods
@quarkus-bot quarkus-bot bot added this to the 3.9 - main milestone Feb 1, 2024
@vsevel
Copy link
Contributor Author

vsevel commented Feb 1, 2024

@mkouba I ran my reproducer with your branch. For the quarkus dev use case, I get this output upon restart s:

2024-02-01 20:08:41,029 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Thu Feb 01 20:08:27 CET 2024 => 15
2024-02-01 20:08:42,033 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Thu Feb 01 20:08:27 CET 2024 => 16
2024-02-01 20:08:43,036 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Thu Feb 01 20:08:27 CET 2024 => 17
2024-02-01 20:08:44,039 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Thu Feb 01 20:08:27 CET 2024 => 18
2024-02-01 20:08:45,043 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Thu Feb 01 20:08:27 CET 2024 => 19
2024-02-01 20:08:45,348 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (Aesh InputStream Reader) Restarting as requested by the user.
2024-02-01 20:08:45,409 INFO  [io.quarkus] (Quarkus Main Thread) scheduler stopped in 0.054s
2024-02-01 20:08:46,048 INFO  [org.acm.MyTimer] (vert.x-worker-thread-2) execute Thu Feb 01 20:08:27 CET 2024 => 20
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2024-02-01 20:08:46,511 INFO  [io.quarkus] (Quarkus Main Thread) scheduler 1.0.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 1.080s. Listening on: http://localhost:8080

2024-02-01 20:08:46,513 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2024-02-01 20:08:46,514 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy-reactive, scheduler, smallrye-context-propagation, vertx]
2024-02-01 20:08:46,516 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (Aesh InputStream Reader) Live reload total time: 1.176s 
2024-02-01 20:08:46,518 ERROR [org.acm.MyTimer] (vert.x-worker-thread-2) timer interrupted: java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at org.acme.MyTimer.run(MyTimer.java:25)
	at org.acme.MyTimer_ClientProxy.run(Unknown Source)
	at org.acme.MyTimer_ScheduledInvoker_run_72e66771a77415a7284d3ae42331659c186071de.invokeBean(Unknown Source)
	at io.quarkus.scheduler.common.runtime.DefaultInvoker.invoke(DefaultInvoker.java:24)
	at io.quarkus.scheduler.common.runtime.StatusEmitterInvoker.invoke(StatusEmitterInvoker.java:35)
	at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask.doInvoke(SimpleScheduler.java:443)
	at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask$2.call(SimpleScheduler.java:425)
	at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask$2.call(SimpleScheduler.java:422)
	at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$0(ContextImpl.java:177)
	at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:276)
	at io.vertx.core.impl.ContextImpl.lambda$internalExecuteBlocking$2(ContextImpl.java:209)
	at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1512)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:833)


2024-02-01 20:08:47,011 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 1
2024-02-01 20:08:48,016 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 2
2024-02-01 20:08:49,023 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 3
2024-02-01 20:08:50,029 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 4
2024-02-01 20:08:51,033 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 5
2024-02-01 20:08:52,039 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 6
2024-02-01 20:08:53,041 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 7
2024-02-01 20:08:54,044 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 8
2024-02-01 20:08:55,051 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 9
2024-02-01 20:08:56,054 INFO  [org.acm.MyTimer] (vert.x-worker-thread-4) execute Thu Feb 01 20:08:47 CET 2024 => 10
2024-02-01 20:08:56,099 INFO  [io.quarkus] (Quarkus Main Thread) scheduler stopped in 0.022s

so once the app restarts, I do not see anymore ticks from the old timer. But I see a interrupted exception, after startup. This means there was no graceful shutdown. In theory stopping the previous app should have waited for the ongoing tasks, stop then restart.

is this what you expected?

I ran the second use case: graceful shutdown, running java -jar:

2024-02-01 20:13:13,346 INFO  [io.quarkus] (main) scheduler 1.0.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 2.380s. Listening on: http://0.0.0.0:8080
2024-02-01 20:13:13,350 INFO  [io.quarkus] (main) Profile prod activated. 
2024-02-01 20:13:13,351 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, scheduler, smallrye-context-propagation, vertx]
2024-02-01 20:13:14,039 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 1
2024-02-01 20:13:15,040 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 2
2024-02-01 20:13:16,042 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 3
2024-02-01 20:13:17,048 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 4
^C2024-02-01 20:13:18,053 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 5
2024-02-01 20:13:19,057 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 6
2024-02-01 20:13:20,060 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 7
2024-02-01 20:13:21,065 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 8
2024-02-01 20:13:22,070 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 9
2024-02-01 20:13:22,132 INFO  [io.qua.thread-pool] (Shutdown thread) Awaiting thread pool shutdown; 1 thread(s) running with 0 task(s) waiting
2024-02-01 20:13:23,075 INFO  [org.acm.MyTimer] (executor-thread-1) execute Thu Feb 01 20:13:14 CET 2024 => 10
2024-02-01 20:13:24,082 INFO  [io.quarkus] (Shutdown thread) scheduler stopped in 7.016s

the behavior looks good. I was thinking there would be a stopped log, like in this 3.7.1 aplication:

2024-02-01 20:16:32,005 INFO  [io.quarkus] (main) toto 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.7.1) started in 1.576s. Listening on: http://0.0.0.0:8080
2024-02-01 20:16:32,027 INFO  [io.quarkus] (main) Profile prod activated. 
2024-02-01 20:16:32,028 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, smallrye-context-propagation, vertx]
^C2024-02-01 20:16:35,461 INFO  [io.quarkus] (main) toto stopped in 0.056s

is that expected?

@mkouba
Copy link
Contributor

mkouba commented Feb 1, 2024

But I see a interrupted exception, after startup. This means there was no graceful shutdown. In theory stopping the previous app should have waited for the ongoing tasks, stop then restart.

is this what you expected?

@vsevel Yes, that's expected. I don't think that graceful shutdown makes any sense in the dev mode.

is that expected?

You're missing the toto stopped in 0.056s line, right? I have no idea why it's not there. Need to take a look.

@vsevel
Copy link
Contributor Author

vsevel commented Feb 2, 2024

Yes, that's expected. I don't think that graceful shutdown makes any sense in the dev mode.

fair enough

You're missing the toto stopped in 0.056s line, right?

right

I have no idea why it's not there. Need to take a look.

just in case it is a smell.

thanks for looking into this issue.

@mkouba
Copy link
Contributor

mkouba commented Feb 2, 2024

I have no idea why it's not there. Need to take a look.

just in case it is a smell.

thanks for looking into this issue.

@vsevel Actually, the message is there! It's the INFO [io.quarkus] (Shutdown thread) scheduler stopped in 7.016s because your app is named scheduler as indicated on the line INFO [io.quarkus] (main) scheduler 1.0.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 2.380s. Listening on: http://0.0.0.0:8080.

@vsevel
Copy link
Contributor Author

vsevel commented Feb 2, 2024

of course! one thing that tricked me was the thread difference.
with the scheduler app, it is stopping on the Shutdown thread, whereas in the toto app, it is on the main thread.
I guess we are all good then. thanks!

ozangunalp added a commit to ozangunalp/quarkus that referenced this issue Sep 17, 2024
ozangunalp added a commit to ozangunalp/quarkus that referenced this issue Sep 17, 2024
ozangunalp added a commit to ozangunalp/quarkus that referenced this issue Sep 17, 2024
ozangunalp added a commit to ozangunalp/quarkus that referenced this issue Sep 17, 2024
mskacelik pushed a commit to mskacelik/quarkus that referenced this issue Sep 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/scheduler kind/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants