-1

I have created a cache using CacheBuilder. I have used ExpireAfterWrite and RefreshAfterWrite. I have overrided cacheloader load and reload function. Practically in reload I am calling load by creating ListenableFutureTask and submitting it to ExecutorService. Below is stack trace I am getting -

WARNING: Exception thrown during refresh [junit] com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key abc. [junit] at com.google.common.cache.LocalCache$Segment.getAndRecordStats(Unknown Source) [junit] at com.google.common.cache.LocalCache$Segment$1.run(Unknown Source) [junit] at com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(Unknown Source) [junit] at com.google.common.util.concurrent.ImmediateFuture.addListener(Unknown Source) [junit] at com.google.common.cache.LocalCache$Segment.loadAsync(Unknown Source) [junit] at com.google.common.cache.LocalCache$Segment.refresh(Unknown Source) [junit] at com.google.common.cache.LocalCache$Segment.scheduleRefresh(Unknown Source) [junit] at com.google.common.cache.LocalCache$Segment.get(Unknown Source) [junit] at com.google.common.cache.LocalCache.get(Unknown Source) [junit] at com.google.common.cache.LocalCache.getOrLoad(Unknown Source)

I don't know why its not able to refresh, also It doesn't get to cacheloader.load also. It return null before. I am pretty sure I am not returning null in load function.

Sample code -

public class SampleCacheLoader extends CacheLoader<String, Customer> {

    private final DatabaseClient databaseClient;

    private static final ExecutorService ex  = Executors.newSingleThreadScheduledExecutor();

    @Inject
    public SampleCacheLoader(final DatabaseClient databaseClient) {
        this.databaseClient = databaseClient;
    }

    @Override
    public Customer load(final String customerId) throws Exception {
        Customer customer = databaseClient.getCustomer(customerId);
        if (customer == null) {
            throw new Exception("Customer is null");
        }
        return customer;
    }

@Override
public ListenableFuture<Customer> reload(final String customerId,
                                                     final Customer prevCustomer)
        throws Exception {
    ListenableFutureTask<Customer> task = ListenableFutureTask
            .create(new Callable<Customer>() {

        @Override
        public Customer call() {
            try {
                // try to get a new value
                load(customerId);

            } catch (Throwable e) {
                // or return the old one in the event of failure
                return prevCustomer;
            }
        }
    });

    // run in the background so that concurrent get() requests still return values.
    ex.execute(task);
    return task;
}
}

public class SampleCache {

    public  LoadingCache<String, Customer> sampleCache;

    @Inject
    public SampleCache(final SampleCacheLoader sampleCacheLoader,
                                         final int cacheMaxSize,
                                         final int cacheExpireTime,
                                         final int cacheRefreshTime,
                                         final int concurrencryLevel,
                                         final Ticker ticker) {
        this.cache = CacheBuilder.newBuilder()
                .maximumSize(cacheMaxSize)
                .expireAfterWrite(cacheExpireTime, TimeUnit.MINUTES)
                .refreshAfterWrite(cacheRefreshTime, TimeUnit.MINUTES)
                .concurrencyLevel(concurrencryLevel)
                .ticker(ticker)
                .build(sampleCacheLoader);
    }

    public Optional<Customer> get(final String customerId) {
        try {
            Customer customer = cache.get(customerId);
            return Optional.of(customer);
        } catch (ExecutionException e) {
            log.warn(String.format("failed to get customer from cache (customerId=%s)", customerId));
            log.warn(e.getMessage());
        }

        return Optional.empty();
    }

    /**
     * Size of customer cache.
     * @return size of customer cache.
     */
    public long size() {
        return  cache.size();
    }

}

public class Customer {

   private String name;
}

public class SampleCacheTest extends TestCase {

    SampleCache SampleCache;

    SampleCacheLoader SampleCacheLoader;

    // FakeTicker to test cache expiry.
    FakeTicker ft;

    // Max size of cache
    final  int CACHE_MAX_SIZE = 1000;

    // CACHE_EXPIRE_TIME is in minutes.
    final int CACHE_EXPIRE_TIME = 1000;

    // CACHE_REFRESH_TIME is in minutes.
    final int CACHE_REFRESH_TIME = 3;

    // CACHE_CONCURRENCY_LEVEL.
    final int CACHE_CONCURRENCY_LEVEL = 10;

    // Resource arn
    final Static String CUSTOMER_ID =
            "abc";

    @Before
    public void setUp() throws Exception {
        // FaceTicker provided by google source code.
        ft = new FakeTicker();
        SampleCacheLoader sampleCacheLoader = new sampleCacheLoader(new DatabaseClient());
        SampleCache = new SampleCache(sampleCacheLoader,
                CACHE_MAX_SIZE,
                CACHE_EXPIRE_TIME,
                CACHE_REFRESH_TIME,
                CACHE_CONCURRENCY_LEVEL,
                ft);
    }

    @Test
    public void testCacheRefreshTime() throws Exception {

        Optional<Customer> customer1 = SampleCache.get(CUSTOMER_ID);
        assertTrue(customer1.isPresent());
        assertNotNull(customer1.get());

        // Advancing time by 1 minutes and retrieve it from cache
        // So that it won't expire. Gets the old entry.
        advanceTimeForCache(1);

        Optional<Customer> customer2 = SampleCache.get(CUSTOMER_ID);
        assertTrue(customer2.isPresent());
        assertNotNull(customer2.get());

        // After this any get call for CUSTOMER_ID
        // should initiate the refresh and Load.
        // new value from db.
        advanceTimeForCache(4);

        // This is the place where I get CacheInvalidateStateException
        Optional<Customer> customer3 = SampleCache.get(CUSTOMER_ID);


    }
}

user3671657
  • 37
  • 1
  • 8

1 Answers1

0

So it was @ mock at SimpleCacheLoader in test file which was not doing anything on refresh as I didn't stub the method. I should have used @ spy in this case. I got it fixed. Thanks for help everyone.

user3671657
  • 37
  • 1
  • 8