package ibis.ipl.support.vivaldi;
import ibis.ipl.impl.Ibis;
import ibis.ipl.impl.IbisIdentifier;
import ibis.ipl.registry.Registry;
import ibis.ipl.support.Client;
import ibis.ipl.support.Connection;
import ibis.smartsockets.virtual.VirtualServerSocket;
import ibis.smartsockets.virtual.VirtualSocketFactory;
import ibis.util.ThreadPool;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VivaldiClient implements Runnable {
private static final Logger logger = LoggerFactory
.getLogger(VivaldiClient.class);
// how long do we wait for a connection
public static final int CONNECTION_TIMEOUT = 10 * 1000;
// how long do we wait between two "pings"
public static final int PING_INTERVAL = 10 * 1000;
public static final int PING_COUNT = 4;
private static final int CONNECTION_BACKLOG = 10;
private final Registry registry;
private final VirtualSocketFactory virtualSocketFactory;
private final VirtualServerSocket serverSocket;
private boolean ended;
private Coordinates coordinates;
public VivaldiClient(Properties properties, Registry registry)
throws IOException {
this.registry = registry;
this.coordinates = new Coordinates();
String clientID = properties.getProperty(Ibis.ID_PROPERTY);
Client client = Client.getOrCreateClient(clientID, properties, 0);
this.virtualSocketFactory = client.getFactory();
serverSocket = virtualSocketFactory.createServerSocket(
Protocol.VIRTUAL_PORT, CONNECTION_BACKLOG, null);
// start handling connections
new ConnectionHandler(serverSocket, this);
ThreadPool.createNew(this, "Vivaldi Client");
}
public double ping(IbisIdentifier identifier, boolean updateCoordinates)
throws IOException {
double result = Double.MAX_VALUE;
if (logger.isDebugEnabled()) {
logger.debug("ping to " + identifier);
}
Connection connection = new Connection(identifier, CONNECTION_TIMEOUT,
true, virtualSocketFactory, Protocol.VIRTUAL_PORT);
InputStream in = connection.in();
OutputStream out = connection.out();
// get coordinates from peer
byte[] coordinateBytes = new byte[Coordinates.SIZE];
int remaining = Coordinates.SIZE;
int offset = 0;
while (remaining > 0) {
int read = in.read(coordinateBytes, offset, remaining);
if (read == -1) {
throw new IOException("could not read Coordinates");
}
offset += read;
remaining -= read;
}
Coordinates remoteCoordinates = new Coordinates(coordinateBytes);
for (int i = 0; i < PING_COUNT; i++) {
long start = System.nanoTime();
out.write(i);
out.flush();
int reply = in.read();
long end = System.nanoTime();
if (reply != i) {
throw new IOException("ping failed, wrong reply: " + reply);
}
long time = end - start;
double rtt = time / 1000000.0;
if (rtt < result) {
result = rtt;
}
}
connection.close();
if (updateCoordinates) {
updateCoordinates(remoteCoordinates, result);
}
if (logger.isInfoEnabled()) {
logger.info("vivaldi distance to " + identifier + " is "
+ remoteCoordinates.distance(getCoordinates())
+ " actual distance is " + result + " ms, coordinates now " + getCoordinates());
}
return result;
}
public void handleConnection(Connection connection) {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
try {
DataOutputStream out = connection.out();
DataInputStream in = connection.in();
// send coordinates
out.write(getCoordinates().toBytes());
out.flush();
for (int i = 0; i < PING_COUNT; i++) {
int read = in.read();
out.write(read);
out.flush();
}
} catch (IOException e) {
logger.error("error on handling ping", e);
}
connection.close();
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
}
public void start() {
ThreadPool.createNew(this, "vivaldi");
logger.info("Started Vivaldi service");
}
private synchronized void updateCoordinates(Coordinates remoteCoordinates,
double rtt) {
coordinates = coordinates.update(remoteCoordinates, rtt);
if (logger.isDebugEnabled()) {
logger.debug("coordinates now " + coordinates);
}
}
public synchronized Coordinates getCoordinates() {
return coordinates;
}
private synchronized boolean ended() {
return ended;
}
public void end() {
synchronized (this) {
ended = true;
notifyAll();
}
try {
serverSocket.close();
} catch (Exception e) {
// IGNORE
}
try {
virtualSocketFactory.end();
} catch (Exception e) {
// IGNORE
}
}
public void run() {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
while (!ended()) {
IbisIdentifier randomNode = registry.getRandomPoolMember();
if (randomNode != null
&& !randomNode.equals(registry.getIbisIdentifier())) {
try {
ping(randomNode, true);
} catch (Exception e) {
logger.debug("error on pinging random node " + randomNode,
e);
}
}
try {
Thread.sleep(PING_INTERVAL);
} catch (InterruptedException e) {
// IGNORE
}
}
}
}