Skip to content

Race condition in computing write timestamp #1623

@chaoren

Description

@chaoren

The expireAfterWrite timestamp for an async load should be computed when the CompletableFuture is complete and not when it is put into the cache, right? It looks like there's an after-write maintenance task to make incomplete CompletableFutures unevictable after the CompletableFuture is first put into the cache, but if the CompletableFuture is completed before that maintenance task runs, then the write timestamp would be incorrect.

Here's a failing test case of the issue:

  @Test
  public void writeTimeRaceCondition() {
    FakeTicker ticker = new FakeTicker();
    CountDownLatch latch = new CountDownLatch(1);
    AsyncCache<String, String> cache =
        Caffeine.newBuilder()
            .executor(
                command ->
                    commonPool()
                        .execute(
                            () -> {
                              awaitUninterruptibly(latch);
                              command.run(); // afterWrite maintenance task
                            }))
            .expireAfterWrite(Duration.ofMillis(5))
            .ticker(ticker::read)
            .buildAsync();
    CompletableFuture<String> future = cache.get("key", (k, e) -> new CompletableFuture<>());

    ticker.advance(Duration.ofMillis(10));
    future.complete("value");
    latch.countDown();

    assertThat(cache.getIfPresent("key")).isNotNull();
    ticker.advance(Duration.ofMillis(4));
    assertThat(cache.getIfPresent("key")).isNotNull();
    ticker.advance(Duration.ofMillis(1));
    assertThat(cache.getIfPresent("key")).isNull();
  }

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