/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.controller; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.msc.service.AbstractServiceListener; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.ServiceRegistry; import org.jboss.msc.service.StabilityMonitor; import org.jboss.msc.service.StartException; import static org.jboss.as.controller.logging.ControllerLogger.ROOT_LOGGER; /** * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> * @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a> */ public final class ContainerStateMonitor extends AbstractServiceListener<Object> { private final ServiceRegistry serviceRegistry; private final StabilityMonitor monitor = new StabilityMonitor(); final Set<ServiceController<?>> failed = new HashSet<ServiceController<?>>(); final Set<ServiceController<?>> problems = new HashSet<ServiceController<?>>(); private Set<ServiceName> previousMissingDepSet = new HashSet<ServiceName>(); ContainerStateMonitor(final ServiceRegistry registry) { serviceRegistry = registry; } /** * Log a report of any problematic container state changes and reset container state change history * so another run of this method or of {@link #awaitContainerStateChangeReport(long, java.util.concurrent.TimeUnit)} * will produce a report not including any changes included in a report returned by this run. */ void logContainerStateChangesAndReset() { ContainerStateChangeReport changeReport = createContainerStateChangeReport(true); if (changeReport != null) { final String msg = createChangeReportLogMessage(changeReport); ROOT_LOGGER.info(msg); } } @Override public void listenerAdded(final ServiceController<?> controller) { monitor.addController(controller); controller.removeListener(this); } /** * Await service container stability ignoring thread interruption. * * @param timeout maximum period to wait for service container stability * @param timeUnit unit in which {@code timeout} is expressed * * @throws java.util.concurrent.TimeoutException if service container stability is not reached before the specified timeout */ void awaitStabilityUninterruptibly(long timeout, TimeUnit timeUnit) throws TimeoutException { boolean interrupted = false; try { long toWait = timeUnit.toMillis(timeout); long msTimeout = System.currentTimeMillis() + toWait; while (true) { if (interrupted) { toWait = msTimeout - System.currentTimeMillis(); } try { if (toWait <= 0 || !monitor.awaitStability(toWait, TimeUnit.MILLISECONDS, failed, problems)) { throw new TimeoutException(); } break; } catch (InterruptedException e) { interrupted = true; } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } /** * Await service container stability. * * @param timeout maximum period to wait for service container stability * @param timeUnit unit in which {@code timeout} is expressed * * @throws java.lang.InterruptedException if the thread is interrupted while awaiting service container stability * @throws java.util.concurrent.TimeoutException if service container stability is not reached before the specified timeout */ void awaitStability(long timeout, TimeUnit timeUnit) throws InterruptedException, TimeoutException { if (!monitor.awaitStability(timeout, timeUnit, failed, problems)) { throw new TimeoutException(); } } /** * Await service container stability and then report on container state changes. Does not reset change history, * so another run of this method with no intervening call to {@link #logContainerStateChangesAndReset()} * will produce a report including any changes included in a report returned by the first run. * * @param timeout maximum period to wait for service container stability * @param timeUnit unit in which {@code timeout} is expressed * * @return a change report, or {@code null} if there is nothing to report * * @throws java.lang.InterruptedException if the thread is interrupted while awaiting service container stability * @throws java.util.concurrent.TimeoutException if service container stability is not reached before the specified timeout */ ContainerStateChangeReport awaitContainerStateChangeReport(long timeout, TimeUnit timeUnit) throws InterruptedException, TimeoutException { if (monitor.awaitStability(timeout, timeUnit, failed, problems)) { return createContainerStateChangeReport(false); } throw new TimeoutException(); } /** * Creates a data structure reporting recent favorable and unfavorable changes in the state of installed services. * * @param resetHistory {@code true} if history tracking state used for detecting what has changed on the next * invocation of this method should be reset (meaning the next run will detect * more changes); {@code false} if the current history should be retained * (meaning the next run will act as if this run never happened) * * @return the report, or {@code null} if there is nothing noteworthy to report; i.e. no newly failed or missing * services and no newly corrected services */ private synchronized ContainerStateChangeReport createContainerStateChangeReport(boolean resetHistory) { final Map<ServiceName, Set<ServiceName>> missingDeps = new HashMap<ServiceName, Set<ServiceName>>(); for (ServiceController<?> controller : problems) { for (ServiceName missing : controller.getImmediateUnavailableDependencies()) { Set<ServiceName> dependents = missingDeps.get(missing); if (dependents == null) { dependents = new HashSet<ServiceName>(); missingDeps.put(missing, dependents); } dependents.add(controller.getName()); } } final Set<ServiceName> previousMissing = previousMissingDepSet; // no longer missing deps... final Map<ServiceName, Boolean> noLongerMissingServices = new TreeMap<ServiceName, Boolean>(); for (ServiceName name : previousMissing) { if (! missingDeps.containsKey(name)) { ServiceController<?> controller = serviceRegistry.getService(name); noLongerMissingServices.put(name, controller != null); } } // newly missing deps final Map<ServiceName, MissingDependencyInfo> missingServices = new TreeMap<ServiceName, MissingDependencyInfo>(); for (Map.Entry<ServiceName, Set<ServiceName>> entry : missingDeps.entrySet()) { final ServiceName name = entry.getKey(); if (! previousMissing.contains(name)) { ServiceController<?> controller = serviceRegistry.getService(name); boolean unavailable = controller != null; missingServices.put(name, new MissingDependencyInfo(name, unavailable, entry.getValue())); } } final Set<ServiceController<?>> currentFailedControllers = new HashSet<ServiceController<?>>(failed); if (resetHistory) { previousMissingDepSet = new HashSet<ServiceName>(missingDeps.keySet()); failed.clear(); problems.clear(); } boolean needReport = !missingServices.isEmpty() || !currentFailedControllers.isEmpty() || !noLongerMissingServices.isEmpty(); return needReport ? new ContainerStateChangeReport(missingServices, currentFailedControllers, noLongerMissingServices) : null; } private synchronized String createChangeReportLogMessage(ContainerStateChangeReport changeReport) { final StringBuilder msg = new StringBuilder(); msg.append(ControllerLogger.ROOT_LOGGER.serviceStatusReportHeader()); if (!changeReport.getMissingServices().isEmpty()) { msg.append(ControllerLogger.ROOT_LOGGER.serviceStatusReportDependencies()); for (Map.Entry<ServiceName, MissingDependencyInfo> entry : changeReport.getMissingServices().entrySet()) { if (!entry.getValue().isUnavailable()) { msg.append(ControllerLogger.ROOT_LOGGER.serviceStatusReportMissing(entry.getKey(), createDependentsString(entry.getValue().getDependents()))); } else { msg.append(ControllerLogger.ROOT_LOGGER.serviceStatusReportUnavailable(entry.getKey(), createDependentsString(entry.getValue().getDependents()))); } } } if (!changeReport.getNoLongerMissingServices().isEmpty()) { msg.append(ControllerLogger.ROOT_LOGGER.serviceStatusReportCorrected()); for (Map.Entry<ServiceName, Boolean> entry : changeReport.getNoLongerMissingServices().entrySet()) { if (entry.getValue()) { msg.append(ControllerLogger.ROOT_LOGGER.serviceStatusReportAvailable(entry.getKey())); } else { msg.append(ControllerLogger.ROOT_LOGGER.serviceStatusReportNoLongerRequired(entry.getKey())); } } } if (!changeReport.getFailedControllers().isEmpty()) { msg.append(ControllerLogger.ROOT_LOGGER.serviceStatusReportFailed()); for (ServiceController<?> controller : changeReport.getFailedControllers()) { msg.append(" ").append(controller.getName()); //noinspection ThrowableResultOfMethodCallIgnored final StartException startException = controller.getStartException(); if (startException != null) { msg.append(": ").append(startException.toString()); } msg.append('\n'); } } return msg.toString(); } public static class ContainerStateChangeReport { private final Map<ServiceName, MissingDependencyInfo> missingServices; private final Set<ServiceController<?>> failedControllers; private final Map<ServiceName, Boolean> noLongerMissingServices; private ContainerStateChangeReport(final Map<ServiceName, MissingDependencyInfo> missingServices, final Set<ServiceController<?>> failedControllers, final Map<ServiceName, Boolean> noLongerMissingServices) { this.missingServices = missingServices; this.failedControllers = failedControllers; this.noLongerMissingServices = noLongerMissingServices; } public final Set<ServiceController<?>> getFailedControllers() { return failedControllers; } public Map<ServiceName, MissingDependencyInfo> getMissingServices() { return missingServices; } /** * Gets services that are no longer considered to be missing. * @return a map of the service name of the no-longer-missing service to a boolean indicating * whether or not the service now exists ({@code true} if it does.) If {@code false} * the service is no longer "missing" because it is no longer depended upon */ public Map<ServiceName, Boolean> getNoLongerMissingServices() { return noLongerMissingServices; } } private static String createDependentsString(final Set<ServiceName> serviceNames) { if(serviceNames.size() <= 4) { return serviceNames.toString(); } else { final StringBuilder ret = new StringBuilder("["); int count = 0; Iterator<ServiceName> it = serviceNames.iterator(); while (count < 4) { final ServiceName val = it.next(); ret.append(val); ret.append(", "); ++count; } ret.append(ControllerLogger.ROOT_LOGGER.andNMore(serviceNames.size() - 3)); ret.append(" ]"); return ret.toString(); } } public static class MissingDependencyInfo { private final ServiceName serviceName; private final boolean unavailable; private final Set<ServiceName> dependents; public MissingDependencyInfo(ServiceName serviceName, boolean unavailable, final Set<ServiceName> dependents) { this.serviceName = serviceName; this.unavailable = unavailable; this.dependents = dependents; } public ServiceName getServiceName() { return serviceName; } public boolean isUnavailable() { return unavailable; } public Set<ServiceName> getDependents() { return Collections.unmodifiableSet(dependents); } } }