0

Traffic on my site can get heavy and I would like to slow down how frequently I run an expensive cleanup function; I would also like to run it in the background.

I implemented a cache with a removalListener and expected it to run asynchronously 30 seconds after an entry was created, but I'm seeing it either not run at all or run instantly. I also am having to wait for the cleanup script when it does run. Am I misunderstanding the purpose of a cache and removal listener?

@Path("my-endpoint")
public class MyResource{

    private static final Cache<String, String> debounceCleaner = CacheBuilder
        .newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(30, TimeUnit.SECONDS)
        .removalListener((RemovalListener<String, String>) x-> new Cleaner().clean())
        .build();

    @PUT
    public Response update(){
        ...

        try{
            debounceCleaner.get("foo", ()->"bar");
        } catch (ExecutionException ex){
            throw new IllegalStateException("Unhandled", ex);
        }

        return Response(...)
solbs
  • 940
  • 3
  • 15
  • 29
  • where do you set to the cache? – matanper May 02 '19 at 17:53
  • debounceCleaner is the cache. I found a SO question which may make mine duplicate -- I will update accordingly https://stackoverflow.com/questions/10626720/guava-cachebuilder-removal-listener – solbs May 02 '19 at 17:55
  • 1
    Guava doesn't use a scheduling thread to handle expiration, but waits until it expires and other accesses occur. When enough work piles up, it triggers a maintenance cycle and sends the notification. You can of course use a `ScheduledExecutorService` to schedule it yourself. – Ben Manes May 03 '19 at 16:45
  • Thanks, Ben. I noticed that it triggered on the next access, which is not what I want. I also tried your Caffeine package (which performed better), but I broke a fundamental rule of coding -- using a package for something for which it was not intended. – solbs May 03 '19 at 17:05

1 Answers1

0

Cache expiration is not intended to be used as a debouncer. Other questions have answered how to create a debouncer to varying degrees.

implementing debounce in Java Deliver the first item immediately, 'debounce' following items

I wrote a debouncer for this application as follows:

public class Debouncer {

    private final int TIMEOUT = 30000;
    private Runnable runnable;
    private Thread backgroundRunnerLoop;
    private Date lastRun = null;
    private Date lastRequest = null;

    public Debouncer(Runnable runnable) {
        this.runnable = runnable;
        this.backgroundRunnerLoop = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(TIMEOUT);
                    if (lastRequest != null && (lastRun == null || lastRun.before(DateUtils.addMilliseconds(lastRequest, TIMEOUT)))) {
                        this.runnable.run();
                        lastRun = new Date();
                    }
                } catch (Exception ex) {
                    throw new IllegalStateException("Debouncer loop broke.", ex);
                }
            }
        });
    }

    public void start() {
        if(!backgroundRunnerLoop.isAlive()){
            backgroundRunnerLoop.start();
        }
    }

    public void resetTimer() {
        lastRequest = new Date();
    }
}

I instantiate it like this

@Path("my-endpoint")
public class MyResource {
    private static final Debouncer debouncer = new Debouncer(() -> new Cleaner().run());

    public MyResource() {
        debouncer.start();
    }

    @PUT
    public Response update(){
        // ...
        debouncer.resetTimer();

        return Response.noContent(...).build();
    }
}
solbs
  • 940
  • 3
  • 15
  • 29