/*
* (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* bstefanescu
*
* $Id$
*/
package org.nuxeo.runtime.detection;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class MulticastDetector<T> {
private static final Log log = LogFactory.getLog(MulticastDetector.class);
protected final InetAddress groupAddr;
protected final int groupPort;
protected final String identity;
protected final Map<String, Peer<T>> peers;
protected MulticastSocket socket;
protected long heartBeatTimeout = 5000; // 10 sec
private DetectionHandler handler;
private HeartBeatDetection heartBeatDetection;
private Timer heartBeatTimer;
private Timer processingTimer;
public MulticastDetector(String identity, InetAddress groupAddr,
int groupPort) throws IOException {
this.identity = identity;
this.groupAddr = groupAddr;
this.groupPort = groupPort;
socket = new MulticastSocket(groupPort);
peers = new HashMap<String, Peer<T>>();
}
public MulticastDetector(String identity) throws IOException {
this(identity, "224.1.9.2", 4444);
}
public MulticastDetector(String identity, String groupAddr, int groupPort)
throws IOException {
this(identity, InetAddress.getByName(groupAddr), groupPort);
}
public void setDetectionHandler(DetectionHandler handler) {
this.handler = handler;
}
public DetectionHandler getDetectionHandler() {
return handler;
}
public MulticastSocket getSocket() {
return socket;
}
public void setHeartBeatTimeout(long ms) {
heartBeatTimeout = ms;
}
public long getHeartBeatTimeout() {
return heartBeatTimeout;
}
public synchronized void start() {
if (heartBeatDetection != null) {
return;
}
try {
socket.setSoTimeout((int) heartBeatTimeout); // give a chance to stop heart beat detector
heartBeatDetection = new HeartBeatDetection();
heartBeatDetection.start();
processingTimer = new Timer("Nuxeo.Detection.Cleanup");
processingTimer.schedule(new CleanupTask(), heartBeatTimeout, heartBeatTimeout);
socket.joinGroup(groupAddr);
heartBeatTimer = new Timer("Nuxeo.Detection.HeartBeat");
heartBeatTimer.schedule(new HeartBeatTask(), 0, heartBeatTimeout);
} catch (Throwable t) {
stop();
}
}
public synchronized void stop() {
if (heartBeatDetection == null) {
return;
}
heartBeatTimer.cancel();
heartBeatTimer = null;
heartBeatDetection.cancel();
heartBeatDetection = null;
processingTimer.cancel();
processingTimer = null;
}
public String getIdentity() {
return identity;
}
@SuppressWarnings("unchecked")
public Peer<T>[] getPeers() {
synchronized (peers) {
return peers.values().toArray(new Peer[peers.size()]);
}
}
private DatagramPacket createHeartBeat() {
byte[] bytes = identity.getBytes();
return new DatagramPacket(bytes, bytes.length, groupAddr, groupPort);
}
private String readHeartBeat(DatagramPacket p) {
return new String(p.getData(), p.getOffset(), p.getLength());
}
protected void notifyPeerOnline(Peer<T> peer) {
// async exec needed in that case otherwise
// heartbeat detection may be significantly delayed and some heartbeats lost
if (handler != null) {
processingTimer.schedule(new NotifyTask(peer, true), 0);
}
}
protected void notifyPeerOffline(Peer<T> peer) {
// async exec not needed in that case
if (handler != null) {
handler.peerOffline(peer);
}
}
class HeartBeatDetection extends Thread {
private boolean running = false;
private final Object runLock = new Object();
HeartBeatDetection() {
super("Nuxeo.HeartBeatDetection");
}
public void cancel() {
synchronized (runLock) {
running = false;
}
interrupt();
}
@Override
public synchronized void start() {
synchronized (runLock) {
running = true;
}
super.start();
}
@Override
public void run() {
while (true) {
//System.out.println(identity+": running heart beat listener");
try {
synchronized (runLock) {
if (!running) {
break; // detector was stopped
}
}
byte[] bytes = new byte[4000];
DatagramPacket p = new DatagramPacket(bytes, bytes.length);
socket.receive(p); // block until a new message is received
String identity = readHeartBeat(p);
if (MulticastDetector.this.identity.equals(identity)) {
continue;
}
Peer<T> peer;
synchronized (peers) {
peer = peers.get(identity);
if (peer == null) { // create new peer
peer = new Peer<T>(p.getAddress(), p.getPort(), identity);
assert peer.addr.equals(p.getAddress());
assert peer.port == p.getPort();
peers.put(identity, peer);
} else { // update peer last heart beat
peer.lastHeartBeat = System.currentTimeMillis();
peer = null;
}
}
if (peer != null) { // new peer detected
System.out.println("Peer online: "+peer);
notifyPeerOnline(peer);
}
} catch (SocketTimeoutException e) {
// socket timeout -> continue
} catch (Throwable e) {
log.error(e, e);
}
}
}
}
class HeartBeatTask extends TimerTask {
@Override
public void run() {
//System.out.println(identity+": running heart beat task");
try {
socket.send(createHeartBeat());
} catch (IOException e) {
log.error(e, e);
}
}
}
class CleanupTask extends TimerTask {
@SuppressWarnings("unchecked")
@Override
public void run() {
//System.out.println(identity+": running cleanup task");
long tm = System.currentTimeMillis();
// copy existing peers into an array to avoid race conditions
Peer[] arPeers = getPeers();
// remove expired peers
for (Peer peer : arPeers) {
if (tm - peer.lastHeartBeat > heartBeatTimeout*2) {
synchronized (peers) {
peers.remove(peer.identity);
}
System.out.println("Peer Offline: "+peer);
notifyPeerOffline(peer);
peer.data = null;
}
}
}
}
class NotifyTask extends TimerTask {
private final boolean online;
private final Peer peer;
NotifyTask(Peer<T> peer, boolean online) {
this.peer = peer;
this.online = online;
}
@Override
public void run() {
if (handler == null) {
return;
}
if (online) {
handler.peerOnline(peer);
}
else {
handler.peerOffline(peer);
}
}
}
}