Skip to content

Commit

Permalink
Add a TTL for the file vote cache.
Browse files Browse the repository at this point in the history
Adds a locally trusted TTL to each vote locally, not using untrusted
remote timestamp. Add day-based ttl to FileVoteCache.

Fixes #15
  • Loading branch information
Ichbinjoe committed Nov 1, 2017
1 parent 359b616 commit d8c2e07
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 14 deletions.
Expand Up @@ -221,7 +221,7 @@ protected void initChannel(NioSocketChannel channel) throws Exception {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
serverChannel = future.channel();
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
getLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
} else {
SocketAddress socketAddress = future.channel().localAddress();
if (socketAddress == null) {
Expand Down Expand Up @@ -255,8 +255,10 @@ public void operationComplete(ChannelFuture future) throws Exception {
getLogger().info("Using in-memory cache for votes that are not able to be delivered.");
} else if ("file".equals(cacheMethod)) {
try {
voteCache = new FileVoteCache(ProxyServer.getInstance().getServers().size(), this, new File(getDataFolder(),
fwdCfg.getString("pluginMessaging.file.name")));
voteCache = new FileVoteCache(
ProxyServer.getInstance().getServers().size(), this,
new File(getDataFolder(), fwdCfg.getString("pluginMessaging.file.name")),
fwdCfg.getInt("pluginMessaging.file.cacheTime", -1));
} catch (IOException e) {
getLogger().log(Level.SEVERE, "Unload to load file cache. Votes will be lost!", e);
}
Expand Down Expand Up @@ -294,7 +296,7 @@ public void operationComplete(ChannelFuture future) throws Exception {
}
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
new InetSocketAddress(address, section.getShort("port")),
KeyCreator.createKeyFrom(section.getString("token",section.getString("key"))));
KeyCreator.createKeyFrom(section.getString("token", section.getString("key"))));
serverList.add(server);
}

