package ibis.ipl.registry.central.client;
import ibis.ipl.IbisCapabilities;
import ibis.ipl.IbisConfigurationException;
import ibis.ipl.IbisProperties;
import ibis.ipl.impl.IbisIdentifier;
import ibis.ipl.registry.central.Election;
import ibis.ipl.registry.central.ElectionSet;
import ibis.ipl.registry.central.Event;
import ibis.ipl.registry.central.EventList;
import ibis.ipl.registry.central.ListMemberSet;
import ibis.ipl.registry.central.Member;
import ibis.ipl.registry.central.MemberSet;
import ibis.ipl.registry.central.RegistryProperties;
import ibis.ipl.registry.central.TreeMemberSet;
import ibis.ipl.registry.statistics.Statistics;
import ibis.util.TypedProperties;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.SortedSet;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class Pool {
private static final Logger logger = LoggerFactory.getLogger(Pool.class);
private final String poolName;
private final boolean closedWorld;
private final int size;
private final long heartbeatInterval;
private final MemberSet members;
private final ElectionSet elections;
private final EventList eventList;
private final Registry registry;
private final Statistics statistics;
private boolean initialized;
private boolean closed;
private boolean stopped;
private boolean terminated;
private Event closeEvent;
private Event terminateEvent;
private int time;
Pool(IbisCapabilities capabilities, TypedProperties properties,
Registry registry, Statistics statistics) {
this.registry = registry;
this.statistics = statistics;
if (statistics != null) {
statistics.newPoolSize(0);
}
if (properties.getBooleanProperty(RegistryProperties.TREE)) {
members = new TreeMemberSet();
} else {
members = new ListMemberSet();
}
elections = new ElectionSet();
eventList = new EventList();
time = -1;
initialized = false;
closed = false;
closeEvent = null;
terminated = false;
terminateEvent = null;
stopped = false;
// get the pool ....
poolName = properties.getProperty(IbisProperties.POOL_NAME);
if (poolName == null) {
throw new IbisConfigurationException(
"cannot initialize registry, property "
+ IbisProperties.POOL_NAME + " is not specified");
}
closedWorld = capabilities.hasCapability(IbisCapabilities.CLOSED_WORLD);
if (closedWorld) {
try {
size = properties.getIntProperty(IbisProperties.POOL_SIZE);
} catch (final NumberFormatException e) {
throw new IbisConfigurationException(
"could not start registry for a closed world ibis, "
+ "required property: "
+ IbisProperties.POOL_SIZE + " undefined", e);
}
} else {
size = -1;
}
heartbeatInterval = properties
.getIntProperty(RegistryProperties.HEARTBEAT_INTERVAL) * 1000;
}
synchronized Event[] getEventsFrom(int start) {
return eventList.getList(start);
}
String getName() {
return poolName;
}
boolean isClosedWorld() {
return closedWorld;
}
int getSize() {
return size;
}
synchronized Member getRandomMember() {
return members.getRandom();
}
synchronized int getNextRequiredEvent() {
return eventList.getNextRequiredEvent();
}
synchronized int getTime() {
return time;
}
synchronized boolean isInitialized() {
return initialized;
}
synchronized boolean isStopped() {
return stopped;
}
synchronized boolean isMember(IbisIdentifier ibis) {
return members.contains(ibis);
}
public synchronized boolean mustReportMaybeDead(
ibis.ipl.IbisIdentifier ibisIdentifier) {
Member member = members.get(ibisIdentifier.name());
if (member == null) {
logger.debug("user reporting ibis " + ibisIdentifier
+ " which is not in pool, not reporting");
return false;
}
if (member.getTime() > (System.currentTimeMillis() - heartbeatInterval)) {
logger.debug("user reporting member " + ibisIdentifier
+ " recently reported already, skiping this time");
return false;
}
member.updateTime();
return true;
}
// new incoming events
void newEventsReceived(Event[] events) {
synchronized (this) {
eventList.add(events);
}
handleEvents();
}
synchronized void purgeHistoryUpto(int time) {
if (this.time != -1 && this.time < time) {
logger
.error("EEP! we are asked to purge the history of events we still need. Our time = "
+ this.time + " purge time = " + time);
return;
}
eventList.setMinimum(time);
}
void init(DataInputStream stream) throws IOException {
long start = System.currentTimeMillis();
// copy over data first so we are not blocked while reading data
byte[] bytes = new byte[stream.readInt()];
stream.readFully(bytes);
DataInputStream in = new DataInputStream(
new ByteArrayInputStream(bytes));
long read = System.currentTimeMillis();
// we have all the data in the array now, read from that...
synchronized (this) {
long locked = System.currentTimeMillis();
if (initialized) {
// already initialized, ignore
return;
}
logger.debug("reading bootstrap state");
time = in.readInt();
members.init(in);
long membersDone = System.currentTimeMillis();
elections.init(in);
int nrOfSignals = in.readInt();
if (nrOfSignals < 0) {
throw new IOException("negative number of signals");
}
ArrayList<Event> signals = new ArrayList<Event>();
for (int i = 0; i < nrOfSignals; i++) {
signals.add(new Event(in));
}
closed = in.readBoolean();
if (closed) {
closeEvent = new Event(in);
}
terminated = in.readBoolean();
if (terminated) {
terminateEvent = new Event(in);
}
// Create list of "old" events
SortedSet<Event> events = new TreeSet<Event>();
events.addAll(members.getJoinEvents());
events.addAll(elections.getEvents());
events.addAll(signals);
if (closed) {
events.add(closeEvent);
}
if (terminated) {
events.add(terminateEvent);
}
long used = System.currentTimeMillis();
// pass old events to the registry
// CALLS REGISTRY WHILE POOL IS LOCKED!
for (Event event : events) {
registry.handleEvent(event);
}
long handled = System.currentTimeMillis();
initialized = true;
notifyAll();
if (statistics != null) {
statistics.newPoolSize(members.size());
}
long statted = System.currentTimeMillis();
logger.info("pool init, read = " + (read - start) + ", locked = "
+ (locked - read) + ", membersDone = "
+ (membersDone - locked) + ", used = "
+ (used - membersDone) + ", handled = " + (handled - used)
+ ", statted = " + (statted - handled));
}
handleEvents();
logger.debug("bootstrap complete");
}
void writeState(DataOutputStream out, int joinTime) throws IOException {
ByteArrayOutputStream arrayOut = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(arrayOut);
synchronized (this) {
if (!initialized) {
throw new IOException("state not initialized yet");
}
dataOut.writeInt(time);
members.writeTo(dataOut);
elections.writeTo(dataOut);
Event[] signals = eventList.getSignalEvents(joinTime, time);
dataOut.writeInt(signals.length);
for (Event event : signals) {
event.writeTo(dataOut);
}
dataOut.writeBoolean(closed);
if (closed) {
closeEvent.writeTo(out);
}
dataOut.writeBoolean(terminated);
if (terminated) {
terminateEvent.writeTo(out);
}
}
dataOut.flush();
dataOut.close();
byte[] bytes = arrayOut.toByteArray();
out.writeInt(bytes.length);
out.write(bytes);
logger.debug("pool state size = " + bytes.length);
}
synchronized String[] wonElections(IbisIdentifier id) {
ArrayList<String> result = new ArrayList<String>();
for (Election e : elections) {
if (e.getWinner().equals(id)) {
result.add(e.getName());
}
}
return result.toArray(new String[result.size()]);
}
synchronized IbisIdentifier getElectionResult(String election, long timeout)
throws IOException {
long deadline = System.currentTimeMillis() + timeout;
if (timeout == 0) {
deadline = Long.MAX_VALUE;
}
Election result = elections.get(election);
while (result == null) {
long timeRemaining = deadline - System.currentTimeMillis();
if (timeRemaining <= 0) {
logger.debug("getElectionResullt deadline expired");
return null;
}
try {
if (timeRemaining > 1000) {
timeRemaining = 1000;
}
logger.debug("waiting " + timeRemaining + " for election");
wait(timeRemaining);
logger.debug("DONE waiting " + timeRemaining + " for election");
} catch (final InterruptedException e) {
// IGNORE
}
result = elections.get(election);
}
logger.debug("getElection result = " + result);
return result.getWinner();
}
private synchronized void handleEvent(Event event) {
logger.debug("handling event: " + event);
switch (event.getType()) {
case Event.JOIN:
members.add(new Member(event.getIbis(), event));
if (statistics != null) {
statistics.newPoolSize(members.size());
}
break;
case Event.LEAVE:
members.remove(event.getIbis());
if (statistics != null) {
statistics.newPoolSize(members.size());
}
break;
case Event.DIED:
IbisIdentifier died = event.getIbis();
members.remove(died);
if (statistics != null) {
statistics.newPoolSize(members.size());
}
if (died.equals(registry.getIbisIdentifier())) {
logger.debug("we were declared dead");
stop();
}
break;
case Event.SIGNAL:
// Not handled here
break;
case Event.ELECT:
elections.put(new Election(event));
if (statistics != null) {
statistics.electionEvent();
}
break;
case Event.UN_ELECT:
elections.remove(event.getDescription());
if (statistics != null) {
statistics.electionEvent();
}
break;
case Event.POOL_CLOSED:
closed = true;
closeEvent = event;
break;
case Event.POOL_TERMINATED:
terminated = true;
terminateEvent = event;
break;
default:
logger.error("unknown event type: " + event.getType());
}
// wake up threads waiting for events
notifyAll();
if (logger.isDebugEnabled()) {
logger.debug("member list now: " + members);
}
}
synchronized boolean isClosed() {
if (!closedWorld) {
throw new IbisConfigurationException("isClosed() called but not "
+ "closed world");
}
return closed;
}
synchronized void waitUntilPoolClosed() {
if (!closedWorld) {
throw new IbisConfigurationException(
"waitUntilPoolClosed() called but not " + "closed world");
}
while (!(closed || stopped)) {
try {
wait();
} catch (final InterruptedException e) {
// IGNORE
}
}
}
synchronized void waitForEventTime(int time, long timeout) {
long deadline = System.currentTimeMillis() + timeout;
if (timeout == 0) {
deadline = Long.MAX_VALUE;
}
while (getTime() < time) {
if (stopped) {
return;
}
long currentTime = System.currentTimeMillis();
if (currentTime >= deadline) {
return;
}
try {
wait(deadline - currentTime);
} catch (InterruptedException e) {
// IGNORE
}
}
}
synchronized void stop() {
stopped = true;
notifyAll();
if (statistics != null) {
statistics.newPoolSize(0);
statistics.write();
}
}
private synchronized Event getEvent() {
return eventList.get(time);
}
/**
* Handles incoming events, passes events to the registry
*/
private void handleEvents() {
logger.info("handling events");
if (!isInitialized()) {
logger.info("handle events: not initialized yet");
return;
}
while (true) {
// Modified the code below to do getEvent and time++ within
// one synchronized block, otherwise race condition.
Event event;
synchronized (this) {
event = getEvent();
if (event == null) {
logger
.info("done handling events, event time now: "
+ time);
return;
}
handleEvent(event);
time++;
notifyAll();
registry.handleEvent(event);
}
}
}
public synchronized Member[] getChildren() {
return members.getChildren(registry.getIbisIdentifier());
}
synchronized boolean hasTerminated() {
return terminated;
}
synchronized ibis.ipl.IbisIdentifier waitUntilTerminated() {
while (!(terminated || stopped)) {
try {
wait();
} catch (final InterruptedException e) {
// IGNORE
}
}
return terminateEvent.getIbis();
}
}