src.net.sf.ehcache.distribution.MulticastKeepaliveHeartbeatSender Maven / Gradle / Ivy
/** * Copyright 2003-2006 Greg Luck * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sf.ehcache.distribution; import net.sf.ehcache.CacheManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; import java.util.List; /** * Sends heartbeats to a multicast group containing a compressed list of URLs. Supports up to approximately * 500 configured caches. * * @author Greg Luck * @version $Id: MulticastKeepaliveHeartbeatSender.java 173 2006-08-21 07:04:28Z gregluck $ */ public final class MulticastKeepaliveHeartbeatSender { private static final Log LOG = LogFactory.getLog(MulticastKeepaliveHeartbeatSender.class.getName()); private static final int DEFAULT_HEARTBEAT_INTERVAL = 5000; private static final int MINIMUM_HEARTBEAT_INTERVAL = 1000; private static long heartBeatInterval = DEFAULT_HEARTBEAT_INTERVAL; private final InetAddress groupMulticastAddress; private final Integer groupMulticastPort; private MulticastServerThread serverThread; private boolean stopped; private final CacheManager cacheManager; /** * Constructor * * @param multicastAddress * @param multicastPort */ public MulticastKeepaliveHeartbeatSender(CacheManager cacheManager, InetAddress multicastAddress, Integer multicastPort) { this.cacheManager = cacheManager; this.groupMulticastAddress = multicastAddress; this.groupMulticastPort = multicastPort; } /** * Start the heartbeat thread */ public final void init() { serverThread = new MulticastServerThread(); serverThread.start(); } /** * Shutdown this heartbeat sender */ public final synchronized void dispose() { stopped = true; notifyAll(); serverThread.interrupt(); } /** * A thread which sends a multicast heartbeat every second */ private final class MulticastServerThread extends Thread { private MulticastSocket socket; private byte[] compressedUrlList; private int cachePeersHash; /** * Constructor */ public MulticastServerThread() { super("Multicast Server Thread"); setDaemon(true); } public final void run() { try { socket = new MulticastSocket(groupMulticastPort.intValue()); socket.joinGroup(groupMulticastAddress); while (!stopped) { byte[] buffer = createCachePeersPayload(); DatagramPacket packet = new DatagramPacket(buffer, buffer.length, groupMulticastAddress, groupMulticastPort.intValue()); socket.send(packet); try { synchronized (this) { wait(heartBeatInterval); } } catch (InterruptedException e) { if (!stopped) { LOG.error("Error receiving heartbeat. Initial cause was " + e.getMessage(), e); } } } closeSocket(); } catch (IOException e) { LOG.debug(e); } } /** * Creates a gzipped payload. * * The last gzipped payload is retained and only recalculated if the list of cache peers * has changed. * * @return a gzipped byte[] */ private byte[] createCachePeersPayload() { List localCachePeers = cacheManager.getCachePeerListener().getBoundCachePeers(); int newCachePeersHash = localCachePeers.hashCode(); if (cachePeersHash != newCachePeersHash) { cachePeersHash = newCachePeersHash; byte[] uncompressedUrlList = PayloadUtil.assembleUrlList(localCachePeers); compressedUrlList = PayloadUtil.gzip(uncompressedUrlList); if (compressedUrlList.length > PayloadUtil.MTU) { LOG.fatal("Heartbeat is not working. Configure fewer caches for replication. " + "Size is " + compressedUrlList.length + " but should be no greater than" + PayloadUtil.MTU); } } return compressedUrlList; } /** * Interrupts this thread. * *
interruptible * channelUnless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. *
*If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. *
*If this thread is blocked in an I/O operation upon an {@link * java.nio.channels.InterruptibleChannel
} then the channel will be closed, the thread's interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. * *
If this thread is blocked in a {@link java.nio.channels.Selector} * then the thread's interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector's {@link * java.nio.channels.Selector#wakeup wakeup} method were invoked. *
*If none of the previous conditions hold then this thread's interrupt * status will be set.
* * @throws SecurityException if the current thread cannot modify this thread */ public final void interrupt() { closeSocket(); super.interrupt(); } private void closeSocket() { try { if (socket != null && !socket.isClosed()) { try { socket.leaveGroup(groupMulticastAddress); } catch (IOException e) { LOG.error("Error leaving multicast group. Message was " + e.getMessage()); } socket.close(); } } catch (NoSuchMethodError e) { LOG.debug("socket.isClosed is not supported by JDK1.3"); try { socket.leaveGroup(groupMulticastAddress); } catch (IOException ex) { LOG.error("Error leaving multicast group. Message was " + ex.getMessage()); } socket.close(); } } } /** * Sets the heartbeat interval to something other than the default of 5000ms. This is useful for testing, * but not recommended for production. This method is static and so affects the heartbeat interval of all * senders. The change takes effect after the next scheduled heartbeat. * @param heartBeatInterval a time in ms, greater than 1000 */ static void setHeartBeatInterval(long heartBeatInterval) { if (heartBeatInterval < MINIMUM_HEARTBEAT_INTERVAL) { LOG.warn("Trying to set heartbeat interval too low. Using MINIMUM_HEARTBEAT_INTERVAL instead."); MulticastKeepaliveHeartbeatSender.heartBeatInterval = MINIMUM_HEARTBEAT_INTERVAL; } else { MulticastKeepaliveHeartbeatSender.heartBeatInterval = heartBeatInterval; } } /** * Returns the heartbeat interval. */ public static long getHeartBeatInterval() { return heartBeatInterval; } }