GOSSIP-25 Create reaper process to expire per-node data
This commit is contained in:
@ -30,13 +30,12 @@ import org.apache.log4j.Logger;
|
|||||||
/**
|
/**
|
||||||
* This object represents the service which is responsible for gossiping with other gossip members.
|
* This object represents the service which is responsible for gossiping with other gossip members.
|
||||||
*
|
*
|
||||||
* @author joshclemm, harmenw
|
|
||||||
*/
|
*/
|
||||||
public class GossipService {
|
public class GossipService {
|
||||||
|
|
||||||
public static final Logger LOGGER = Logger.getLogger(GossipService.class);
|
public static final Logger LOGGER = Logger.getLogger(GossipService.class);
|
||||||
|
|
||||||
private GossipManager gossipManager;
|
private final GossipManager gossipManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor with the default settings.
|
* Constructor with the default settings.
|
||||||
@ -71,7 +70,7 @@ public class GossipService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
LOGGER.debug("Starting: " + get_gossipManager().getMyself().getUri());
|
LOGGER.debug("Starting: " + getGossipManager().getMyself().getUri());
|
||||||
gossipManager.init();
|
gossipManager.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,25 +78,26 @@ public class GossipService {
|
|||||||
gossipManager.shutdown();
|
gossipManager.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GossipManager get_gossipManager() {
|
public GossipManager getGossipManager() {
|
||||||
return gossipManager;
|
return gossipManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gossip data to the entire cluster
|
* Gossip data in a namespace that is per-node { node-id { key->value } }
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public void gossipData(GossipDataMessage message){
|
public void gossipPerNodeData(GossipDataMessage message){
|
||||||
gossipManager.gossipData(message);
|
gossipManager.gossipPerNodeData(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
public GossipDataMessage findGossipData(String nodeId, String key){
|
* Retrieve per-node gossip data by key
|
||||||
return this.get_gossipManager().findGossipData(nodeId, key);
|
* @param nodeId
|
||||||
}
|
* @param key
|
||||||
|
* @return return the value if found or null if not found or expired
|
||||||
public void set_gossipManager(GossipManager _gossipManager) {
|
*/
|
||||||
this.gossipManager = _gossipManager;
|
public GossipDataMessage findPerNodeData(String nodeId, String key){
|
||||||
|
return getGossipManager().findGossipData(nodeId, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
8
src/main/java/org/apache/gossip/manager/Clock.java
Normal file
8
src/main/java/org/apache/gossip/manager/Clock.java
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package org.apache.gossip.manager;
|
||||||
|
|
||||||
|
public interface Clock {
|
||||||
|
|
||||||
|
long currentTimeMillis();
|
||||||
|
long nanoTime();
|
||||||
|
|
||||||
|
}
|
58
src/main/java/org/apache/gossip/manager/DataReaper.java
Normal file
58
src/main/java/org/apache/gossip/manager/DataReaper.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package org.apache.gossip.manager;
|
||||||
|
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.gossip.model.GossipDataMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We wish to periodically sweep user data and remove entries past their timestamp. This
|
||||||
|
* implementation periodically sweeps through the data and removes old entries. While it might make
|
||||||
|
* sense to use a more specific high performance data-structure to handle eviction, keep in mind
|
||||||
|
* that we are not looking to store a large quantity of data as we currently have to transmit this
|
||||||
|
* data cluster wide.
|
||||||
|
*/
|
||||||
|
public class DataReaper {
|
||||||
|
|
||||||
|
private final GossipCore gossipCore;
|
||||||
|
private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
public DataReaper(GossipCore gossipCore, Clock clock){
|
||||||
|
this.gossipCore = gossipCore;
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(){
|
||||||
|
Runnable reapPerNodeData = () -> {
|
||||||
|
runOnce();
|
||||||
|
};
|
||||||
|
scheduledExecutor.scheduleAtFixedRate(reapPerNodeData, 0, 5, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void runOnce(){
|
||||||
|
for (Entry<String, ConcurrentHashMap<String, GossipDataMessage>> node : gossipCore.getPerNodeData().entrySet()){
|
||||||
|
reapData(node.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reapData(ConcurrentHashMap<String, GossipDataMessage> concurrentHashMap){
|
||||||
|
for (Entry<String, GossipDataMessage> entry : concurrentHashMap.entrySet()){
|
||||||
|
if (entry.getValue().getExpireAt() < clock.currentTimeMillis()){
|
||||||
|
concurrentHashMap.remove(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(){
|
||||||
|
scheduledExecutor.shutdown();
|
||||||
|
try {
|
||||||
|
scheduledExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -47,16 +47,21 @@ public class GossipCore {
|
|||||||
perNodeData = new ConcurrentHashMap<>();
|
perNodeData = new ConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
*/
|
|
||||||
public void addPerNodeData(GossipDataMessage message){
|
public void addPerNodeData(GossipDataMessage message){
|
||||||
ConcurrentHashMap<String,GossipDataMessage> m = new ConcurrentHashMap<>();
|
ConcurrentHashMap<String,GossipDataMessage> nodeMap = new ConcurrentHashMap<>();
|
||||||
m.put(message.getKey(), message);
|
nodeMap.put(message.getKey(), message);
|
||||||
m = perNodeData.putIfAbsent(message.getNodeId(), m);
|
nodeMap = perNodeData.putIfAbsent(message.getNodeId(), nodeMap);
|
||||||
if (m != null){
|
if (nodeMap != null){
|
||||||
m.put(message.getKey(), message); //TODO only put if > ts
|
//m.put(message.getKey(), message); //TODO only put if > ts
|
||||||
|
GossipDataMessage current = nodeMap.get(message.getKey());
|
||||||
|
if (current == null){
|
||||||
|
nodeMap.replace(message.getKey(), null, message);
|
||||||
|
} else {
|
||||||
|
if (current.getTimestamp() < message.getTimestamp()){
|
||||||
|
nodeMap.replace(message.getKey(), current, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentSkipListMap;
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@ -67,14 +68,20 @@ public abstract class GossipManager implements NotificationListener {
|
|||||||
|
|
||||||
private ExecutorService gossipThreadExecutor;
|
private ExecutorService gossipThreadExecutor;
|
||||||
|
|
||||||
private GossipCore gossipCore;
|
private final GossipCore gossipCore;
|
||||||
|
|
||||||
|
private final DataReaper dataReaper;
|
||||||
|
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
public GossipManager(String cluster,
|
public GossipManager(String cluster,
|
||||||
URI uri, String id, GossipSettings settings,
|
URI uri, String id, GossipSettings settings,
|
||||||
List<GossipMember> gossipMembers, GossipListener listener) {
|
List<GossipMember> gossipMembers, GossipListener listener) {
|
||||||
|
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.gossipCore = new GossipCore(this);
|
gossipCore = new GossipCore(this);
|
||||||
|
clock = new SystemClock();
|
||||||
|
dataReaper = new DataReaper(gossipCore, clock);
|
||||||
me = new LocalGossipMember(cluster, uri, id, System.currentTimeMillis(), this,
|
me = new LocalGossipMember(cluster, uri, id, System.currentTimeMillis(), this,
|
||||||
settings.getCleanupInterval());
|
settings.getCleanupInterval());
|
||||||
members = new ConcurrentSkipListMap<>();
|
members = new ConcurrentSkipListMap<>();
|
||||||
@ -192,6 +199,7 @@ public abstract class GossipManager implements NotificationListener {
|
|||||||
gossipThreadExecutor.execute(passiveGossipThread);
|
gossipThreadExecutor.execute(passiveGossipThread);
|
||||||
activeGossipThread = new ActiveGossipThread(this, this.gossipCore);
|
activeGossipThread = new ActiveGossipThread(this, this.gossipCore);
|
||||||
activeGossipThread.init();
|
activeGossipThread.init();
|
||||||
|
dataReaper.init();
|
||||||
GossipService.LOGGER.debug("The GossipService is started.");
|
GossipService.LOGGER.debug("The GossipService is started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,6 +210,7 @@ public abstract class GossipManager implements NotificationListener {
|
|||||||
gossipServiceRunning.set(false);
|
gossipServiceRunning.set(false);
|
||||||
gossipThreadExecutor.shutdown();
|
gossipThreadExecutor.shutdown();
|
||||||
gossipCore.shutdown();
|
gossipCore.shutdown();
|
||||||
|
dataReaper.close();
|
||||||
if (passiveGossipThread != null) {
|
if (passiveGossipThread != null) {
|
||||||
passiveGossipThread.shutdown();
|
passiveGossipThread.shutdown();
|
||||||
}
|
}
|
||||||
@ -218,7 +227,10 @@ public abstract class GossipManager implements NotificationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void gossipData(GossipDataMessage message){
|
public void gossipPerNodeData(GossipDataMessage message){
|
||||||
|
Objects.nonNull(message.getKey());
|
||||||
|
Objects.nonNull(message.getTimestamp());
|
||||||
|
Objects.nonNull(message.getPayload());
|
||||||
message.setNodeId(me.getId());
|
message.setNodeId(me.getId());
|
||||||
gossipCore.addPerNodeData(message);
|
gossipCore.addPerNodeData(message);
|
||||||
}
|
}
|
||||||
@ -228,8 +240,19 @@ public abstract class GossipManager implements NotificationListener {
|
|||||||
if (j == null){
|
if (j == null){
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return j.get(key);
|
GossipDataMessage l = j.get(key);
|
||||||
|
if (l == null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (l.getExpireAt() != null && l.getExpireAt() < clock.currentTimeMillis()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataReaper getDataReaper() {
|
||||||
|
return dataReaper;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,15 +23,11 @@ import java.net.DatagramSocket;
|
|||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.apache.gossip.GossipMember;
|
|
||||||
import org.apache.gossip.GossipService;
|
|
||||||
import org.apache.gossip.model.Base;
|
import org.apache.gossip.model.Base;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import org.apache.gossip.RemoteGossipMember;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [The passive thread: reply to incoming gossip request.] This class handles the passive cycle,
|
* [The passive thread: reply to incoming gossip request.] This class handles the passive cycle,
|
||||||
@ -107,9 +103,9 @@ abstract public class PassiveGossipThread implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void debug(int packetLength, byte[] jsonBytes) {
|
private void debug(int packetLength, byte[] jsonBytes) {
|
||||||
if (GossipService.LOGGER.isDebugEnabled()){
|
if (LOGGER.isDebugEnabled()){
|
||||||
String receivedMessage = new String(jsonBytes);
|
String receivedMessage = new String(jsonBytes);
|
||||||
GossipService.LOGGER.debug("Received message (" + packetLength + " bytes): "
|
LOGGER.debug("Received message (" + packetLength + " bytes): "
|
||||||
+ receivedMessage);
|
+ receivedMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
src/main/java/org/apache/gossip/manager/SystemClock.java
Normal file
15
src/main/java/org/apache/gossip/manager/SystemClock.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package org.apache.gossip.manager;
|
||||||
|
|
||||||
|
public class SystemClock implements Clock {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long currentTimeMillis() {
|
||||||
|
return System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long nanoTime() {
|
||||||
|
return System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -46,17 +46,17 @@ public class DataTest {
|
|||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (int i = 0; i < clusterMembers; ++i) {
|
for (int i = 0; i < clusterMembers; ++i) {
|
||||||
total += clients.get(i).get_gossipManager().getLiveMembers().size();
|
total += clients.get(i).getGossipManager().getLiveMembers().size();
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo(2);
|
}}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo(2);
|
||||||
clients.get(0).gossipData(msg());
|
clients.get(0).gossipPerNodeData(msg());
|
||||||
Thread.sleep(10000);
|
Thread.sleep(10000);
|
||||||
TUnit.assertThat(
|
TUnit.assertThat(
|
||||||
|
|
||||||
new Callable<Object> (){
|
new Callable<Object> (){
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
GossipDataMessage x = clients.get(1).findGossipData(1+"" , "a");
|
GossipDataMessage x = clients.get(1).findPerNodeData(1+"" , "a");
|
||||||
if (x == null) return "";
|
if (x == null) return "";
|
||||||
else return x.getPayload();
|
else return x.getPayload();
|
||||||
}})
|
}})
|
||||||
|
@ -78,7 +78,7 @@ public class ShutdownDeadtimeTest {
|
|||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (int i = 0; i < clusterMembers; ++i) {
|
for (int i = 0; i < clusterMembers; ++i) {
|
||||||
total += clients.get(i).get_gossipManager().getLiveMembers().size();
|
total += clients.get(i).getGossipManager().getLiveMembers().size();
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
@ -88,15 +88,15 @@ public class ShutdownDeadtimeTest {
|
|||||||
Random r = new Random();
|
Random r = new Random();
|
||||||
int randomClientId = r.nextInt(clusterMembers);
|
int randomClientId = r.nextInt(clusterMembers);
|
||||||
log.info("shutting down " + randomClientId);
|
log.info("shutting down " + randomClientId);
|
||||||
final int shutdownPort = clients.get(randomClientId).get_gossipManager().getMyself().getUri()
|
final int shutdownPort = clients.get(randomClientId).getGossipManager().getMyself().getUri()
|
||||||
.getPort();
|
.getPort();
|
||||||
final String shutdownId = clients.get(randomClientId).get_gossipManager().getMyself().getId();
|
final String shutdownId = clients.get(randomClientId).getGossipManager().getMyself().getId();
|
||||||
clients.get(randomClientId).shutdown();
|
clients.get(randomClientId).shutdown();
|
||||||
TUnit.assertThat(new Callable<Integer>() {
|
TUnit.assertThat(new Callable<Integer>() {
|
||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (int i = 0; i < clusterMembers; ++i) {
|
for (int i = 0; i < clusterMembers; ++i) {
|
||||||
total += clients.get(i).get_gossipManager().getLiveMembers().size();
|
total += clients.get(i).getGossipManager().getLiveMembers().size();
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ public class ShutdownDeadtimeTest {
|
|||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (int i = 0; i < clusterMembers - 1; ++i) {
|
for (int i = 0; i < clusterMembers - 1; ++i) {
|
||||||
total += clients.get(i).get_gossipManager().getDeadList().size();
|
total += clients.get(i).getGossipManager().getDeadList().size();
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ public class ShutdownDeadtimeTest {
|
|||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (int i = 0; i < clusterMembers; ++i) {
|
for (int i = 0; i < clusterMembers; ++i) {
|
||||||
total += clients.get(i).get_gossipManager().getLiveMembers().size();
|
total += clients.get(i).getGossipManager().getLiveMembers().size();
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ public class StartupSettingsTest {
|
|||||||
|
|
||||||
TUnit.assertThat(new Callable<Integer> (){
|
TUnit.assertThat(new Callable<Integer> (){
|
||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
return firstService.get_gossipManager().getLiveMembers().size();
|
return firstService.getGossipManager().getLiveMembers().size();
|
||||||
}}).afterWaitingAtMost(30, TimeUnit.SECONDS).isEqualTo(0);
|
}}).afterWaitingAtMost(30, TimeUnit.SECONDS).isEqualTo(0);
|
||||||
final GossipService serviceUnderTest = new GossipService(
|
final GossipService serviceUnderTest = new GossipService(
|
||||||
StartupSettings.fromJSONFile( settingsFile )
|
StartupSettings.fromJSONFile( settingsFile )
|
||||||
@ -70,7 +70,7 @@ public class StartupSettingsTest {
|
|||||||
serviceUnderTest.start();
|
serviceUnderTest.start();
|
||||||
TUnit.assertThat(new Callable<Integer> (){
|
TUnit.assertThat(new Callable<Integer> (){
|
||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
return serviceUnderTest.get_gossipManager().getLiveMembers().size();
|
return serviceUnderTest.getGossipManager().getLiveMembers().size();
|
||||||
}}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(1);
|
}}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(1);
|
||||||
firstService.shutdown();
|
firstService.shutdown();
|
||||||
serviceUnderTest.shutdown();
|
serviceUnderTest.shutdown();
|
||||||
|
@ -82,7 +82,7 @@ public class TenNodeThreeSeedTest {
|
|||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (int i = 0; i < clusterMembers; ++i) {
|
for (int i = 0; i < clusterMembers; ++i) {
|
||||||
total += clients.get(i).get_gossipManager().getLiveMembers().size();
|
total += clients.get(i).getGossipManager().getLiveMembers().size();
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo(20);
|
}}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo(20);
|
||||||
|
55
src/test/java/org/apache/gossip/manager/DataReaperTest.java
Normal file
55
src/test/java/org/apache/gossip/manager/DataReaperTest.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package org.apache.gossip.manager;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import org.apache.gossip.GossipSettings;
|
||||||
|
import org.apache.gossip.manager.random.RandomGossipManager;
|
||||||
|
import org.apache.gossip.model.GossipDataMessage;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import io.teknek.tunit.TUnit;
|
||||||
|
|
||||||
|
public class DataReaperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReaperOneShot() {
|
||||||
|
String myId = "4";
|
||||||
|
String key = "key";
|
||||||
|
String value = "a";
|
||||||
|
GossipSettings settings = new GossipSettings();
|
||||||
|
GossipManager gm = RandomGossipManager.newBuilder().cluster("abc").settings(settings)
|
||||||
|
.withId(myId).uri(URI.create("udp://localhost:5000")).build();
|
||||||
|
gm.gossipPerNodeData(perNodeDatum(key, value));
|
||||||
|
Assert.assertEquals(value, gm.findGossipData(myId, key).getPayload());
|
||||||
|
gm.getDataReaper().runOnce();
|
||||||
|
TUnit.assertThat(() -> gm.findGossipData(myId, key)).equals(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GossipDataMessage perNodeDatum(String key, String value) {
|
||||||
|
GossipDataMessage m = new GossipDataMessage();
|
||||||
|
m.setExpireAt(System.currentTimeMillis() + 5L);
|
||||||
|
m.setKey(key);
|
||||||
|
m.setPayload(value);
|
||||||
|
m.setTimestamp(System.currentTimeMillis());
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHigherTimestampWins() {
|
||||||
|
String myId = "4";
|
||||||
|
String key = "key";
|
||||||
|
String value = "a";
|
||||||
|
GossipSettings settings = new GossipSettings();
|
||||||
|
GossipManager gm = RandomGossipManager.newBuilder().cluster("abc").settings(settings)
|
||||||
|
.withId(myId).uri(URI.create("udp://localhost:5000")).build();
|
||||||
|
GossipDataMessage before = perNodeDatum(key, value);
|
||||||
|
GossipDataMessage after = perNodeDatum(key, "b");
|
||||||
|
after.setTimestamp(after.getTimestamp() - 1);
|
||||||
|
gm.gossipPerNodeData(before);
|
||||||
|
Assert.assertEquals(value, gm.findGossipData(myId, key).getPayload());
|
||||||
|
gm.gossipPerNodeData(after);
|
||||||
|
Assert.assertEquals(value, gm.findGossipData(myId, key).getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user