package ibis.ipl.registry.gossip;
import ibis.ipl.impl.IbisIdentifier;
import ibis.ipl.registry.statistics.Statistics;
import ibis.util.TypedProperties;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class MemberSet extends Thread {
private static final Logger logger = LoggerFactory
.getLogger(MemberSet.class);
private final TypedProperties properties;
private final Registry registry;
private final Statistics statistics;
private final HashSet<UUID> deceased;
private final HashSet<UUID> left;
private final HashMap<UUID, Member> members;
private Member self;
private final Random random;
/**
* Members that are actually reachable.
*/
private int liveMembers;
MemberSet(TypedProperties properties, Registry registry,
Statistics statistics) {
this.properties = properties;
this.registry = registry;
this.statistics = statistics;
deceased = new HashSet<UUID>();
left = new HashSet<UUID>();
members = new HashMap<UUID, Member>();
random = new Random();
}
@Override
public synchronized void start() {
this.setDaemon(true);
super.start();
}
private synchronized Member getMember(IbisIdentifier ibis, boolean create) {
Member result;
UUID id = UUID.fromString(ibis.getID());
if (deceased.contains(id) || left.contains(id)) {
return null;
}
result = members.get(id);
if (result == null && create) {
result = new Member(ibis, properties);
members.put(id, result);
registry.ibisJoined(ibis);
if (statistics != null) {
statistics.newPoolSize(members.size());
}
}
return result;
}
public synchronized void maybeDead(IbisIdentifier ibis) {
Member member = getMember(ibis, false);
if (member == null) {
return;
}
member.suspectDead(registry.getIbisIdentifier());
cleanup(member);
}
public synchronized void assumeDead(IbisIdentifier ibis) {
Member member = getMember(ibis, false);
if (member == null) {
return;
}
member.declareDead();
cleanup(member);
}
public synchronized void leave(IbisIdentifier ibis) {
Member member = getMember(ibis, true);
if (member == null) {
return;
}
member.setLeft();
cleanup(member);
}
public synchronized void leave() {
if (self != null) {
self.setLeft();
}
}
public synchronized IbisIdentifier getFirstLiving(
IbisIdentifier[] candidates) {
if (candidates == null || candidates.length == 0) {
return null;
}
for (IbisIdentifier candidate : candidates) {
Member member = getMember(candidate, false);
if (member != null && !member.hasLeft() && !member.isDead()) {
return candidate;
}
}
// no alive canidates found, return first candidate
return candidates[0];
}
public void writeGossipData(DataOutputStream out, int gossipSize) throws IOException {
UUID[] deceased;
UUID[] left;
Member[] randomMembers;
synchronized (this) {
deceased = this.deceased.toArray(new UUID[0]);
left = this.left.toArray(new UUID[0]);
randomMembers = getRandomMembers(gossipSize);
if (self != null) {
// make sure we send out ourselves as "just seen"
self.seen();
}
}
out.writeInt(deceased.length);
for (UUID id : deceased) {
out.writeLong(id.getMostSignificantBits());
out.writeLong(id.getLeastSignificantBits());
}
out.writeInt(left.length);
for (UUID id : left) {
out.writeLong(id.getMostSignificantBits());
out.writeLong(id.getLeastSignificantBits());
}
out.writeInt(randomMembers.length);
for (Member member : randomMembers) {
member.writeTo(out);
}
}
public void readGossipData(DataInputStream in) throws IOException {
int nrOfDeceased = in.readInt();
if (nrOfDeceased < 0) {
throw new IOException("negative deceased list value");
}
ArrayList<UUID> newDeceased = new ArrayList<UUID>();
for (int i = 0; i < nrOfDeceased; i++) {
UUID id = new UUID(in.readLong(), in.readLong());
newDeceased.add(id);
}
int nrOfLeft = in.readInt();
if (nrOfLeft < 0) {
throw new IOException("negative left list value");
}
ArrayList<UUID> newLeft = new ArrayList<UUID>();
for (int i = 0; i < nrOfLeft; i++) {
UUID id = new UUID(in.readLong(), in.readLong());
newLeft.add(id);
}
int nrOfMembers = in.readInt();
if (nrOfMembers < 0) {
throw new IOException("negative member list value");
}
ArrayList<Member> newMembers = new ArrayList<Member>();
for (int i = 0; i < nrOfMembers; i++) {
Member member = new Member(in, properties);
newMembers.add(member);
}
synchronized (this) {
for (Member member : newMembers) {
UUID id = member.getUUID();
if (members.containsKey(id)) {
// merge state of know and received member
members.get(id).merge(member);
} else if (!deceased.contains(id) && !left.contains(id)) {
// add new member
members.put(id, member);
// tell registry about his new member
registry.ibisJoined(member.getIdentifier());
if (statistics != null) {
statistics.newPoolSize(members.size());
}
}
}
for (UUID id : newDeceased) {
if (members.containsKey(id)) {
members.get(id).declareDead();
} else if (!left.contains(id)) {
deceased.add(id);
}
}
for (UUID id : newLeft) {
if (members.containsKey(id)) {
members.get(id).setLeft();
} else {
left.add(id);
}
}
}
}
/**
* Clean up the list of members. Also passes leave and died events to the
* registry.
*/
private synchronized void cleanup(Member member) {
if (deceased.contains(member.getUUID())) {
member.declareDead();
}
if (left.contains(member.getUUID())) {
member.setLeft();
}
// if there are not enough live members in a pool to reach the
// minimum needed to otherwise declare a member dead, do it now
if (member.isSuspect() && member.nrOfWitnesses() >= liveMembers) {
logger.warn("declared " + member + " with "
+ member.nrOfWitnesses()
+ " witnesses dead due to a low number of live members ("
+ liveMembers + ").");
member.declareDead();
}
if (member.hasLeft()) {
left.add(member.getUUID());
members.remove(member.getUUID());
if (statistics != null) {
statistics.newPoolSize(members.size());
}
registry.ibisLeft(member.getIdentifier());
logger.debug("purged " + member + " from list");
} else if (member.isDead()) {
deceased.add(member.getUUID());
members.remove(member.getUUID());
if (statistics != null) {
statistics.newPoolSize(members.size());
}
registry.ibisDied(member.getIdentifier());
logger.debug("purged " + member + " from list");
}
}
/**
* Update number of "alive" members
*/
private synchronized void updateLiveMembers() {
int result = 0;
for (Member member : members.values()) {
if (!member.isDead() && !member.hasLeft() && !member.timedout()) {
result++;
}
}
liveMembers = result;
}
/**
* Clean up the list of members.
*/
private synchronized void cleanup() {
// notice ourselves ;)
if (self != null) {
self.seen();
}
// update live member count
updateLiveMembers();
// iterate over copy of values, so we can remove them if we need to
for (Member member : members.values().toArray(new Member[0])) {
cleanup(member);
}
if (logger.isDebugEnabled()) {
logger.debug(self.getIdentifier() + ": members = " + members.size()
+ ", left = " + left.size() + " deceased = "
+ deceased.size());
}
}
private synchronized Member[] getRandomSuspects(int count) {
ArrayList<Member> suspects = new ArrayList<Member>();
for (Member member : members.values()) {
if (member.isSuspect()) {
suspects.add(member);
}
}
while (suspects.size() > count) {
suspects.remove(random.nextInt(suspects.size()));
}
return suspects.toArray(new Member[0]);
}
synchronized Member[] getRandomMembers(int count) {
if (count < 0) {
return new Member[0];
}
ArrayList<Member> result = new ArrayList<Member>(members.values());
while (result.size() > count) {
result.remove(random.nextInt(result.size()));
}
return result.toArray(new Member[0]);
}
synchronized void printMembers() {
System.out.println("pool at " + registry.getIbisIdentifier());
System.out.println("dead:");
for (UUID member : deceased) {
System.out.println(member);
}
System.out.println("left:");
for (UUID member : left) {
System.out.println(member);
}
System.out.println("current:");
for (Member member : members.values()) {
System.out.println(member);
}
}
// ping suspect members once a second
public void run() {
Member self;
synchronized (this) {
// add ourselves to the member list
self = new Member(registry.getIbisIdentifier(), properties);
this.self = self;
members.put(self.getUUID(), self);
if (statistics != null) {
statistics.newPoolSize(members.size());
}
registry.ibisJoined(self.getIdentifier());
}
long interval = properties
.getIntProperty(RegistryProperties.PING_INTERVAL) * 1000;
int count = properties.getIntProperty(RegistryProperties.PING_COUNT);
while (!registry.isStopped()) {
cleanup();
Member[] suspects = getRandomSuspects(count);
logger.debug(self.getIdentifier() + ": checking " + suspects.length
+ " suspects");
for (Member suspect : suspects) {
if (suspect.equals(self)) {
logger.error("we are a suspect ourselves");
suspect.seen();
} else {
logger
.debug("suspecting " + suspect
+ " is dead, checking");
try {
registry.getCommHandler().ping(suspect.getIdentifier());
suspect.seen();
} catch (Exception e) {
logger.debug("could not reach " + suspect
+ ", adding ourselves as witness");
suspect.suspectDead(registry.getIbisIdentifier());
}
logger.debug("done checking " + suspect);
}
}
logger.debug(self.getIdentifier() + ": done checking "
+ suspects.length + " suspects");
int timeout = (int) (Math.random() * interval);
synchronized (this) {
if (timeout > 0) {
try {
wait(timeout);
} catch (InterruptedException e) {
// IGNORE
}
}
}
}
logger.debug(self.getIdentifier() + ": registry stopped, exiting");
}
}