question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RemovalListener is called not each time when an entry was evicted.

See original GitHub issue

I have a RemovalListener attached to LoadingCache. When the cache is accessed frequently, everything works good. If there is no activity with the cache since entry write till entry expiration, then expired entity is “silently” evicted and removal listener is not called.

Tried with expireAfterWrite == refreshAfterWrite and expireAfterWrite > refreshAfterWrite.

Not sure whether it’s a bug or designed behaviour caused by combination of config params. Anyway, I cannot find the solution for the problem in the documentation.

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:23 (12 by maintainers)

github_iconTop GitHub Comments

2reactions
Favorlockcommented, Jul 18, 2015

I’m also experiencing the same issue. Forcing cleanup appears to work, however, this isn’t a desirable workaround. My entries are supposed to expire after a short period of time, but they do not. The removal listener never gets invoked and thus throws off the functionality of my application. This is of course when I do not force cleanup.

1reaction
ben-manescommented, Aug 3, 2019

We’ve implemented active expiration for the next version of Caffeine, which provides closer to your ask of immediate expiration. The default remains to be passive and to await cache operations to trigger a maintenance cycle, which detects expired entries and removes them. That means if there is no usage of the cache then the expired entries can remain for a long period. We still consider this the appropriate default and conforms to the typical behavior of what a cache offers.

Active expiration relies on an external scheduling thread to trigger the maintenance routine based on the next expiration event. This schedules only the next to expire entry, not all entries, so it scales independent of the cache’s size. It is best effort and pacing is induced to avoid thrashing (e.g. expireAfterAccess resets the timestamp on each read - you don’t want nanosecond offsets causing reschedulings).

In Guava/Caffeine, we take special care to ensure all of our operations are implemented in amortized O(1) worst case time, as our caches are used for tiny and humongous sizes. We also design around the semantics of a cache, which allows certain relaxations, and cases where they are not a fit deserve their own, custom solutions outside of our caches.

Java 9+ users can avoid adding their own dedicated scheduling thread by leveraging a system-wide JVM scheduler, hidden within CompletableFuture for its orTimeout(duration) option (accessed via CompletableFuture.delayedExecutor).

Cache<Integer, Integer> cache = Caffeine.newBuilder()
    .expireAfterWrite(3, TimeUnit.SECONDS)
    .scheduler(Scheduler.systemScheduler())
    .removalListener((key, value, cause) -> {
      System.out.printf("key=%s, value=%s, cause=%s%n", key, value, cause);
    }).build();
cache.put(1, 2);
TimeUnit.SECONDS.sleep(5);

// outputs:
// key=1, value=2, cause=EXPIRED

This functionality works with fixed expiration policies (expireAfterWrite, expireAfterAccess) and the per-entry expiration policy (expireAfter( /* per-entry custom calculation */)). Since the Scheduler is based on when a known event fires, the scheduler does not change the semantics of other policies like reference caching. For example, weakValues() lets the GC collect the value and the entry is eligible for eviction. However the cache is unaware, as this is done passively, and can only discover it when the ReferenceQueue is polled during the maintenance routine. We do not provide a hook to have a dedicated thread block on ReferenceQueue.remove for immediate notification. That can be easily achieved in user code via Java 9’s Cleaner, e.g. cleaner.register(value, cache::cleanUp) in the weakValues example.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Guava CacheBuilder Cache RemovalListener onRemoval ...
It doesn't get called the second time an entry is removed. (The actual removal happens. Just the onRemoval method of the removalListener doesn't ......
Read more >
Inserting back into cache in RemovalListener.onRemoval
Hello, I have a use case where some of the keys in my cache needs different expireAfterWrite timeouts. So what I'm doing is...
Read more >
How to use eviction, listeners, and statistics in Guava Cache
Evict the entry after the specified amount of time has passed since the entry was created or updated. TimeUnit in Java provides time...
Read more >
Caffeine (caffeine 2.6.0 API) - javadoc.io
Entries are automatically evicted from the cache when any of maximumSize, ... but calling it should not be necessary with a high throughput...
Read more >
CacheBuilder (Guava: Google Core Libraries for Java 22.0 API)
Entries are automatically evicted from the cache when any of maximumSize, ... but calling it should not be necessary with a high throughput...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found