package org.infinispan.api;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
import java.util.Collections;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.util.DefaultTimeService;
import org.infinispan.util.TimeService;
import org.testng.annotations.Test;
@Test(groups = "functional", testName = "api.AsyncAPITest")
public class AsyncAPITest extends SingleCacheManagerTest {
private Cache<String, String> c;
private ControlledTimeService timeService = new ControlledTimeService();
private Long startTime;
@Override
protected EmbeddedCacheManager createCacheManager() throws Exception {
EmbeddedCacheManager cm = TestCacheManagerFactory.createCacheManager(false);
TestingUtil.replaceComponent(cm, TimeService.class, timeService, true);
c = cm.getCache();
return cm;
}
public void testAsyncMethods() throws Exception {
// get
Future<String> f = c.getAsync("k");
assert f != null;
assert !f.isCancelled();
assertNull(f.get());
assert f.isDone();
assert c.get("k") == null;
// put
f = c.putAsync("k", "v");
assert f != null;
assert !f.isCancelled();
assertEquals(f.get(), null);
assert f.isDone();
assert c.get("k").equals("v");
f = c.putAsync("k", "v2");
assert f != null;
assert !f.isCancelled();
assert f.get().equals("v");
assert f.isDone();
assert c.get("k").equals("v2");
// putAll
Future<Void> f2 = c.putAllAsync(Collections.singletonMap("k", "v3"));
assert f2 != null;
assert !f2.isCancelled();
assert f2.get() == null;
assert f2.isDone();
assert c.get("k").equals("v3");
// putIfAbsent
f = c.putIfAbsentAsync("k", "v4");
assert f != null;
assert !f.isCancelled();
assert f.get().equals("v3");
assert f.isDone();
assert c.get("k").equals("v3");
// remove
f = c.removeAsync("k");
assert f != null;
assert !f.isCancelled();
assert f.get().equals("v3");
assert f.isDone();
assert c.get("k") == null;
// putIfAbsent again
f = c.putIfAbsentAsync("k", "v4");
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
assert c.get("k").equals("v4");
// get
f = c.getAsync("k");
assert f != null;
assert !f.isCancelled();
assert f.get().equals("v4");
assert f.isDone();
assert c.get("k").equals("v4");
// removecond
Future<Boolean> f3 = c.removeAsync("k", "v_nonexistent");
assert f3 != null;
assert !f3.isCancelled();
assert f3.get().equals(false);
assert f3.isDone();
assert c.get("k").equals("v4");
f3 = c.removeAsync("k", "v4");
assert f3 != null;
assert !f3.isCancelled();
assert f3.get().equals(true);
assert f3.isDone();
assert c.get("k") == null;
// replace
f = c.replaceAsync("k", "v5");
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
assert c.get("k") == null;
c.put("k", "v");
f = c.replaceAsync("k", "v5");
assert f != null;
assert !f.isCancelled();
assert f.get().equals("v");
assert f.isDone();
assert c.get("k").equals("v5");
//replace2
f3 = c.replaceAsync("k", "v_nonexistent", "v6");
assert f3 != null;
assert !f3.isCancelled();
assert f3.get().equals(false);
assert f3.isDone();
assert c.get("k").equals("v5");
f3 = c.replaceAsync("k", "v5", "v6");
assert f3 != null;
assert !f3.isCancelled();
assert f3.get().equals(true);
assert f3.isDone();
assert c.get("k").equals("v6");
}
public void testAsyncMethodWithLifespanAndMaxIdle() throws Exception {
// lifespan only
Future<String> f = c.putAsync("k", "v", 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
verifyEviction("k", "v", 1000, 500, true);
log.warn("STARTING FAILING ONE");
// lifespan and max idle (test max idle)
f = c.putAsync("k", "v", 3000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
verifyEviction("k", "v", 1000, 500, false);
// lifespan and max idle (test lifespan)
f = c.putAsync("k", "v", 3000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
verifyEviction("k", "v", 3000, 500, true);
// putAll lifespan only
Future<Void> f2 = c.putAllAsync(Collections.singletonMap("k", "v3"), 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f2 != null;
assert !f2.isCancelled();
assert f2.get() == null;
assert f2.isDone();
verifyEviction("k", "v3", 1000, 500, true);
// putAll lifespan and max idle (test max idle)
f2 = c.putAllAsync(Collections.singletonMap("k", "v4"), 3000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f2 != null;
assert !f2.isCancelled();
assert f2.get() == null;
assert f2.isDone();
verifyEviction("k", "v4", 1000, 500, false);
// putAll lifespan and max idle (test lifespan)
f2 = c.putAllAsync(Collections.singletonMap("k", "v5"), 3000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f2 != null;
assert !f2.isCancelled();
assert f2.get() == null;
assert f2.isDone();
verifyEviction("k", "v5", 3000, 500, true);
// putIfAbsent lifespan only
f = c.putAsync("k", "v3");
assertNull(f.get());
f = c.putIfAbsentAsync("k", "v4", 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assertEquals("v3", f.get());
assert f.isDone();
assert c.get("k").equals("v3");
assert !c.get("k").equals("v4");
Thread.sleep(300);
assert c.get("k").equals("v3");
f = c.removeAsync("k");
assert f.get().equals("v3");
assert c.get("k") == null;
// now really put (k removed) lifespan only
f = c.putIfAbsentAsync("k", "v", 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
verifyEviction("k", "v", 1000, 500, true);
// putIfAbsent lifespan and max idle (test max idle)
f = c.putIfAbsentAsync("k", "v", 3000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
verifyEviction("k", "v", 1000, 500, false);
// putIfAbsent lifespan and max idle (test lifespan)
f = c.putIfAbsentAsync("k", "v", 3000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
verifyEviction("k", "v", 3000, 500, true);
// replace
f = c.replaceAsync("k", "v5", 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get() == null;
assert f.isDone();
assert c.get("k") == null;
// replace lifespan only
c.put("k", "v");
f = c.replaceAsync("k", "v5", 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get().equals("v");
assert f.isDone();
verifyEviction("k", "v5", 1000, 500, true);
// replace lifespan and max idle (test max idle)
c.put("k", "v");
f = c.replaceAsync("k", "v5", 5000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get().equals("v");
assert f.isDone();
verifyEviction("k", "v5", 1000, 500, false);
// replace lifespan and max idle (test lifespan)
c.put("k", "v");
f = c.replaceAsync("k", "v5", 3000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f != null;
assert !f.isCancelled();
assert f.get().equals("v");
assert f.isDone();
verifyEviction("k", "v5", 3000, 500, true);
//replace2
c.put("k", "v5");
Future<Boolean> f3 = c.replaceAsync("k", "v_nonexistent", "v6", 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f3 != null;
assert !f3.isCancelled();
assert f3.get().equals(false);
assert f3.isDone();
Thread.sleep(300);
assert c.get("k").equals("v5");
// replace2 lifespan only
f3 = c.replaceAsync("k", "v5", "v6", 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f3 != null;
assert !f3.isCancelled();
assert f3.get().equals(true);
assert f3.isDone();
verifyEviction("k", "v6", 1000, 500, true);
// replace2 lifespan and max idle (test max idle)
c.put("k", "v5");
f3 = c.replaceAsync("k", "v5", "v6", 5000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f3 != null;
assert !f3.isCancelled();
assert f3.get().equals(true);
assert f3.isDone();
verifyEviction("k", "v6", 1000, 500, false);
// replace2 lifespan and max idle (test lifespan)
c.put("k", "v5");
f3 = c.replaceAsync("k", "v5", "v6", 3000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS);
markStartTime();
assert f3 != null;
assert !f3.isCancelled();
assert f3.get().equals(true);
assert f3.isDone();
verifyEviction("k", "v6", 3000, 500, true);
}
private void markStartTime() {
startTime = timeService.wallClockTime();
}
/**
* Verifies if a key is evicted after a certain time.
* @param key the key to check
* @param expectedValue expected key value at the beginning
* @param expectedLifetime expected life of the key
* @param checkPeriod period between executing checks. If the check modifies the idle time. this is important to block idle expiration.
* @param touchKey indicates if the poll for key existence should read the key and cause idle time to be reset
*/
private void verifyEviction(final String key, final String expectedValue, final long expectedLifetime, long checkPeriod, final boolean touchKey) {
if (startTime == null) {
throw new IllegalStateException("markStartTime() must be called before verifyEviction(..)");
}
try {
long expectedEndTime = startTime + expectedLifetime;
Condition condition = () -> {
if (touchKey) {
return !c.containsKey(key); //this check DOES read the key so it resets the idle time
} else {
//this check DOES NOT read the key so it does not interfere with idle time
InternalCacheEntry entry = c.getAdvancedCache().getDataContainer().peek(key);
return entry == null || entry.isExpired(timeService.wallClockTime());
}
};
assertTrue(expectedValue.equals(c.get(key)) || timeService.wallClockTime() > expectedEndTime);
// we need to loop to keep touching the entry and protect against idle expiration
while (timeService.wallClockTime() <= expectedEndTime) {
assertFalse("Entry evicted too soon!", condition.isSatisfied());
timeService.advance(checkPeriod);
}
assertTrue(timeService.wallClockTime() > expectedEndTime);
assertTrue(condition.isSatisfied());
Object value = c.get(key);
assertNull(value);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
startTime = null;
}
}
private static class ControlledTimeService extends DefaultTimeService {
private long time = super.wallClockTime();
@Override
public long wallClockTime() {
return time;
}
@Override
public long time() {
return TimeUnit.MILLISECONDS.toNanos(time);
}
public void advance(long millis) {
time += millis;
}
}
}