Expand Down
Expand Up @@ -14,15 +14,21 @@
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class FileVoteCache extends MemoryVoteCache {

private final Logger l;
private final File cacheFile;
private final int voteTTL;
private final ScheduledTask saveTask;

public FileVoteCache(int initialMemorySize, final Plugin plugin, File cacheFile) throws IOException {
public FileVoteCache(int initialMemorySize, final Plugin plugin, File cacheFile, int voteTTL) throws IOException {
super(initialMemorySize);
this.cacheFile = cacheFile;
this.voteTTL = voteTTL;
this.l = plugin.getLogger();

load();

saveTask = ProxyServer.getInstance().getScheduler().schedule(plugin, new Runnable() {
Expand All @@ -31,7 +37,7 @@ public void run() {
try {
save();
} catch (IOException e) {
plugin.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost if you restart.", e);
l.log(Level.SEVERE, "Unable to save cached votes, votes will be lost if you restart.", e);
}
}
}, 3, 3, TimeUnit.MINUTES);
Expand All @@ -52,22 +58,43 @@ private void load() throws IOException {
List<Vote> votes = new ArrayList<>(voteArray.length());
for (int i = 0; i < voteArray.length(); i++) {
JSONObject voteObject = voteArray.getJSONObject(i);
votes.add(new Vote(voteObject));
Vote v = new Vote(voteObject);
if (hasTimedOut(v))
l.log(Level.WARNING, "Purging out of date vote.", v);
else
votes.add(v);
}
voteCache.put(((String) server), votes);
}

}

public void save() throws IOException {
cacheLock.lock();
JSONObject votesObject = new JSONObject();
try {
// Create a copy of the votes.
for (Map.Entry<String, Collection<Vote>> entry : voteCache.entrySet()) {
Iterator<Map.Entry<String, Collection<Vote>>> entryItr = voteCache.entrySet().iterator();
while (entryItr.hasNext()) {
Map.Entry<String, Collection<Vote>> entry = entryItr.next();
JSONArray array = new JSONArray();
for (Vote vote : entry.getValue()) {
array.put(vote.serialize());
Iterator<Vote> voteItr = entry.getValue().iterator();
while (voteItr.hasNext()) {
Vote vote = voteItr.next();

// if the vote is no longer valid, notify and remove
if (hasTimedOut(vote)) {
l.log(Level.WARNING, "Purging out of date vote.", vote);
voteItr.remove();
} else {
array.put(vote.serialize());
}

}

// if, during our iteration, we TTL invalidated all of the votes
if (entry.getValue().isEmpty())
entryItr.remove();

votesObject.put(entry.getKey(), array);
}
} finally {
Expand All @@ -79,6 +106,12 @@ public void save() throws IOException {
}
}

private boolean hasTimedOut(Vote v) {
if (voteTTL == -1) return false;
// scale voteTTL to milliseconds
return v.getLocalTimestamp() + voteTTL * 24 * 60 * 60 * 1000 < System.currentTimeMillis();
}

public void halt() throws IOException {
saveTask.cancel();
save();
Expand Down
2 changes: 2 additions & 0 deletions bungeecord/src/main/resources/bungeeConfig.yml
Expand Up @@ -53,6 +53,8 @@ forwarding:
# Options for file caching.
file:
name: cached-votes.json
# days before a vote is considered 'dead' - removed from cache with a console warning
voteTTL: 10
# Specify servers to proxy votes for.
proxy:
Hub:
Expand Down
32 changes: 29 additions & 3 deletions common/src/main/java/com/vexsoftware/votifier/model/Vote.java
Expand Up @@ -47,15 +47,26 @@ public class Vote {
*/
private String timeStamp;

/**
* Timestamp (unix-millis) normalized and taken from a known source
*/
private final long localTimestamp;

@Deprecated
public Vote() {
localTimestamp = System.currentTimeMillis();
}

public Vote(String serviceName, String username, String address, String timeStamp) {
this(serviceName, username, address, timeStamp, System.currentTimeMillis());
}

public Vote(String serviceName, String username, String address, String timeStamp, long localTimestamp) {
this.serviceName = serviceName;
this.username = username;
this.address = address;
this.timeStamp = timeStamp;
this.localTimestamp = localTimestamp;
}

private static String getTimestamp(JSONObject object) {
Expand All @@ -67,13 +78,19 @@ private static String getTimestamp(JSONObject object) {
}

public Vote(JSONObject jsonObject) {
this(jsonObject.getString("serviceName"), jsonObject.getString("username"), jsonObject.getString("address"), getTimestamp(jsonObject));
this(jsonObject.getString("serviceName"),
jsonObject.getString("username"),
jsonObject.getString("address"),
getTimestamp(jsonObject),
// maintained for backwards compatibility with <2.3.6 peers
(jsonObject.has("localTimestamp") ? jsonObject.getLong("localTimestamp") : System.currentTimeMillis()));
}

@Override
public String toString() {
return "Vote (from:" + serviceName + " username:" + username
+ " address:" + address + " timeStamp:" + timeStamp + ")";
+ " address:" + address + " timeStamp:" + timeStamp
+ " localTimestamp:" + localTimestamp + ")";
}

/**
Expand Down Expand Up @@ -152,12 +169,22 @@ public String getTimeStamp() {
return timeStamp;
}

/**
* Gets the local timestamp, in unix-millis. Calculated locally by a NuVotifier instance
*
* @return The local timestamp
*/
public long getLocalTimestamp() {
return localTimestamp;
}

public JSONObject serialize() {
JSONObject ret = new JSONObject();
ret.put("serviceName", serviceName);
ret.put("username", username);
ret.put("address", address);
ret.put("timestamp", timeStamp);
ret.put("localTimestamp", localTimestamp);
return ret;
}

Expand All @@ -172,7 +199,6 @@ public boolean equals(Object o) {
if (!username.equals(vote.username)) return false;
if (!address.equals(vote.address)) return false;
return timeStamp.equals(vote.timeStamp);

}

@Override
Expand Down

0 comments on commit d8c2e07

Please sign in to comment.