Code Monkey home page Code Monkey logo

expiringmap's People

Contributors

bratkartoffel avatar codingfabian avatar d-edery avatar hansenc avatar ivannikolchov avatar jhalterman avatar lapo-luchini avatar r0b0ji avatar sirwellington avatar sullis avatar tbw777 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

expiringmap's Issues

Incorrect usage of System.nanoTime()

ExpiringMap compares nanosecond times as time <= now. Both sides are from direct reads of System.nanoTime(). Unfortunately this violates the contract of that method,

* The value returned represents nanoseconds since some fixed but
* arbitrary <i>origin</i> time (perhaps in the future, so values
* may be negative). 
* ...
* <p>The values returned by this method become meaningful only when
* the difference between two such values, obtained within the same
* instance of a Java virtual machine, is computed.
* ...
* one should use {@code t1 - t0 < 0}, not {@code t1 < t0},
* because of the possibility of numerical overflow.

This behavior is not intuitive and causes these simple oversights. However, failing to abide by that directive can lead to incorrect conclusions.

Intermittent test failures

As I was working on #17, I noticed that some tests were failing intermittently. To see if it was caused by the code I added, I checked out the current HEAD on master (78d8f8d) and ran for i in {1..100}; do mvn test; done . Out of 100 executions, I got 9 failures as follows.

7 of these:

testExpectedExpiration(net.jodah.expiringmap.ExpiringMapTest)  Time elapsed: 0.059 sec  <<< FAILURE!
java.lang.AssertionError: expected [true] but found [false]
    at org.testng.Assert.fail(Assert.java:94)
    at org.testng.Assert.failNotEquals(Assert.java:494)
    at org.testng.Assert.assertTrue(Assert.java:42)
    at org.testng.Assert.assertTrue(Assert.java:52)
    at net.jodah.expiringmap.ExpiringMapTest.testExpectedExpiration(ExpiringMapTest.java:589)

2 of these:

shouldNotReschedulePutsWithSameValue(net.jodah.expiringmap.ExpiringMapTest)  Time elapsed: 0.11 sec  <<< FAILURE!
java.lang.AssertionError: expected [null] but found [1]
    at org.testng.Assert.fail(Assert.java:94)
    at org.testng.Assert.failNotSame(Assert.java:490)
    at org.testng.Assert.assertNull(Assert.java:426)
    at org.testng.Assert.assertNull(Assert.java:415)
    at net.jodah.expiringmap.ExpiringMapTest.shouldNotReschedulePutsWithSameValue(ExpiringMapTest.java:96)

I'm using Java 1.8.0_25 (Oracle) and Maven 3.2.5 on Mac OS X 10.10.4. I don't have time to look into this further at the moment, but I suspect the issue may be the tests themselves rather than the code under test.

Illegal access issue

java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load [net.jodah.expiringmap.ExpiringMap$EntryTreeHashMap$ExpiringEntryIterator]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1325)
at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForClassLoading(WebappClassLoaderBase.java:1313)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1178)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1139)
at net.jodah.expiringmap.ExpiringMap$EntryTreeHashMap.valuesIterator(ExpiringMap.java:440)
at net.jodah.expiringmap.ExpiringMap$5.run(ExpiringMap.java:1292)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)

What about a ExpirationPolicy.ONLY_IF_ACCESSED?

  1. entry expires only if it has been accessed
  2. this could be useful especially in conjunciton with .maxSize to expire entries only a set time after they have been accessed but maxSize prevents the map from growing out of control
  3. in some unpredictable situations (like waiting for responses across the network), this could be useful when you need to keep some data in a map until you receive a confirmation request. Then you don't need it for much longer afterwards because your code is not depending upon external slowdown factors

Ability to expire entries on map access instead of in scheduled executor

See jdbi/jdbi#1453 for reference.

