Pending future can block time based eviction for AsyncCache (original) (raw)

When a new key+value (where value is a CompletableFuture) is added to AsyncCache it is at the end of writeOrderDeque with a token timestamp far in future. It will eventually make its way to writeOrderDeque.first where it will block any time based evictions if the future has not completed yet (and the timestamp is still the token value).

to reproduce:

    AtomicLong now = new AtomicLong(System.nanoTime());
    Ticker ticker = now::get;

    AsyncCache<String, String> cache = Caffeine.newBuilder()
      .maximumSize(100)
      .expireAfterWrite(2, TimeUnit.MINUTES)
      .ticker(ticker)
      .buildAsync();

    CompletableFuture<String> stalledDownload = new CompletableFuture<>(); // never completed!
    cache.put("stuck-key", stalledDownload);

    for (int i = 0; i < 50; i++) {
      cache.put("normal-" + i, CompletableFuture.completedFuture("val-" + i));
    }

    System.out.println("map size before cleanup: " + cache.asMap().size());

    // Advance time
    now.addAndGet(TimeUnit.HOURS.toNanos(1));

    cache.synchronous().cleanUp();

    int sizeAfterCleanup = cache.asMap().size();
    System.out.println("map size after cleanup: " + sizeAfterCleanup);

    int iterCountAfterCleanup = 0;
    for (var ignored : cache.asMap().entrySet()) {
      iterCountAfterCleanup++;
    }

    System.out.println("live count: " + iterCountAfterCleanup);

this gives:

map size before cleanup: 51
map size after cleanup: 51
live count: 1