How to prevent Spring app context shutdown until shutdown hook is fired - java

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):
#Scheduled(fixedDelay = 1000 * 60 * 60) // every hour
public void doNothing() {
// Forces Spring Scheduling managing thread to start
}
(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 let main method to finish.
We can run new non-daemon thread from our server bean's start method. This thread will wait on some lock in while loop checking for some running variable, while our stop method will set this running variable to false and notifyAll 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 all SmartLifecycle bean's close methods, that will lead to SNMP server bean's stop method call, that will lead to set running 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.

SpringApplication app = new SpringApplication(Main.class);
app.setRegisterShutdownHook(false);
ConfigurableApplicationContext applicationContext= app.run();
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
#Override
public void run() {
//do your things
applicationContext.close();
}
}));

Related

Spring's DeferredResult setResult interaction with timeouts

I'm experimenting with Spring's DeferredResult on Tomcat, and I'm getting crazy results. Is what I'm doing wrong, or is there some bug in Spring or Tomcat? My code is simple enough.
#Controller
public class Test {
private DeferredResult<String> deferred;
static class DoSomethingUseful implements Runnable {
public void run() {
try { Thread.sleep(2000); } catch (InterruptedException e) { }
}
}
#RequestMapping(value="/test/start")
#ResponseBody
public synchronized DeferredResult<String> start() {
deferred = new DeferredResult<>(4000L, "timeout\n");
deferred.onTimeout(new DoSomethingUseful());
return deferred;
}
#RequestMapping(value="/test/stop")
#ResponseBody
public synchronized String stop() {
deferred.setResult("stopped\n");
return "ok\n";
}
}
So. The start request creates a DeferredResult with a 4 second timeout. The stop request will set a result on the DeferredResult. If you send stop before or after the deferred result times out, everything works fine.
However if you send stop at the same time as start times out, things go crazy. I've added an onTimeout action to make this easy to reproduce, but that's not necessary for the problem to occur. With an APR connector, it simply deadlocks. With a NIO connector, it sometimes works, but sometimes it incorrectly sends the "timeout" message to the stop client and never answers the start client.
To test this:
curl http://localhost/test/start & sleep 5; curl http://localhost/test/stop
I don't think I'm doing anything wrong. The Spring documentation seems to say it's okay to call setResult at any time, even after the request already expired, and from any thread ("the
application can produce the result from a thread of its choice").
Versions used: Tomcat 7.0.39 on Linux, Spring 3.2.2.
This is an excellent bug find !
Just adding more information about the bug (that got fixed) for a better understanding.
There was a synchronized block inside setResult() that extended up to the part of submitting a dispatch. This can cause a deadlock if a timeout occurs at the same time since the Tomcat timeout thread has its own locking that permits only one thread to do timeout or dispatch processing.
Detailed explanation:
When you call "stop" at the same time as the request "times out", two threads are attempting to lock the DeferredResult object 'deferred'.
The thread that executes the "onTimeout" handler
Here is the excerpt from the Spring doc:
This onTimeout method is called from a container thread when an async request times out before the DeferredResult has been set. It may invoke setResult or setErrorResult to resume processing.
Another thread that executes the "stop" service.
If the dispatch processing called during the stop() service obtains the 'deferred' lock, it will wait for a tomcat lock (say TomcatLock) to finish the dispatch.
And if the other thread doing timeout handling has already acquired the TomcatLock, that thread waits to acquire a lock on 'deferred' to complete the setResult()!
So, we end up in a classic deadlock situation !

OSGi background thread failure