In JEE container environments like Tomcat or Wildfly, one needs to design applications for so that app-specific threads are stopped and classes may be unloaded, so that everything from the old .war can be freed when a new .war is deployed. ExpiringMap's use of static ScheduledExecutorService to expire entries, and ThreadPoolExecutor to notify expiration listeners are correctly logged as a thread leak.

In order to facilitate class unloading, it would be nice if we could configure our expiring maps to perform expiration and listener notification on the calling thread, whenever the map is accessed.

When using foreach, the keyset method loops infinitely

code
public static void main(String[] args) { ExpiringMap<String, String> map = ExpiringMap.builder() .maxSize(100) .expiration(20L, TimeUnit.MINUTES) .expirationPolicy(ExpirationPolicy.ACCESSED) .variableExpiration() .build(); map.put("1", "1"); map.put("2", "2"); map.keySet().forEach((key) -> { System.out.println(map.get(key)); }); }

and result
1 2 1 2 1 2 1 2 .....

Variable Expiration

I could not find a Map object having put method with 4 parameter passing.

Not available in maven repository

Hi,
I tried include dependency for thin in my project but i was not able to find this dependency in maven repository.

Please help me on this

Expiring Map has duplicate values

Considered that ExpiringMap is a Map, it still has duplicate values.
The client code needs to handle it.
Can we add some builder method to make it behave like HashMap with expiring values.
Similarly like TreeMap?

Google App Engine support

Hi,

I get the following error when trying to use ExpiringMap on Google App Engine.

java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "modifyThreadGroup") at java.security.AccessControlContext.checkPermission(AccessControlContext.java:382) at java.security.AccessController.checkPermission(AccessController.java:572) at java.lang.SecurityManager.checkPermission(SecurityManager.java:549) at java.lang.ThreadGroup.checkAccess(ThreadGroup.java:315) at java.lang.Thread.init(Thread.java:391) at java.lang.Thread.init(Thread.java:349) at java.lang.Thread.<init>(Thread.java:548) at net.jodah.expiringmap.internal.NamedThreadFactory.newThread(NamedThreadFactory.java:23) at java.util.concurrent.ThreadPoolExecutor$Worker.<init>(ThreadPoolExecutor.java:600) at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:943) at java.util.concurrent.ThreadPoolExecutor.ensurePrestart(ThreadPoolExecutor.java:1635) at java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:307) at java.util.concurrent.ScheduledThreadPoolExecutor.schedule(ScheduledThreadPoolExecutor.java:526) at java.util.concurrent.Executors$DelegatedScheduledExecutorService.schedule(Executors.java:689) at net.jodah.expiringmap.ExpiringMap.scheduleEntry(ExpiringMap.java:1190)

ConcurrentModificationException :: Iterating through map while expirator is running

Hello,

For my use case of expiring map I see a ConcurrentModification exception when using an Expirator with policy CREATED. The issue occurs when an iteration is made on the map and an expirator is running:

    java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:711)
