/* * Copyright to the original author or authors. * * 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 org.rioproject.impl.fdh; import net.jini.admin.Administrable; import net.jini.core.lookup.ServiceID; import net.jini.core.lookup.ServiceItem; import net.jini.lookup.ServiceDiscoveryEvent; import org.rioproject.impl.util.ThrowableUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.rmi.RemoteException; import java.util.*; import java.util.concurrent.*; /** * @author Dennis Reedy */ public class PooledFaultDetectionHandler /*implements FaultDetectionHandler<ServiceID>*/ extends AbstractFaultDetectionHandler { private final Set<ServiceEntry> serviceSet; private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private static final Logger logger = LoggerFactory.getLogger(PooledFaultDetectionHandler.class); public PooledFaultDetectionHandler() { Map<ServiceEntry, Boolean> concurrentMap = new ConcurrentHashMap<>(); serviceSet = Collections.newSetFromMap(concurrentMap); } @Override public void configure(Properties properties) { super.configure(properties); long invocationDelay = getInvocationDelay(); scheduler.scheduleAtFixedRate(new Reaper(), 0, invocationDelay, TimeUnit.MILLISECONDS); } int getServiceCount() { return serviceSet.size(); } @Override public void monitor(Object service, ServiceID serviceID) { if(service==null || serviceID==null) throw new IllegalArgumentException("The proxy or the serviceID cannot be null"); serviceSet.add(new ServiceEntry(service, serviceID)); } @Override protected ServiceMonitor getServiceMonitor() { return null; } @Override protected ServiceCacheListener getServiceCacheListener() { return new FDHListener(); } @Override public void terminate() { System.out.println("Terminate: "+serviceSet.size()); scheduler.shutdownNow(); super.terminate(); } private class Reaper implements Runnable { @Override public void run() { List<ServiceEntry> removals = new ArrayList<>(); System.out.println("Reaper: "+serviceSet.size()); for(ServiceEntry entry : serviceSet) { if(entry.administrable!=null) { boolean verified = false; try { entry.administrable.getAdmin(); verified = true; } catch (RemoteException e) { //e.printStackTrace(); } if(!verified) { removals.add(entry); } } else { logger.debug("Skipped {}, it is not Administrable", entry.serviceID) ; } } for(final ServiceEntry entry : removals) { serviceSet.remove(entry); getListeners().stream().forEach(l -> l.serviceFailure(entry.proxy, entry.serviceID)); } } } /** * Listen for service removal events for the service we're interested in */ private class FDHListener extends ServiceCacheListener { final ExecutorService verifyPool = Executors.newCachedThreadPool(); /** * Notification that the service has been removed * * @param sdEvent The ServiceDiscoveryEvent */ public void serviceRemoved(ServiceDiscoveryEvent sdEvent) { ServiceItem item = sdEvent.getPreEventServiceItem(); ServiceEntry entry = new ServiceEntry(item); if(!serviceSet.contains(entry)) { return; } if(logger.isTraceEnabled()) logger.trace("FDH: service [{}], removed notification", NameHelper.getName(item.attributeSets)); verifyPool.submit(new ServiceVerifier(entry)); } public void terminate() { verifyPool.shutdownNow(); } } private class ServiceVerifier implements Runnable { ServiceEntry serviceEntry; ServiceVerifier(ServiceEntry serviceEntry) { this.serviceEntry = serviceEntry; } @Override public void run() { boolean reachable = false; for(int i = 0; i < retryCount; i++) { reachable = verify(); if(!reachable) { break; } if(retryTimeout > 0) { try { Thread.sleep(retryTimeout); } catch(InterruptedException ie) { // Restore the interrupted status Thread.currentThread().interrupt(); if(logger.isTraceEnabled()) logger.trace("FDH: service [{}], proxy [{}] interrupted during retry timeout", NameHelper.getName(serviceEntry.item.attributeSets), serviceEntry.proxy.getClass().getName()); } } } if(logger.isTraceEnabled()) logger.trace("FDH: service [{}], proxy [{}] is reachable [{}]", NameHelper.getName(serviceEntry.item.attributeSets), serviceEntry.proxy.getClass().getName(), reachable); if(!reachable) { getListeners().stream().forEach(l -> l.serviceFailure(serviceEntry.proxy, serviceEntry.serviceID)); } } boolean verify() { boolean verified = false; try { if(logger.isTraceEnabled()) logger.trace("Invoke getAdmin() on : {}", serviceEntry.proxy.getClass().getName()); serviceEntry.administrable.getAdmin(); if(logger.isTraceEnabled()) logger.trace("Invocation to getAdmin() on : {} returned", serviceEntry.proxy.getClass().getName()); verified = true; } catch(RemoteException e) { if(logger.isDebugEnabled()) logger.debug("RemoteException reaching service, service cannot be reached"); } catch(Throwable t) { if(!ThrowableUtil.isRetryable(t)) { if(logger.isDebugEnabled()) logger.debug("Unrecoverable Exception invoking getAdmin()", t); } } return verified; } } /* * Holds a reference to a service proxy and service id */ private class ServiceEntry { Object proxy; ServiceID serviceID; Administrable administrable; ServiceItem item; ServiceEntry(ServiceItem item) { this(item.service, item.serviceID); this.item = item; } ServiceEntry(Object proxy, ServiceID serviceID) { this.proxy = proxy; this.serviceID = serviceID; if(proxy instanceof Administrable) administrable = (Administrable)proxy; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ServiceEntry that = (ServiceEntry) o; return !(serviceID != null ? !serviceID.equals(that.serviceID) : that.serviceID != null); } public int hashCode() { return serviceID != null ? serviceID.hashCode() : 0; } } }