What should be done when a BundleActivator runs a background thread, and that background thread has an unrecoverable error?
public class Activator implements BundleActivator
{
private Thread t;
#Override
public void start(BundleContext context) throws Exception
{
t = new Thread(new Runnable(){
#Override
public void run(){
while (!Thread.interrupted()){
// do something which may throw a runtime exception
}
}
});
t.start();
}
#Override void stop(BundleContext context) throws Exception
{
t.interrupt();
t.join();
}
}
With this example, how can I notify the OSGi framework that the thread is dead and the bundle is effectively stopped and not running?
Look at how Peter Kriens performs similar actions in this article. All you would need to do with his example is invoke the stop on the activator in his catch block, instead of doing the printStackTrace.
Probably the best thing to do is just log the error, preferably to the OSGi Log Service. Then an administrator can detect the problem with the bundle and decide what to do. You should implement this as a Declarative Services component rather than as a BundleActivator, because that will give you much easier access to the Log Service, and you will also be able to have more than one of these things in your bundle.
I don't think that the bundle should attempt to stop itself. This puts the bundle in a weird state.... it's stopped but still has code running... i.e. the code that called stop(). This may be only for a brief period but it feels wrong.
A bundle that's in the ACTIVE state doesn't necessarily have to be "doing something" all the time, it just has the potential to "do something". The fact that something failed shouldn't really affect the external state of the bundle.
As far as I know, OSGi cannot directly help you in this particular situation. I usually rely on uncaught exception handlers to get notified of thread crashes or I implement some form of SW watchdog.
The point is that a bundle that spawns multiple threads and sucessfully completes its start method remains ACTIVE even if one of these threads crashes after some time.
Neil is (as usual) very right. A bundle should never stop itself since that interferes with the management agent. The start/stop is the message from this management agent to a bundle to say that it should be active. If the bundle cannot perform its responsibility you should log the message, wait a bit (increasingly longer) and retry.
The log is the place to notify, stopping a bundle is mixing levels badly.

Tomcat Cluster 6 and ContextListener

On single instance of Tomcat I have a thread that was started when context was initialized. Something like this :
public class MyContextListener implements ServletContextListener {
private MyThread thread = null;
#Override
public void contextInitialized(ServletContextEvent sce) {
//Start thread...
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
//Stop thread...
}
}
This thread performs some important jobs in system every 10 minutes, and it was working fine.
Now I have switched to cluster of to instances of tomcat and this thread is running on two instances. I'm trying to achieve different behavior.
What I'm trying to achieve:
This thread should be running only on one instance at the time.
If first instance fails (on which thread was running), thread should be started on second instance.
I would be grateful for any hint.
What is my application logic ?
Application logic that is executed by a thread is as follows:
Read sth from DB.
Analyze DB information.
Do HTTP request to external system, if needed.
Sleep thread for another 10 minutes.
The point is: If I will have 2 instances of tomcat, only one should execute this logic
If I understand correctly you are not really allowed to start a new thread in your application when using a web-application-server. All threads must be managed by the application server.

Understanding launching of Spring batch Jobs in a web container

I was reading the Spring Batch documentation when I came across the fact that we would have to use a different implementation of the TaskExecutor interface (The Asynchronous version) if we would efficiently have to run batch jobs from a web container.
I am assuming that an Http request would trigger the batch job. And as I understand it, when the client launches the job via the run method of the JobLauncher interface, the client has to wait for the JobExecution object to be returned back and since a typical batch job would run for hours at an end, this might not be very feasible if the jobs are executed synchronously. Now, the AsyncTaskExecutor would execute each step in separate threads and would return the JobExecution object immediately with an UNKNOWN status.
Firstly, can someone please explain to me, how this works from a client-server connection perspective? In each case, would the client not wait for the batch to be finished before he terminates the session? Or, would the client not know about the exit status of the batch job? Is the whole problem to do with the connection having to remain till the batch ends?
As an example, say the client has a web page which sends an HTTP get request, which is served by a servlet's doget method. This method calls the run method of the job launcher. This method will return the JobExecution object. And the rest of the story is as above.
Thanks,
Aditya.
It depends a bit on what your servlet does after it has called the run method and received the JobExecution object in return. I will assume that the doget method just returns after run is called.
If you do not use an asynchronous executor the call to the run method on the job launcher will be executed synchronously. That is, the call will wait until the batch job is done and the JobExecution object is returned. From a connection perspective, the client's HTTP connection will remain open during the entire batch job. The HTTP connection will be closed when the servlet's doGet method returns (or before if some kind of timeout is encountered on some level, e.g. firewall or a socket read timeout).
If you do use a asynchronous executor the call to the run method will return immediately. The doGet method will return after that, the HTTP response will be sent to the client and the connection will be closed (assuming there is not HTTP keep-alive).
Running Jobs from within a Web Container
Usually jobs are launched from the command-line. However, there are many cases where launching from an HttpRequest is a better option. Many such use cases include reporting, ad-hoc job running, and web application support. Because a batch job by definition is long running, the most important concern is ensuring to launch the job asynchronously:
Here Spring MVC controller launches a Job using a JobLauncher that has been configured to launch asynchronously, which immediately returns a JobExecution. The Job will likely still be running, however, this nonblocking behaviour allows the controller to return immediately, which is required when handling an HttpRequest.
An example is below:
#Controller
public class JobLauncherController {
#Autowired
JobLauncher jobLauncher;
#Autowired
Job job;
#RequestMapping("/jobLauncher.html")
public void handle() throws Exception{
jobLauncher.run(job, new JobParameters());
}
}
source

