View Javadoc

1   /**
2    *  Copyright 2003-2007 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.distribution;
18  
19  import net.sf.ehcache.CacheManager;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import java.io.IOException;
24  import java.net.DatagramPacket;
25  import java.net.InetAddress;
26  import java.net.MulticastSocket;
27  import java.rmi.RemoteException;
28  import java.util.Set;
29  import java.util.Collections;
30  import java.util.HashSet;
31  import java.util.StringTokenizer;
32  import java.util.List;
33  
34  import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
35  import edu.emory.mathcs.backport.java.util.concurrent.Executors;
36  
37  /**
38   * Receives heartbeats from any {@link MulticastKeepaliveHeartbeatSender}s out there.
39   * <p/>
40   * Our own multicast heartbeats are ignored.
41   *
42   * @author Greg Luck
43   * @version $Id: MulticastKeepaliveHeartbeatReceiver.java 512 2007-07-10 09:18:45Z gregluck $
44   */
45  public final class MulticastKeepaliveHeartbeatReceiver {
46      private static final Log LOG = LogFactory.getLog(MulticastKeepaliveHeartbeatReceiver.class.getName());
47  
48      private ExecutorService processingThreadPool;
49      private Set rmiUrlsProcessingQueue = Collections.synchronizedSet(new HashSet());
50      private final InetAddress groupMulticastAddress;
51      private final Integer groupMulticastPort;
52      private MulticastReceiverThread receiverThread;
53      private MulticastSocket socket;
54      private boolean stopped;
55      private final MulticastRMICacheManagerPeerProvider peerProvider;
56  
57      /**
58       * Constructor.
59       *
60       * @param peerProvider
61       * @param multicastAddress
62       * @param multicastPort
63       */
64      public MulticastKeepaliveHeartbeatReceiver(
65              MulticastRMICacheManagerPeerProvider peerProvider, InetAddress multicastAddress, Integer multicastPort) {
66          this.peerProvider = peerProvider;
67          this.groupMulticastAddress = multicastAddress;
68          this.groupMulticastPort = multicastPort;
69      }
70  
71      /**
72       * Start.
73       *
74       * @throws IOException
75       */
76      final void init() throws IOException {
77          socket = new MulticastSocket(groupMulticastPort.intValue());
78          socket.joinGroup(groupMulticastAddress);
79          receiverThread = new MulticastReceiverThread();
80          receiverThread.start();
81          processingThreadPool = Executors.newCachedThreadPool();
82      }
83  
84      /**
85       * Shutdown the heartbeat.
86       */
87      public final void dispose() {
88          LOG.debug("dispose called");
89          processingThreadPool.shutdownNow();
90          stopped = true;
91          receiverThread.interrupt();
92      }
93  
94      /**
95       * A multicast receiver which continously receives heartbeats.
96       */
97      private final class MulticastReceiverThread extends Thread {
98  
99          /**
100          * Constructor
101          */
102         public MulticastReceiverThread() {
103             super("Multicast Heartbeat Receiver Thread");
104             setDaemon(true);
105         }
106 
107         public final void run() {
108             byte[] buf = new byte[PayloadUtil.MTU];
109             try {
110                 while (!stopped) {
111                     DatagramPacket packet = new DatagramPacket(buf, buf.length);
112                     try {
113                         socket.receive(packet);
114                         byte[] payload = packet.getData();
115                         processPayload(payload);
116 
117 
118                     } catch (IOException e) {
119                         if (!stopped) {
120                             LOG.error("Error receiving heartbeat. " + e.getMessage() +
121                                     ". Initial cause was " + e.getMessage(), e);
122                         }
123                     }
124                 }
125             } catch (Throwable t) {
126                 LOG.error("Multicast receiver thread caught throwable. Cause was " + t.getMessage() + ". Continuing...");
127             }
128         }
129 
130         private void processPayload(byte[] compressedPayload) {
131             byte[] payload = PayloadUtil.ungzip(compressedPayload);
132             String rmiUrls = new String(payload);
133             if (self(rmiUrls)) {
134                 return;
135             }
136             rmiUrls = rmiUrls.trim();
137             if (LOG.isTraceEnabled()) {
138                 LOG.trace("rmiUrls received " + rmiUrls);
139             }
140             processRmiUrls(rmiUrls);
141         }
142 
143         /**
144          * This method forks a new executor to process the received heartbeat in a thread pool.
145          * That way each remote cache manager cannot interfere with others.
146          * <p/>
147          * In the worst case, we have as many concurrent threads as remote cache managers.
148          *
149          * @param rmiUrls
150          */
151         private void processRmiUrls(final String rmiUrls) {
152             if (rmiUrlsProcessingQueue.contains(rmiUrls)) {
153                 if (LOG.isDebugEnabled()) {
154                     LOG.debug("We are already processing these rmiUrls. Another heartbeat came before we finished: " + rmiUrls);
155                 }
156                 return;
157             }
158 
159             processingThreadPool.execute(new Runnable() {
160                 public void run() {
161                     try {
162                         // Add the rmiUrls we are processing.
163                         rmiUrlsProcessingQueue.add(rmiUrls);
164                         for (StringTokenizer stringTokenizer = new StringTokenizer(rmiUrls,
165                                 PayloadUtil.URL_DELIMITER); stringTokenizer.hasMoreTokens();) {
166                             if (stopped) {
167                                 return;
168                             }
169                             String rmiUrl = stringTokenizer.nextToken();
170                             registerNotification(rmiUrl);
171                             if (!peerProvider.peerUrls.containsKey(rmiUrl)) {
172                                 if (LOG.isDebugEnabled()) {
173                                     LOG.debug("Aborting processing of rmiUrls since failed to add rmiUrl: " + rmiUrl);
174                                 }
175                                 return;
176                             }
177                         }
178                     } finally {
179                         // Remove the rmiUrls we just processed
180                         rmiUrlsProcessingQueue.remove(rmiUrls);
181                     }
182                 }
183             });
184         }
185 
186 
187         /**
188          * @param rmiUrls
189          * @return true if our own hostname and listener port are found in the list. This then means we have
190          *         caught our onw multicast, and should be ignored.
191          */
192         private boolean self(String rmiUrls) {
193             CacheManager cacheManager = peerProvider.getCacheManager();
194             CacheManagerPeerListener cacheManagerPeerListener = cacheManager.getCachePeerListener();
195             if (cacheManagerPeerListener == null) {
196                 return false;
197             }
198             List boundCachePeers = cacheManagerPeerListener.getBoundCachePeers();
199             if (boundCachePeers == null || boundCachePeers.size() == 0) {
200                 return false;
201             }
202             CachePeer peer = (CachePeer) boundCachePeers.get(0);
203             String cacheManagerUrlBase = null;
204             try {
205                 cacheManagerUrlBase = peer.getUrlBase();
206             } catch (RemoteException e) {
207                 LOG.error("Error geting url base");
208             }
209             int baseUrlMatch = rmiUrls.indexOf(cacheManagerUrlBase);
210             return baseUrlMatch != -1;
211         }
212 
213         private void registerNotification(String rmiUrl) {
214             peerProvider.registerPeer(rmiUrl);
215         }
216 
217 
218         /**
219          * {@inheritDoc}
220          */
221         public final void interrupt() {
222             try {
223                 socket.leaveGroup(groupMulticastAddress);
224             } catch (IOException e) {
225                 LOG.error("Error leaving group");
226             }
227             socket.close();
228             super.interrupt();
229         }
230     }
231 
232 
233 }