Added basic expiry to cache and tests.

This commit is contained in:
Tux 2015-06-23 23:17:50 -04:00
parent 982644d31c
commit 95a2a33781
5 changed files with 256 additions and 16 deletions

View File

@ -26,8 +26,6 @@
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.net.InetAddresses;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@ -50,6 +48,7 @@ import java.net.InetAddress;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
/**
@ -57,18 +56,34 @@ import java.util.logging.Level;
*
* @since 0.3.3
*/
@RequiredArgsConstructor
public class DataManager implements Listener {
private final RedisBungee plugin;
// TODO: Add cleanup for this.
private final InternalCache<UUID, String> serverCache = createCache();
private final InternalCache<UUID, String> proxyCache = createCache();
private final InternalCache<UUID, InetAddress> ipCache = createCache();
private final InternalCache<UUID, Long> lastOnlineCache = createCache();
private final InternalCache<UUID, String> proxyCache = createCache(TimeUnit.MINUTES.toMillis(60));
private final InternalCache<UUID, InetAddress> ipCache = createCache(TimeUnit.MINUTES.toMillis(60));
private final InternalCache<UUID, Long> lastOnlineCache = createCache(TimeUnit.MINUTES.toMillis(60));
public DataManager(RedisBungee plugin) {
this.plugin = plugin;
plugin.getProxy().getScheduler().schedule(plugin, new Runnable() {
@Override
public void run() {
proxyCache.cleanup();
ipCache.cleanup();
lastOnlineCache.cleanup();
}
}, 1, 1, TimeUnit.MINUTES);
}
public static <K, V> InternalCache<K, V> createCache() {
return new InternalCache<>();
}
public static <K, V> InternalCache<K, V> createCache(long entryWriteExpiry) {
return new InternalCache<>(entryWriteExpiry);
}
private final JsonParser parser = new JsonParser();
public String getServer(final UUID uuid) {

View File

@ -26,10 +26,12 @@
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.annotations.VisibleForTesting;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
class RedisUtil {
@VisibleForTesting
public class RedisUtil {
// Compatibility restraints prevent me from using using HDEL with multiple keys.
public static void cleanUpPlayer(String player, Jedis rsc) {
rsc.srem("proxy:" + RedisBungee.getApi().getServerId() + ":usersOnline", player);

View File

@ -26,39 +26,78 @@
*/
package com.imaginarycode.minecraft.redisbungee.util;
import lombok.Data;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
// I would use the Guava, but can't because I need a few more properties.
// I would use the Guava cache, but can't because I need a few more properties.
public class InternalCache<K, V> {
private final ConcurrentMap<K, V> map = new ConcurrentHashMap<>(128, 0.75f, 4);
private final ConcurrentMap<K, Holder> map = new ConcurrentHashMap<>(128, 0.75f, 4);
private final long entryWriteExpiry;
public InternalCache() {
this.entryWriteExpiry = 0;
}
public InternalCache(long entryWriteExpiry) {
this.entryWriteExpiry = entryWriteExpiry;
}
public V get(K key, Callable<V> loader) throws ExecutionException {
V value = map.get(key);
Holder value = map.get(key);
if (value == null || (entryWriteExpiry > 0 && System.currentTimeMillis() > value.expiry)) {
V freshValue;
if (value == null) {
try {
value = loader.call();
freshValue = loader.call();
} catch (Exception e) {
throw new ExecutionException(e);
}
if (value == null)
if (freshValue == null)
return null;
map.putIfAbsent(key, value);
map.putIfAbsent(key, value = new Holder(freshValue, System.currentTimeMillis() + entryWriteExpiry));
}
return value;
return value.value;
}
public V put(K key, V value) {
return map.put(key, value);
Holder holder = map.put(key, new Holder(value, System.currentTimeMillis() + entryWriteExpiry));
if (holder == null)
return null;
return holder.value;
}
public void invalidate(K key) {
map.remove(key);
}
// Run periodically to clean up the cache mappings.
public void cleanup() {
if (entryWriteExpiry <= 0)
return;
long fixedReference = System.currentTimeMillis();
for (Iterator<Map.Entry<K, Holder>> it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<K, Holder> entry = it.next();
if (entry.getValue().expiry > fixedReference)
it.remove();
}
}
@Data
private class Holder {
private final V value;
private final long expiry;
}
}

View File

@ -0,0 +1,141 @@
/**
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/
package com.imaginarycode.minecraft.redisbungee.test;
import com.imaginarycode.minecraft.redisbungee.util.InternalCache;
import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
public class InternalCacheTest {
@Test
public void testNonCached() {
InternalCache<String, String> cache = new InternalCache<>();
try {
Assert.assertEquals("hi", cache.get("hi", new Callable<String>() {
@Override
public String call() throws Exception {
return "hi";
}
}));
} catch (ExecutionException e) {
throw new AssertionError(e);
}
}
@Test
public void testCached() {
InternalCache<String, String> cache = new InternalCache<>();
try {
Assert.assertEquals("hi", cache.get("hi", new Callable<String>() {
@Override
public String call() throws Exception {
return "hi";
}
}));
Assert.assertEquals("hi", cache.get("hi", new Callable<String>() {
@Override
public String call() throws Exception {
Assert.fail("Cache is using loader!");
return null;
}
}));
} catch (ExecutionException e) {
throw new AssertionError(e);
}
}
@Test
public void testWriteExpiry() {
final Object one = new Object();
InternalCache<String, Object> cache = new InternalCache<>(100); // not very long
try {
// Successive calls should always work.
Assert.assertEquals(one, cache.get("hi", new Callable<Object>() {
@Override
public Object call() throws Exception {
return one;
}
}));
Assert.assertEquals(one, cache.get("hi", new Callable<Object>() {
@Override
public Object call() throws Exception {
Assert.fail("Cache is using loader!");
return null;
}
}));
// But try again in a second and a bit:
try {
Thread.sleep(150);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
final Object two = new Object();
Assert.assertEquals(two, cache.get("hi", new Callable<Object>() {
@Override
public Object call() throws Exception {
return two;
}
}));
} catch (ExecutionException e) {
throw new AssertionError(e);
}
}
@Test
public void testCleanup() {
InternalCache<String, Object> cache = new InternalCache<>(10);
final Object one = new Object();
final Object two = new Object();
try {
Assert.assertEquals(one, cache.get("hi", new Callable<Object>() {
@Override
public Object call() throws Exception {
return one;
}
}));
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
cache.cleanup();
Assert.assertEquals(two, cache.get("hi", new Callable<Object>() {
@Override
public Object call() throws Exception {
return two;
}
}));
} catch (ExecutionException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,43 @@
/**
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/
package com.imaginarycode.minecraft.redisbungee.test;
import com.imaginarycode.minecraft.redisbungee.RedisUtil;
import org.junit.Assert;
import org.junit.Test;
public class RedisUtilTest {
@Test
public void testRedisLuaCheck() {
Assert.assertTrue(RedisUtil.canUseLua("2.6.0"));
Assert.assertFalse(RedisUtil.canUseLua("2.2.12"));
Assert.assertFalse(RedisUtil.canUseLua("1.2.4"));
Assert.assertTrue(RedisUtil.canUseLua("2.8.4"));
Assert.assertTrue(RedisUtil.canUseLua("3.0.0"));
Assert.assertTrue(RedisUtil.canUseLua("3.2.1"));
}
}