Обновление кэширования в GuavaAfterWrite путаница

Я получаю результаты, которые я действительно не понимаю при использовании Guava Caches.

Я реализую один кеш-ключ, который я хочу обновить асинхронно.

Я попадаю в кеш каждую секунду, и я установил refreshAfterWrite на 20 секунд. Моя функция загрузки/перезагрузки занимает 5 секунд.

Если я распечатаю в начале метода load/reload текущее время - я бы ожидал некоторых результатов, подобных этому:

загрузка вызова началась в 00:00:00
  перезагрузка вызова началась в 00:00:25
  перезагрузка начинается в 00:00:50

Таким образом, загрузка займет 5 секунд, а следующая запись начнет через 20 секунд после этого (5 + 20 = 25). Эта запись будет происходить через 50 секунд (25 + 5 + 20 = 50) секунд после этого.. etc

Вместо этого я получаю:

загрузка вызова началась в 00:00:00
  перезагрузка вызова началась в 00:00:25
  перезагрузка начинается в 00:00:30

Это говорит о том, что вторая перезагрузка происходит сразу после завершения первой перезагрузки.

Я думал, что запись произойдет после того, как будущее будет обработано, и поэтому следующая перезагрузка будет запланирована на 20 секунд после этого?

Я нашел ошибку или у меня есть фундаментальное непонимание того, как работает refreshAfterWrite?

Пример кода ниже:

private static SimpleDateFormat format = new SimpleDateFormat("hh:mm:ss");

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        final ExecutorService executor = Executors.newFixedThreadPool(3);

        final LoadingCache<String, Long> cache = CacheBuilder.newBuilder().maximumSize(1) //
                .refreshAfterWrite(20, TimeUnit.SECONDS)//
                .build(new CacheLoader<String, Long>() {//
                    public Long load(String key) {
                        return getLongRunningProcess("load", key);
                    }

                    public ListenableFuture<Long> reload(final String key, Long prevGraph) {
                        ListenableFutureTask<Long> task = ListenableFutureTask.create(new Callable<Long>() {
                            public Long call() {
                                return getLongRunningProcess("reload", key);
                            }
                        });
                        executor.execute(task);
                        return task;
                    }
                });

        while (true) {
            Thread.sleep(1000L);
            cache.get(CACHE_KEY);
        }
    }

    private static Long getLongRunningProcess(String callType, String key) {
        System.out.printf("%s call started at %s\n", callType, format.format(new Date()));
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return counter.getAndIncrement();
    }

}

Ответ 1

Я думаю, что вы нашли законную ошибку. (Я поддерживаю common.cache.)

Если я правильно соблюдаю, я считаю, что цепочка событий выглядит следующим образом:

Скажем, получим, что A - это первый get, который вызывает обновление, и после этого B является первым get.

  • Получить вызовы scheduleRefresh, который запускает задачу refresh в исполнителе. Ссылка на значение позиции заменяется на LoadingValueReference, а loadAsync добавляет слушателя, ожидающего завершения перезагрузки.
  • Развернутая задача для перезагрузки Get A завершается и получает блокировку.
  • Получить B вызовов scheduleRefresh. Время доступа еще не обновлено, поэтому оно продолжается и переходит в insertLoadingValueReference.
  • Разбитая задача для перезагрузки Get A обновляет время записи и заменяет ссылку на значение с помощью StrongValueReference, так как загрузка завершена. Блокировка отключена.
  • Get B определяет, что значение не находится в процессе загрузки, поэтому оно начинает перезагружать новую версию.

(Обновление: зарегистрировано https://code.google.com/p/guava-libraries/issues/detail?id=1211.)