at java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:744)
at java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:742)
at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap$AbstractHashIterator.getNext(ExpiringMap.java:364)
at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap$EntryIterator.getNext(ExpiringMap.java:385)
at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap$EntryIterator.next(ExpiringMap.java:387)
at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap$EntryIterator.next(ExpiringMap.java:385)
at net.jodah.expiringmap.functional.ConcurrencyTestWithExpirator.deleteWithEntrySetIterator(ConcurrencyTestWithExpirator.java:40)`

This happens with both EntrySet iterator, or values/keyset iterators.

Note: I read about issue #10 that is why I tried with EntrySet too.

Thank you!

Year 2016 at the end of the readme should be 2017 by now?

Just noticed that readme ends with

Copyright 2009-2016 Jonathan Halterman - Released under the Apache 2.0 license.

But as it is 2017 already it should be

Copyright 2009-2017 Jonathan Halterman - Released under the Apache 2.0 license.

ConcurrentModificationException when using EntryTreeHashMap's iterator

Related exception:

java.util.ConcurrentModificationException
at java.util.TreeMap$PrivateEntryIterator.nextEntry(TreeMap.java:1232)
at java.util.TreeMap$KeyIterator.next(TreeMap.java:1286)
at net.jodah.expiringmap.ExpiringMap$EntryTreeHashMap$AbstractHashIterator.getNext(ExpiringMap.java:473)
at net.jodah.expiringmap.ExpiringMap$EntryTreeHashMap$ValueIterator.next(ExpiringMap.java:497)
...

The code on which this exception is thrown does something like:

for (MyEntity myEntity : myCachedEntities.values()) {
	...
}

The expiring map is built with the following config:

private ExpiringMap<String, MyEntity> myCachedEntities = ExpiringMap.builder()
	.variableExpiration()
	.asyncExpirationListener(new ExpirationListener<String, MyEntity>() {
		@Override
		public void expired(String key, MyEntity value) {
			...
		}
	})
	.build();

I've hit this bug constantly in our testing scenarios (never in production, this is probably due to high rate of cache read/write/expiration from multiple threads).

The solution to the problem was to change the type of sortedSet in EntryTreeHashMap from TreeSet to ConcurrentSkipListSet (safe, implements the SortedSet interface)

  /** Entry TreeHashMap implementation for variable expiration ExpiringMap entries. */
  private static class EntryTreeHashMap<K, V> extends HashMap<K, ExpiringEntry<K, V>> implements EntryMap<K, V> {
    private static final long serialVersionUID = 1L;
    SortedSet<ExpiringEntry<K, V>> sortedSet = new ConcurrentSkipListSet<>();

A pull request is on its way (leaving the issue open until PR is approved & merged / other valid suggestions)

Why modify value object of a entry only if equals() returns false?

This is not a bug, but a question.

Is there a reason why you are storing the value of a entry only if equals() method returns false

https://github.com/jhalterman/expiringmap/blob/master/src/main/java/net/jodah/expiringmap/ExpiringMap.java#L1106

This created some problem for me when i attempted to integrate it with https://github.com/spring-projects/spring-session

I am using your library, so that the session are automatically removed from a map, after the session timeout happens.

Map<String,ExpiringSession> sessions = ExpiringMap.builder()
                  .expirationPolicy(ExpirationPolicy.ACCESSED)
                  .expiration(maxInactiveIntervalInSeconds, TimeUnit.SECONDS)
                  .expirationListener(new ExpirationListener<String, ExpiringSession>() {
                    @Override
                    public void expired(String key, ExpiringSession value) {
                        logger.error("Session {} expired. So removing it from memory", key);
                    }
                })
                  .build(); 

In short, spring adds MapSession[1] data to the map, after every request is completed. If you see the implementation of MapSession, the equals method is based on the session id.

Now when i use your map with spring session, since the equal method says the value object has not changed, you dont modify anything and as a result of it, the next request for getSession() does not return a most up-to-date session object.

If i replace your map with JDK's ConcurrentHashMap, thing work fine since the JDK implementation does not have a equals check.

Based on your response, i will submit a pull request to enable/disable this behavior using the builder.

[1] https://github.com/spring-projects/spring-session/blob/master/spring-session/src/main/java/org/springframework/session/MapSession.java

Builder constructor has private access

This is something that really bugs me. Why abstract the Builder constructor to a function, which in turn, provides no way of defining generic types? This leads to problems when EntryLoader needs to know type parameters - this code will fail to compile:

Map<String, Object> objectMap = ExpiringMap.builder() // This is Builder<Object, Object>
        .entryLoader(new EntryLoader<String, Object>() { ... }) // This is EntryLoader<String, Object>
        .build();

As stated, the simplest fix is to grant access to the builder's constructor, which would allow us to supply generic types. If there is a way around this problem, please let me know.

Ability to Test Expiry

At the moment we cannot test expiry in Unit Tests without blocking. In Guava, you can pass in a custom ticker to control time, it would be nice to have something similar for the ExpiringMap

maven central?

Nice work.
Is it available in any public maven repository?

listner not acting as expected

I am trying to use listeners , I have very simple code,
map = ExpiringMap.builder() .variableExpiration() .build(); TokenGen t1= new T1("T1","m1","m2"); map.put("T1",t1, ExpirationPolicy.CREATED, 20, TimeUnit.SECONDS); TokenGen t2= new T2("T2","m1","m2","m3"); map.put("T2",t2, ExpirationPolicy.CREATED, 20, TimeUnit.SECONDS); ExpirationListener<String, TokenGen> myListner = (key, val) -> { try { System.out.println("******* this is refresh for "+key); val.reload(); map.put(key,val) map.setExpiration(key, 20, TimeUnit.SECONDS); map.setExpirationPolicy(key, ExpirationPolicy.CREATED); } catch (Exception e) { e.printStackTrace(); } }; map.addExpirationListener(myListner); }

output:
[T1, T2]
Value of T1>>m1m258
Value of T2>>m2m1m358
******* this is refresh for T1
******* this is refresh for T2
******* this is refresh for T2

For some reason, My listeners are executing twice for t2 and once for t1 every 20 sec..Any Idea why t2 executes twice?
Do you have any documentation on listeners and how they work and manages multithreading

Lazy loaded entry `null` is still cached, for a short period

I am trying to implement a 'near-cache' for an expensive Database query using ExpiringMap. The following example is a gross simplification, but should describe the gist of it. In the real, more complex implementation the expiration time is different for each entry and there is some more logic associated with each entry which is not relevant for this issue.

ExpiringMap.builder()
	.variableExpiration()
	.expiringEntryLoader((ExpiringEntryLoader<String, DBItem>) id -> {

		DBItem obj = Database.findById(id);

		return (obj != null) ? new ExpiringValue<>(obj, 5, TimeUnit.MINUTES) : null;
	})
	.build();

This works fine for when the item is found in the database. The first time the DB is hit and the item is cached for 5 minutes, while all future calls within that duration only hit the ExpiringMap cache.

However, if the object is not found in the DB, then null is still cached. Even weirder, it seems to be cached for some short default duration that I did not define anywhere (1 minute?). Is there a way to avoid this behavior and prevent a value from being stored in the map? In case of null, I would like each subsequent get call to hit the database again.

improvement on Assert

Hi,
do you think, it's an improvement to make use of the standardized Objects class to check if a parameter/Object is null?
Example:
Objects.requireNonNull(listener, "listener cannot be null");
instead of
Assert.notNull(listener, "listener");

containsAll()

I have not had time to look closer into why this is, but it seems that containsAll() on the Collection returned by values() does not work properly.

If i exchange the expiringmap with an "org.apache.commons.collections4.map.PassiveExpiringMap", things work fine.

Variable expiration not working correctly with CREATED policy

Hi, thanks for sharing this library, it's very handy.

Can I get help with the test below? I don't understand why it's failing, it's either a bug or I misinterpreted the documentation, because both keys are expiring at the same time, even though they have different TTL:

import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class VariableExpirationInMemoryCacheTest {

    @Test
    public void shouldSupportVariableExpiration() throws InterruptedException {
        ExpiringMap<String, Object> cache = ExpiringMap.builder()
                .variableExpiration()
                .build();

        cache.put("hello", "world", ExpirationPolicy.CREATED, 1000L, TimeUnit.MILLISECONDS);
        cache.put("foo", "bar", ExpirationPolicy.CREATED, 2000L, TimeUnit.MILLISECONDS);

        // both keys are available in the first second
        assertEquals("world", cache.get("hello"));
        assertEquals("bar", cache.get("foo"));

        // after waiting 1 second, only "hello" should have expired
        Thread.sleep(1001L);
        assertNull(cache.get("hello"));
        assertEquals("foo", cache.get("bar"));   <============   this fails

        // after waiting a total of 2 seconds, both keys should have expired
        Thread.sleep(1000L);
        assertNull(cache.get("hello"));
        assertNull(cache.get("bar"));
    }
}

Thread Pool configuration

Can the thread pool configuration be exposed for Expirer and Listener thread pools?

If a single threaded expirer may not be sufficient and an unbounded thread pool an over kill for certain cases, can the caller be asked to optionally supply thread pool executor instance?

Metrics for the ExpiringMap

Hey Jonathan,

Great work with this project!
I was wondering if there is any plan to add some metrics (eg. https://github.com/dropwizard/metrics) in order to gain some insights into how our application is using the ExpiringMap. Information such as hit ratio, cache requests, number of entries cached, entry load time histograms (how long it takes for a new entry to be created), how many items expired in a given time frame would be super useful.

Cheers,
Costi

Get method of Expiration Policy.ACCESSED map is adding duplicate item to the entry set

Here is the test case to reproduce an issue:


import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

public class MapTest {

    @Test
    public void testMap() {
        ExpiringMap<Long, String> map = ExpiringMap.builder().expiration(10, TimeUnit.SECONDS)
                .expirationPolicy(ExpirationPolicy.ACCESSED).variableExpiration().build();

        map.put(1L, "1");
        map.put(2L, "2");
        map.put(3L, "3");

        System.out.println("Size: " + map.size());
        map.entrySet().forEach(en -> System.out.println(en.getKey() + "->" + en.getValue()));

        String value = map.get(1L);

        System.out.println("Size: " + map.size());
        map.entrySet().forEach(en -> System.out.println(en.getKey() + "->" + en.getValue()));

        map.remove(1L);

        System.out.println("Contains on map returned: " + map.containsKey(1L));
        System.out.println("Size: " + map.size());
        map.entrySet().forEach(en -> System.out.println(en.getKey() + "->" + en.getValue()));
    }
}

This prints the following output to the command line:

Size: 3
1->1
2->2
3->3
Size: 3
1->1
2->2
3->3
1->1
Contains on map returned: false
Size: 2
1->1
2->2
3->3

adding async expiration listener outside of builder causes problems

The behavior I'm seeing in my app is that the async expiration listener that I add after building the ExpiringMap doesn't receive its callbacks and some of the items in the map don't expire.

You can see a unit test for this on my fork that recreates the problem.

Adding in Builder and After works

ExpiringMap<String, String> map = ExpiringMap.builder()
                .expiration(100, TimeUnit.MILLISECONDS)
                .asyncExpirationListener((thekey, thevalue) -> {
                    System.out.println("builder one called");
                 })
                .build();
map.addAsyncExpirationListener((thekey, thevalue) -> {
     System.out.println("after builder called");
});

Adding only after the builder doesn't work

ExpiringMap<String, String> map = ExpiringMap.builder()
                .expiration(100, TimeUnit.MILLISECONDS)
                .build();
map.addAsyncExpirationListener((thekey, thevalue) -> {
     System.out.println("after builder called");
});

In this case, the expiration listener isn't called and I'm also seeing that some items don't expire from the map. In all cases I'm adding the listener immediately after building the map and before putting any items in it.

Note that in my branch I added priority to the TestNG methods. If I run the whole suite and your existing tests run first then everything works. If I just run my test then it fails. If I sequence the tests such that mine run first then the failure is present. I haven't dug into the ExpiringMap or its builder to see what might be sensitive to the ordering of the tests but I don't see anything shared between them the way the existing tests were written.

ConcurrentModificationException while iterating ExpiringMap with ACCESSED expiration policy

How can I iterate ExpiringMap with ACCESS expiration policy?

Here is a test to get ConcurrentModificationException:

public void test() {
    ExpiringMap<String, String> map = ExpiringMap.builder().expiration(10, TimeUnit.MINUTES)
            .expirationPolicy(ExpiringMap.ExpirationPolicy.ACCESSED).build();
    map.put("k1", "v1");
    map.put("k2", "v2");
    for (Object key : map.keySet()) {
        String val = map.get(key);
        System.out.printf("key:" + key + " val:" + val);
    }
}

The exception throws at the second iteration, first iteration doesn't throw the exception.

It's possible to itrate values by using valuesIterator() method, but how to link key to value in this case?

Java documentation gives wrong time complexity

This is my first time logging an issue. Please bare with me if I'm not following the correct process.

The Java documentation for Expiring map says:
"put/remove operations are constant O(n). When variable expiration is enabled, put/remove operations impose an O(log n) cost."

Believe that should be O(1) instead of O(log n).

Add a method to get the actual time remaining to expiration

Originally I thought (hoped) that getExpiration(key) would give me the remaining expiration time for the element, while it is returning the initial expiration time.
It would be nice to have a method to get that, too; right now I'm wrapping the value with an object that contains that, but it is a duplicate and not so trustworthy as a value.

expiringmap hanging on exit

I write a very simple test code for expireingmap.

 import net.jodah.expiringmap.ExpiringMap;
 import java.util.concurrent.TimeUnit;
 import java.util.Map;
public class App 
{
    public static void main( String[] args )
    {
        Map<String, T> map = ExpiringMap.builder()
                                      .expiration(5,TimeUnit.SECONDS)
                                      .build();
        T t1=new T(1,2);
        map.put("t1",t1);
        T ts=map.get("t1");
        ts.print();
        System.out.println( "exit!" );
    }
}
class T{
    private int v1;
    private int v2;
    public T(int v1,int v2){
        this.v1=v1;
        this.v2=v2;
    }
    void print(){
        System.out.println("v1="+v1+",v2="+v2);
    }
}

But the program can not exit rightly,just hanging there.

Is this the design of expiringmap or a bug?

Concurrent Modification Exception

Hello,

I noticed an older issue reporting ConcurrentModificationException, and found the issue is still occurring in 0.4.2. I tried keyset and entrySet.

    java.util.ConcurrentModificationException
            at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:350)
            at java.util.LinkedHashMap$EntryIterator.next(LinkedHashMap.java:379)
            at java.util.LinkedHashMap$EntryIterator.next(LinkedHashMap.java:377)
            at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap$AbstractHashIterator.getNext(ExpiringMap.java:293)
            at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap$EntryIterator.getNext(ExpiringMap.java:314)
            at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap$EntryIterator.next(ExpiringMap.java:316)
            at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap$EntryIterator.next(ExpiringMap.java:314)

Combining variable expiration with lazy loading

I'd like to be able to combine variable expiration with lazy loading. For example, creating an OAuth2 access token when needed and storing it for its lifetime (e.g. expires_in). I didn't see a way of doing this in the current API.

Let me know what your thoughts are regarding the interface for this and I'm happy to submit a PR. I was thinking something along the lines of:

ExpiringMap<String, OAuth2AccessToken > tokenMap = ExpiringMap.builder()
        .variableExpiration() //this may be redundant given the new type of loader
        .expiringEntryLoader(new ExpiringEntryLoader<String, OAuth2AccessToken>() {
            public ExpiringValue<OAuth2AccessToken> load(String key) { //key is unused in this case
              OAuth2AccessToken token = loadAccessToken();
              Duration expiresIn = token.getExpiresIn();
              return new ExpiringValue<>(token, expiresIn.getSeconds(), TimeUnit.SECONDS);
            }
        })
        .build();

setExpiration Throws a nullpointer if you attempt to set the expiration for a non-existing entry

setExpiration Throws a nullpointer if you attempt to set the expiration for a non-existing entry.

If the map doesn't have an entry which is mapped to the given key entries.get(key) will return null and then it will attempt to set the expiration time on a null object.

https://github.com/jhalterman/expiringmap/blob/master/src/main/java/net/jodah/expiringmap/ExpiringMap.java#L1068

Just had this problem because I was using the wrong map but I am assuming it would happen if your entry expired before or as you try to setExpiration for that expired entry.

Failures in Guava's MapTestSuiteBuilder suite

It appears that there are some small coverage mistakes that break the Map contract. Guava's testlib provides a nice way to perform a deep validation in a pluggable manner. In the following configuration there are 7 errors and 40 failures (many are duplicates at different population counts).

For example, testPutAll_nullKeyUnsupported fails due to putAll delegating to putInternal which does not perform a null key check. Instead it could delegate to any of the put(...) methods which includes it.

public final class ExpiringMapTest {

  public static Test suite() throws Exception {
    TestSuite suite = new TestSuite();
    suite.addTest(ExpiringMapTest.suite("ExpiringMap", () -> {
      return ExpiringMap.builder().build();
    }));
    return suite;
  }
  
  private static Test suite(String name, Supplier<Map<String, String>> supplier) {
    return MapTestSuiteBuilder
        .using(new TestStringMapGenerator() {
          @Override protected Map<String, String> create(Entry<String, String>[] entries) {
            Map<String, String> map = supplier.get();
            for (Entry<String, String> entry : entries) {
              map.put(entry.getKey(), entry.getValue());
            }
            return map;
          }
        })
        .named(name)
        .withFeatures(
            MapFeature.GENERAL_PURPOSE,
            MapFeature.ALLOWS_NULL_ENTRY_QUERIES,
            MapFeature.ALLOWS_NULL_VALUES,
            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
            CollectionSize.ANY)
        .createTestSuite();
  }
}

Implementing loaders for values?

I'm a fan of Guava's LoadingCache but it misses exactly what this implementation offers: variable entry expiry.

But... This project lacks one thing LoadingCache has, and that is loaders for values (depending on the key).

It is planned in the future to add this functionality? Ideally, with a checked and unchecked get method? (well, unchecked there is already and that's .get())

OSGi bundle?

Hi,
I would like to use this neat library as an OSGi bundle. Would you be willing to accept a PR changing the build to produce one? (it actually only adds manifest headers)

ExpiringMap:remove(Object key) throws java.util.NoSuchElementException

Under heavy load with multiple concurrent threads performing add/remove this error is thrown:

java.util.NoSuchElementException
at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:396)
at java.util.LinkedHashMap$ValueIterator.next(LinkedHashMap.java:409)
at net.jodah.expiringmap.ExpiringMap$EntryLinkedHashMap.first(ExpiringMap.java:266)
at net.jodah.expiringmap.ExpiringMap.remove(ExpiringMap.java:838)

The reason for it: the WriteLock is released to early

public V remove(Object key) {
    Assert.notNull(key, "key");
    ExpiringEntry<K, V> entry = null;
    writeLock.lock();
    try {
      entry = entries.remove(key);
    } finally {//This finally block should be moved to the end of the method
      writeLock.unlock();
    }
    if (entry == null)
      return null;
    if (entry.cancel(false))
      scheduleEntry(entries.first());
    return entry.getValue();
  }

BTW the method public boolean remove(Object key, Object value) is correctly implemented.

Variable Expiration Map expires more than entries count in the Map

I used ExpiringMap with variableExpiration.

ExpiringMap<Long, Transaction> expiringMap = ExpiringMap.builder()
			.expirationPolicy(ExpirationPolicy.CREATED)
			.variableExpiration()
			.asyncExpirationListener((key, transaction) -> removeTransaction((Transaction) transaction))
			.build();

When an entry expires, removeTransaction method will be called. In this case, I put many entries with same duration (They will expire in the same time).

expiringMap.put(transactionTimestamp, transaction, ExpirationPolicy.CREATED, duration, TimeUnit.MILLISECONDS);

The problem is, removeTransaction method is called more than entries count in the expiringMap.

Maven

Hi Jonathan,

Thanks for well documented and well written small library.
Do you have plan to publish expiringmap to the central maven repo?

size

Is there a size limit or just time?
I need a combo that will take out the oldest when over.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.