/*
* KeepAliveThread.java
*/
package org.activemq.transport.reliable;
import java.util.Iterator;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.message.KeepAlive;
import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArraySet;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
/**
* KeepAliveDaemon keeps channels alive by sending KeepAlive packets on a
* specified interval. If the packets aren't don't get a receipt within a
* specified time, the channel will be forcefully disconnected.
*/
public class KeepAliveDaemon implements Runnable {
private static final Log log = LogFactory.getLog(KeepAliveDaemon.class);
private static KeepAliveDaemon instance = null;
private long checkInterval = 15000L;
private long lastCheck = 0;
private Object lock = new Object();
private SynchronizedBoolean started = new SynchronizedBoolean(false);
private SynchronizedBoolean stopped = new SynchronizedBoolean(false);
private CopyOnWriteArraySet monitoredChannels = new CopyOnWriteArraySet();
private CopyOnWriteArraySet zombieChannelSuspects = new CopyOnWriteArraySet();
/**
* Constructs a new KeepAliveDaemon which will send KeepAlive packets
* throught the wrapped channel.
*/
protected KeepAliveDaemon() {
}
/**
* Gets the current instance. Singletons implemented this way aren't popular
* these days, but it might be good here. :)
*
* @return the daemon
*/
public static synchronized KeepAliveDaemon getInstance() {
if (instance == null)
instance = new KeepAliveDaemon();
return instance;
}
public void addMonitoredChannel(ReliableTransportChannel channel) {
if (channel.getKeepAliveTimeout() <= 0)
return;
log.debug("Adding channel " + channel);
// Check that the timeout isn't lower than our check interval as
// this would cause the channel to constantly be disconnected.
// This check should perhaps be done whenever the channel changes it's
// interval, but in practice, this will probably never happen.
if (channel.getKeepAliveTimeout() / 2 < checkInterval) {
setCheckInterval(channel.getKeepAliveTimeout() / 2);
log.info("Adjusting check interval to " + checkInterval + " as channel " + channel.toString()
+ " has lower timeout time than the current check interval.");
}
monitoredChannels.add(channel);
}
public void removeMonitoredChannel(ReliableTransportChannel channel) {
log.debug("Removing channel " + channel);
monitoredChannels.remove(channel);
}
/**
* Sets the number of milliseconds between keep-alive checks are done.
*
* @param interval
* the interval
*/
public void setCheckInterval(long interval) {
this.checkInterval = interval;
if (started.and(!stopped.get())) {
restart();
}
}
public long getCheckInterval() {
return checkInterval;
}
public long getLastCheckTime() {
return lastCheck;
}
public void start() {
if (started.commit(false, true)) {
log.debug("Scheduling keep-alive every " + checkInterval + " millisecond.");
Thread t = new Thread(this);
t.setName("KeepAliveDaemon");
t.setDaemon(true);
t.start();
}
}
public void stop() {
if (stopped.commit(false, true)) {
synchronized (lock) {
lock.notifyAll();
}
log.debug("Stopping keep-alive.");
}
}
public void restart() {
log.debug("Restarting keep-alive.");
stop();
start();
}
public void run() {
lastCheck = System.currentTimeMillis() - checkInterval;
while (!stopped.get()) {
for (Iterator i = zombieChannelSuspects.iterator(); i.hasNext();) {
ReliableTransportChannel channel = (ReliableTransportChannel) i.next();
examineZombieSuspect(channel);
}
for (Iterator i = monitoredChannels.iterator(); i.hasNext();) {
ReliableTransportChannel channel = (ReliableTransportChannel) i.next();
if (!zombieChannelSuspects.contains(channel))
examineChannel(channel);
}
lastCheck = System.currentTimeMillis();
synchronized (lock) {
try {
lock.wait(checkInterval);
} catch (InterruptedException e) {
}
}
}
}
private void examineZombieSuspect(ReliableTransportChannel channel) {
if ((channel.getLastReceiptTimestamp() + channel.getKeepAliveTimeout() * 2) < System.currentTimeMillis()) {
// Timed out
log.info("Forcing channel "
+ channel
+ " to disconnect since it "
+ (channel.getLastReceiptTimestamp() == 0 ? "never has responded " : "hasn't responded since "
+ new java.util.Date(channel.getLastReceiptTimestamp())) + " and has a timeout of "
+ channel.getKeepAliveTimeout());
channel.forceDisconnect();
// Remove it and wait for a reconnect
zombieChannelSuspects.remove(channel);
} else if ((channel.getLastReceiptTimestamp() + channel.getKeepAliveTimeout()) < System.currentTimeMillis()) {
// Still a zombie suspect, but has not timed out
log.debug("Still waiting for response from channel " + channel);
} else {
// It's alive again
log.debug("Channel " + channel + " responded in time.");
zombieChannelSuspects.remove(channel);
}
}
private void examineChannel(ReliableTransportChannel channel) {
// Is the channel stopping?
if (channel.isPendingStop()) {
removeMonitoredChannel(channel);
} else {
// Then it should be active
// Keep pinging the channel periodically
if ((channel.getLastReceiptTimestamp() + channel.getKeepAliveTimeout()) < System.currentTimeMillis()) {
log.debug("Sending keep-alive on channel " + channel.toString());
KeepAlive packet = new KeepAlive();
packet.setReceiptRequired(true);
boolean wasConnected = channel.isTransportConnected();
try {
channel.asyncSendWithReceipt(packet);
zombieChannelSuspects.add(channel);
} catch (JMSException e) {
// only report an error if the channel was connetected.
if (wasConnected) {
log.error("Error sending keep-alive to channel " + channel.toString()
+ ". Treating as temporary problem.", e);
}
}
}
}
}
}