Background Thread for a Tomcat servlet app [duplicate]

This question already has answers here:
How to run a background task in a servlet based web application?
(5 answers)
Closed 7 years ago.
I am not very familiar with Tomcat, in my head it is basically abstracted as a cgi server that saves the JVM between calls -- I know it can do a lot more than that, though.
I am looking for a way to launch a background thread when a Tomcat server starts, which would periodically update the Server Context (in my particular case this is a thread that listens to heartbeats from some other services and updates availability information, but one can imagine a variety of uses for this).
Is there a standard way to do this? Both the launching, and the updating/querying of the Context?
Any pointers to the relevant documentation and/or code samples would be much appreciated.
If you want to start a thread when your WAR is deployed, you can define a context listener within the web.xml:
<web-app>
<listener>
<listener-class>com.mypackage.MyServletContextListener</listener-class>
</listener>
</web-app>
Then implement that class something like:
public class MyServletContextListener implements ServletContextListener {
private MyThreadClass myThread = null;
public void contextInitialized(ServletContextEvent sce) {
if ((myThread == null) || (!myThread.isAlive())) {
myThread = new MyThreadClass();
myThread.start();
}
}
public void contextDestroyed(ServletContextEvent sce){
try {
myThread.doShutdown();
myThread.interrupt();
} catch (Exception ex) {
}
}
}
I am looking for a way to launch a background thread when a Tomcat server starts
I think you are looking for a way to launch a background thread when your web application is started by Tomcat.
This can be done using a ServletContextListener. It is registered in web.xml and will be called when your app is started or stopped. You can then created (and later stop) your Thread, using the normal Java ways to create a Thread (or ExecutionService).
Putting <load-on-startup>1</load-on-startup> in the <servlet> block in your web.xml will force your servlet's init() to happen as soon as Tomcat starts up, rather than waiting for the first request to arrive. This is useful if you want to spawn the background thread from init().
I'd just make a small change to the very detailed answer Chris gave; I would set myThread to be a Daemon thread by myThread.setDaemon(true); which will basically keep the thread active as long as you have other non-Daemon threads working which need your background thread around. When all these threads finish then your Daemon thread is stopped by the JVM and you do not need to handle it youself in contextDestroyed.
But that's just my 2 cents.
Another way if you are using spring based framework you can specify the class/thread which you want to initialize in the beans.xml. So when the tomcat starts up, beans.xml will initialize all the classes mentioned in it. You can also pass constructor arguments if required. Below is the example of the same.
beans.xml
<bean id="monitoringSvc" class="com.mypackage.MonitoringService">
<constructor-arg value="60"></constructor-arg>
</bean>
MonitoringService.java
public class MonitoringService{
private MyThread myThread;
public MonitoringService(int seconds){
myThread = new MyThread(seconds);
myThread.start();
}
}

Categories

Resources