11
votes

I have a Spring Boot + Spring Data Redis/KeyValue project. I setup a Spring profile to run the application with all dependencies embedded. So at startup, I launch an embedded Redis Server. Everything works fine when I start it in Eclipse, except that I would like the Redis server to be stopped when I stop the Spring Boot application. So I setup several shutdown hooks, however they are not called when I terminate the application from Eclipse.

They are similar questions on SO, I created this one hoping there would be a Redis solution. Also none of these similar questions are specific to Spring Boot.

I tried many things:

  • Spring Boot's ExitCodeGenerator;
  • DisposableBean;
  • @PreDestroy;
  • I tried a ShutdownHook (after @mp911de's answer)

None of them are called.

Perhaps there is a Redis option, timeout, keep alive.. something outside the box I am not aware of ? How can I ensure that the Redis Server is stopped when my Spring Boot app is abruptly stopped ?

=> I saw this Embedded Redis for spring boot, but @PreDestroy is just not called when killing the application unexpectedly.

Here are some of the similar questions:

I also saw this post on eclipse.org discussing how shutdown hook is not called when stopping an application from eclipse: Graceful shutdown of Java Applications


Here's all my relevant code:

Component to start the embedded Redis server at startup (With my attempts to stop it too!!):

@Component
public class EmbeddedRedis implements ExitCodeGenerator, DisposableBean{

    @Value("${spring.redis.port}")
    private int redisPort;

    private RedisServer redisServer;

    @PostConstruct
    public void startRedis() throws IOException {
        redisServer = new RedisServer(redisPort);
        redisServer.stop();
        redisServer.start();
    }

    @PreDestroy
    public void stopRedis() {
        redisServer.stop();
    }

    @Override
    public int getExitCode() {
        redisServer.stop();
        return 0;
    }

    @Override
    public void destroy() throws Exception {
        redisServer.stop();
    }
}

application.properties:

spring.redis.port=6379

Spring Boot App:

@SpringBootApplication
@EnableRedisRepositories
public class Launcher {

    public static void main(String[] args){
        new SpringApplicationBuilder() //
        .sources(Launcher.class)//
        .run(args);
    }

    @Bean
    public RedisTemplate<String, Model> redisTemplate() {
        RedisTemplate<String, Model> redisTemplate = new RedisTemplate<String, Model>();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        return redisTemplate;
    }


    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setHostName("localhost");
        return jedisConnectionFactory;
    }
}

Redis server (~ embedded) running:

$ ps -aef | grep redis
 ... /var/folders/qg/../T/1472402658070-0/redis-server-2.8.19.app *:6379  

The embedded Redis maven dependency:

<dependency>
    <groupId>com.github.kstyrc</groupId>
    <artifactId>embedded-redis</artifactId>
    <version>0.6</version>
</dependency>
5

5 Answers

20
votes

When using SpringBoot and Eclipse, you can install the STS (Spring Tool Suite) eclipse plugin to achieve graceful shutdown.

Once installed, run the application as a "Spring Boot App" instead of regular "Java application" (Run/debug configurations)

Ensure that the checkbox "Enable Life Cycle Management" checkbox is checked and when you will click on the red square button to stop the application, it will perform a graceful shutdown instead of a hard kill.

Edit:

It's worth noticing that there are two "red square button". One in the "Launch" toolbar and one in the "Console" panel. The one in the launch toolbar still performs hard kill but the one in the console allows the graceful shutdown for spring-boot application (launched with STS)

7
votes

After investigation, it turns out that Eclipse simply terminates the application without any chance for a shutdown hook to be run.

I've found a workaround/hack thanks to how Spring-Boot is wired. When the main method is executed, tomcat is started in another thread and the main method continues to execute until completion. This allowed me to insert a termination logic when you hit 'Enter'.

The application launches normally and the console simply awaits for an enter key to execute System.exit(1); :

@SpringBootApplication
@EnableRedisRepositories
public class Launcher {
    public static void main(String[] args){
        new SpringApplicationBuilder() //
        .sources(Launcher.class)//
        .run(args);

        System.out.println("Press 'Enter' to terminate");
        new Scanner(System.in).nextLine();
        System.out.println("Exiting");
        System.exit(1);
    }
}

When launching the application from Eclipse, instead of terminating the application from the interface, I now hit enter in the console. Now, the shutdown hooks (@PreDestroy) is triggered and the Redis server is stopped!

Not what I hoped for, but at least for the time being the Embedded Redis Server is stopped with the application and I don't have to keep killing it manually.

3
votes

ExitCodeGenerator requires the application to call the exit method

System.exit(SpringApplication
             .exit(SpringApplication.run(SampleBatchApplication.class, args)));

You could register additionally a shutdown hook. Hooks will be called

  • when the VM is terminated normally (System.exit).
  • in response to an interrupt (Ctrl+C, SIGINT) or signal (SIGHUP, SIGTERM).

In some circumstances, if the VM does not shut down cleanly (SIGKILL, internal VM errors, errors in native methods), there's no guarantee whether shutdown hooks are called or not.

The code for your EmbeddedRedis component could look like:

@PostConstruct
public void startRedis() throws IOException {

    redisServer = new RedisServer(redisPort);
    redisServer.start();

    Runtime.getRuntime().addShutdownHook(new Thread(){

        @Override
        public void run() {
            redisServer.stop();
        }
    });
}
1
votes

I had the same problem, though not with Redis. I wanted maximum portability so other developers don't have to add STS if they don't want to. You can add this to any Spring Boot application to offer clean shutdown. Here's what I did, elaborating on the OP's own answer.

Add this to your main class, or any @Configuration class:

    @Bean
    public ApplicationRunner systemExitListener() {
        return args -> {
            if (args.getOptionValues("exitListener") != null) {
                System.out.println("Press Enter to exit application");
                new Scanner(System.in).nextLine();
                System.out.println("Exiting");
                System.exit(0);
            }
        };
    }

Then in Eclipse (or in your IDE of choice, or even on the command line), add the argument --exitListener to activate the code. In Eclipse, that would be in Run Configurations, on the Arguments tab, in the Program Arguments box.

0
votes

To resolve killing the Spring boot application on stopping the maven build and also enabling debugging: Maven Build -> JRE -> VM Arguments
Add -Dspring-boot.run.fork=false