I have a spring-boot application.
I have implemented SmartLifecycle
interface in my bean which starts async snmp server in it's start
method and stops it in it's stop
method.
All working fine, except the fact that main application context stops right after start, so my server bean also stops right after start.
All I need is to make spring context to stop only when shutdown hook is fired.
This is not a web application, so I don't need spring-boot-starter-web
, which is solves this problem by starting webserver which prevents context stop until webserver stops.
I can use something like CountDownLatch
and waiting for it to be zero in my main
method right after context starts. Somethig like this:
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext ctx = SpringApplication.run(SnmpTrapRetranslatorApplication.class, args);
CountDownLatch snmpServerCloseLatch = ctx.getBean("snmpServerCloseLatch", CountDownLatch.class);
snmpServerCloseLatch.await();
}
And my server bean's start
method will create this latch with count 1
, while stop
method will call snmpServerCloseLatch.countDown()
.
This technique is described here.
But what wrong with this is that my main
method is responsible for waiting my custom server bean to stop. I feel this just not right.
How for example spring-boot-starter-web
do this? When it starts tomcat, it keeps running until shutdown hook is received and it don't need to have any managing code in the main
method. It stops only when context receiving shoutdown signal.
The same behaviour is for example when I have @Scheduled
method in my bean. Spring also doesn't stops context automatically. Only on CTRL-C
.
I want to achieve similar effect. My main
method should have only one line: start the context. Context should start and stop my async server when it starts or stops (already achieved by SmartLifecycle
) and should not stop until shutdown is requested (CTRL-C, SIGINT etc).
My investigation lead me to the core of the problem: daemon threads.
The snmp server implementation which I use (snmp4j) use daemon threads internally. So even when snmp server started, there are no more live user threads in JVM, so it exits.
TL/DR:
Just add this method to any bean (snmp server bean is good candidate for this):
(Do not forget to add
@EnableScheduling
to your spring configuration).Explanation:
To prevent stopping spring context, while SNMP server is still running, we need any non-daemon thread to be alive in JVM. Not necessarily
main
thread. So we can letmain
method to finish.We can run new non-daemon thread from our server bean's
start
method. This thread willwait
on some lock inwhile
loop checking for somerunning
variable, while ourstop
method will set thisrunning
variable tofalse
andnotifyAll
on this lock.This way, our non-daemon thread will be alive until shotdown hook is triggered (and prevents JVM to exit). After shutdown hook, spring context lifecycle
close
method will call allSmartLifecycle
bean'sclose
methods, that will lead to SNMP server bean'sstop
method call, that will lead to setrunning
to false, that will lead to our non-daemon thread to stop, that allow JVM to stop gracefully.Or instead we can use Spring's scheduling thread in similar way. It also is non-daemon thread, so it will prevent JVM to exit. And Spring manages this thread itself, so it will automatically stop it when shutdown hook is triggered.
To make Spring's scheduling thread to start we need any
@Scheduled
method in any bean.I think that first (manual) approach is still more "correct", while requires more async coding (which is error-prone as we all know). Who knows how Spring will change it's scheduling implementation in the future.