Al-Mothafar Al-Mothafar - 3 months ago 46
Java Question

Playframework + Akka: how to avoid excuting scheduled tasks executed when shutdown application

I have issue with scheduler that I start when the play application server start, but once application got shutdown, its hit the following part of code :

// firstDay something like 1 = monday
private void startScheduler(final ImageService imageService,
final ActorSystem system) {
startImagesCleanupScheduler(imageService, system);
Logger.info("Schedulers started");
}


My problem that the
Runnable
block start to execute immediately instead of just cancel the task.

To clarify code:

The following method to start the
Scheduler
:

private void startImagesCleanupScheduler(ImageService imageService, ActorSystem system) {
system.scheduler().schedule(
Duration.create(0, TimeUnit.MILLISECONDS), //Initial delay
Duration.create(1, TimeUnit.DAYS), //Frequency 1 days
() -> {
int rows = imageService.cleanupInactiveImages();
Logger.info(String.format("%d inactive unused images cleaned from db", rows));

},
system.dispatcher()
);
}


The log when I shutdown note the first line here :

[info] - application - 1 inactive unused images cleaned from db
[info] - application - Shutting down connection pool.
[info] - application - Creating Pool for datasource 'default'
...
[info] - application - Schedulers started
[info] - play.api.Play - Application started (Prod)


You can see it executed the Scheduler ignoring its original execution time, then got shutting down, and then started, and "Schedulers started" after.

What the problem, how I cancel the scheduler or prevent the play to run it before shutdown? is it a bug for Akka?

I'm calling
startScheduler
inside
OnStartup
like the answer of the following question :

java Playframework GlobalSettings deprecation for onStart

Edit:
The following the minimal code to reproduce issue:

First Create
OnStartup
class:

@Singleton
public class OnStartup {

@Inject
public OnStartup(final ActorSystem system) {
startScheduler(system);
}

private void startScheduler(final ActorSystem system) {
startImagesCleanupScheduler(system);
Logger.info("Schedulers started");
}

private void startImagesCleanupScheduler(ActorSystem system) {
system.scheduler().schedule(
Duration.create(0, TimeUnit.MILLISECONDS), //Initial delay
Duration.create(1, TimeUnit.DAYS), //Frequency 1 days
() -> {
//int rows = imageService.cleanupInactiveImages();
rows = 1;
Logger.info(String.format("%d inactive unused images cleaned from db", rows ));
},
system.dispatcher()
);
}

}


Then create module:

public class OnStartupModule extends AbstractModule {
@Override
public void configure() {
bind(OnStartup.class).asEagerSingleton();
}
}


Finally enable module in application.conf :

play.modules.enabled += "modules.OnStartupModule"

Answer

This is a bit too long for a comment, but I did not test this approach, you might need to tweak it. The documentation says that the components are destroyed in the reverse order they are created. That means you could potentially cancel your scheduler before the ActorSystem shuts down, since this class is registered after it and depends on it. Create a shutdown hook like this in your OnStartup class in which you will cancel your schedule. That means at the point of the ActorSystem shutdown, there will be no schedule to execute:

@Singleton
public class OnStartup {

    private final Cancellable cancellableSchedule;

    @Inject
    public OnStartup(final ActorSystem system, final ApplicationLifecycle l) {
        cancellableSchedule = startScheduler(system);
        initStopHook(l);
    }

    private Cancellable startScheduler(final ActorSystem system) {
        return startImagesCleanupScheduler(system);
        Logger.info("Schedulers started");
    }

    private Cancellable startImagesCleanupScheduler(ActorSystem system) {
        return system.scheduler().schedule(
            Duration.create(0, TimeUnit.MILLISECONDS), //Initial delay
            Duration.create(1, TimeUnit.DAYS),     //Frequency 1 days
            () -> {
                //int rows = imageService.cleanupInactiveImages();
                rows = 1;
                Logger.info(String.format("%d inactive unused images cleaned from db", rows ));
                },
            system.dispatcher()
        );
    }

    private void initStopHook(ApplicationLifecycle lifecycle) {
       lifecycle.addStopHook(() -> {
            cancellableSchedule.cancel();
        return CompletableFuture.completedFuture(null);
        });
     }

}