Skip to content

Invalidate before Refresh completes still stores value #193

@boschb

Description

@boschb

Maybe this has come up before, but this seems a bit odd. Here is my test.

@RunWith(JUnit4.class)
public final class AsyncLoadingCacheTest {
  private final AtomicLong counter = new AtomicLong(0);
  private final FakeTicker ticker = new FakeTicker();

  private ListenableFutureTask<Long> loadingTask;

  private final AsyncCacheLoader<String, Long> loader =
      (key, exec) ->
          // Fools the cache into thinking there is a future that's not immediately ready.
          // (The Cache has optimizations for this that we want to avoid)
          toCompletableFuture(loadingTask = ListenableFutureTask.create(counter::getAndIncrement));

  private final String key = AsyncLoadingCacheTest.class.getSimpleName();

  /** This ensures that any outstanding async loading is completed as well */
  private long loadGet(AsyncLoadingCache<String, Long> cache, String key)
      throws InterruptedException, ExecutionException {
    CompletableFuture<Long> future = cache.get(key);
    if (!loadingTask.isDone()) {
      loadingTask.run();
    }
    return future.get();
  }

  @Test
  public void invalidateDuringRefreshRemovalCheck() throws Exception {
    List<Long> removed = new ArrayList<>();
    AsyncLoadingCache<String, Long> cache =
        Caffeine.newBuilder()
            .ticker(ticker)
            .executor(TestingExecutors.sameThreadScheduledExecutor())
            .<String, Long>removalListener((key, value, reason) -> removed.add(value))
            .refreshAfterWrite(10, TimeUnit.NANOSECONDS)
            .buildAsync(loader);

    // Load so there is an initial value.
    assertThat(loadGet(cache, key)).isEqualTo(0);

    ticker.advance(11); // Refresh should fire on next access
    assertThat(cache.synchronous().getIfPresent(key)).isEqualTo(0L); // Old value

    cache.synchronous().invalidate(key); // Invalidate key entirely
    assertThat(cache.synchronous().getIfPresent(key)).isNull(); // No value in cache (good)
    loadingTask.run(); // Completes refresh

    // FIXME: java.lang.AssertionError: Not true that <1> is null
    assertThat(cache.synchronous().getIfPresent(key)).isNull(); // Value in cache (bad)

    // FIXME: Maybe?  This is what I wanted to actually test :)
    assertThat(removed).containsExactly(0L, 1L); // 1L was sent to removalListener anyways
  }
}

It would appear that a refresh that started before an invalidate takes precedence over the invalidate? What I really wanted to test was the removal listener, but I ran into this first.

I'm using Caffeine 2.5.3

Thanks again for your good work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions