NavigableMap<URI, Bundle> stopBundles(Set<URI> bundles) { NavigableMap<URI, Bundle> expect, update; do { expect = get(); update = ImmutableSortedMap.<URI, Bundle>naturalOrder() .putAll(Maps.filterKeys(expect, not(in(bundles)))) .build(); } while (!compareAndSet(expect, update)); List<URI> couldNotStop = new ArrayList<>(); NavigableMap<URI, Bundle> stopped = Maps.filterKeys(expect, in(bundles)); for (Map.Entry<URI, Bundle> e : stopped.entrySet()) { URI bundleURI = e.getKey(); Bundle bundle = e.getValue(); try { bundle.stop(); } catch (BundleException exc) { LOG.error("Failed to stop bundle " + bundleURI, exc); couldNotStop.add(bundleURI); } } if (!couldNotStop.isEmpty()) { throw new ModularException("Failed to stop bundles %s", couldNotStop); } return stopped; }
ImmutableMap<Service, Long> startupTimes() { List<Entry<Service, Long>> loadTimes; monitor.enter(); try { loadTimes = Lists.newArrayListWithCapacity(startupTimers.size()); // N.B. There will only be an entry in the map if the service has started for (Entry<Service, Stopwatch> entry : startupTimers.entrySet()) { Service service = entry.getKey(); Stopwatch stopWatch = entry.getValue(); if (!stopWatch.isRunning() && !(service instanceof NoOpService)) { loadTimes.add(Maps.immutableEntry(service, stopWatch.elapsed(MILLISECONDS))); } } } finally { monitor.leave(); } Collections.sort( loadTimes, Ordering.<Long>natural() .onResultOf( new Function<Entry<Service, Long>, Long>() { @Override public Long apply(Map.Entry<Service, Long> input) { return input.getValue(); } })); ImmutableMap.Builder<Service, Long> builder = ImmutableMap.builder(); for (Entry<Service, Long> entry : loadTimes) { builder.put(entry); } return builder.build(); }
private static Map<Integer, Long> findLocalSources( Collection<IndexSnapshotRequestConfig.PartitionRanges> partitionRanges, SiteTracker tracker) { Set<Integer> partitions = Sets.newHashSet(); for (IndexSnapshotRequestConfig.PartitionRanges partitionRange : partitionRanges) { partitions.add(partitionRange.partitionId); } Map<Integer, Long> pidToLocalHSId = Maps.newHashMap(); List<Long> localSites = Longs.asList(tracker.getLocalSites()); for (long hsId : localSites) { int pid = tracker.getPartitionForSite(hsId); if (partitions.contains(pid)) { pidToLocalHSId.put(pid, hsId); } } return pidToLocalHSId; }
Optional<Bundle> stopBundle(URI bundleURI) { NavigableMap<URI, Bundle> expect, update; do { expect = get(); update = ImmutableSortedMap.<URI, Bundle>naturalOrder() .putAll(Maps.filterKeys(expect, not(equalTo(bundleURI)))) .build(); } while (expect.containsKey(bundleURI) && !compareAndSet(expect, update)); Bundle bundle = expect.get(bundleURI); if (bundle != null) { try { bundle.stop(); } catch (BundleException e) { throw loggedModularException(e, "Failed to stop bundle %s", bundleURI); } } return Optional.ofNullable(bundle); }
/** * An encapsulation of all the mutable state of the {@link ServiceManager} that needs to be * accessed by instances of {@link ServiceListener}. */ private static final class ServiceManagerState { final Monitor monitor = new Monitor(); @GuardedBy("monitor") final SetMultimap<State, Service> servicesByState = Multimaps.newSetMultimap( new EnumMap<State, Collection<Service>>(State.class), new Supplier<Set<Service>>() { @Override public Set<Service> get() { return Sets.newLinkedHashSet(); } }); @GuardedBy("monitor") final Multiset<State> states = servicesByState.keys(); @GuardedBy("monitor") final Map<Service, Stopwatch> startupTimers = Maps.newIdentityHashMap(); /** * These two booleans are used to mark the state as ready to start. {@link #ready}: is set by * {@link #markReady} to indicate that all listeners have been correctly installed {@link * #transitioned}: is set by {@link #transitionService} to indicate that some transition has * been performed. * * <p>Together, they allow us to enforce that all services have their listeners installed prior * to any service performing a transition, then we can fail in the ServiceManager constructor * rather than in a Service.Listener callback. */ @GuardedBy("monitor") boolean ready; @GuardedBy("monitor") boolean transitioned; final int numberOfServices; /** * Controls how long to wait for all the services to either become healthy or reach a state from * which it is guaranteed that it can never become healthy. */ final Monitor.Guard awaitHealthGuard = new Monitor.Guard(monitor) { @Override public boolean isSatisfied() { // All services have started or some service has terminated/failed. return states.count(RUNNING) == numberOfServices || states.contains(STOPPING) || states.contains(TERMINATED) || states.contains(FAILED); } }; /** Controls how long to wait for all services to reach a terminal state. */ final Monitor.Guard stoppedGuard = new Monitor.Guard(monitor) { @Override public boolean isSatisfied() { return states.count(TERMINATED) + states.count(FAILED) == numberOfServices; } }; /** The listeners to notify during a state transition. */ @GuardedBy("monitor") final List<ListenerCallQueue<Listener>> listeners = Collections.synchronizedList(new ArrayList<ListenerCallQueue<Listener>>()); /** * It is implicitly assumed that all the services are NEW and that they will all remain NEW * until all the Listeners are installed and {@link #markReady()} is called. It is our caller's * responsibility to only call {@link #markReady()} if all services were new at the time this * method was called and when all the listeners were installed. */ ServiceManagerState(ImmutableCollection<Service> services) { this.numberOfServices = services.size(); servicesByState.putAll(NEW, services); } /** * Attempts to start the timer immediately prior to the service being started via {@link * Service#startAsync()}. */ void tryStartTiming(Service service) { monitor.enter(); try { Stopwatch stopwatch = startupTimers.get(service); if (stopwatch == null) { startupTimers.put(service, Stopwatch.createStarted()); } } finally { monitor.leave(); } } /** * Marks the {@link State} as ready to receive transitions. Returns true if no transitions have * been observed yet. */ void markReady() { monitor.enter(); try { if (!transitioned) { // nothing has transitioned since construction, good. ready = true; } else { // This should be an extremely rare race condition. List<Service> servicesInBadStates = Lists.newArrayList(); for (Service service : servicesByState().values()) { if (service.state() != NEW) { servicesInBadStates.add(service); } } throw new IllegalArgumentException( "Services started transitioning asynchronously before " + "the ServiceManager was constructed: " + servicesInBadStates); } } finally { monitor.leave(); } } void addListener(Listener listener, Executor executor) { checkNotNull(listener, "listener"); checkNotNull(executor, "executor"); monitor.enter(); try { // no point in adding a listener that will never be called if (!stoppedGuard.isSatisfied()) { listeners.add(new ListenerCallQueue<Listener>(listener, executor)); } } finally { monitor.leave(); } } void awaitHealthy() { monitor.enterWhenUninterruptibly(awaitHealthGuard); try { checkHealthy(); } finally { monitor.leave(); } } void awaitHealthy(long timeout, TimeUnit unit) throws TimeoutException { monitor.enter(); try { if (!monitor.waitForUninterruptibly(awaitHealthGuard, timeout, unit)) { throw new TimeoutException( "Timeout waiting for the services to become healthy. The " + "following services have not started: " + Multimaps.filterKeys(servicesByState, in(ImmutableSet.of(NEW, STARTING)))); } checkHealthy(); } finally { monitor.leave(); } } void awaitStopped() { monitor.enterWhenUninterruptibly(stoppedGuard); monitor.leave(); } void awaitStopped(long timeout, TimeUnit unit) throws TimeoutException { monitor.enter(); try { if (!monitor.waitForUninterruptibly(stoppedGuard, timeout, unit)) { throw new TimeoutException( "Timeout waiting for the services to stop. The following " + "services have not stopped: " + Multimaps.filterKeys( servicesByState, not(in(ImmutableSet.of(TERMINATED, FAILED))))); } } finally { monitor.leave(); } } ImmutableMultimap<State, Service> servicesByState() { ImmutableSetMultimap.Builder<State, Service> builder = ImmutableSetMultimap.builder(); monitor.enter(); try { for (Entry<State, Service> entry : servicesByState.entries()) { if (!(entry.getValue() instanceof NoOpService)) { builder.put(entry.getKey(), entry.getValue()); } } } finally { monitor.leave(); } return builder.build(); } ImmutableMap<Service, Long> startupTimes() { List<Entry<Service, Long>> loadTimes; monitor.enter(); try { loadTimes = Lists.newArrayListWithCapacity(startupTimers.size()); // N.B. There will only be an entry in the map if the service has started for (Entry<Service, Stopwatch> entry : startupTimers.entrySet()) { Service service = entry.getKey(); Stopwatch stopWatch = entry.getValue(); if (!stopWatch.isRunning() && !(service instanceof NoOpService)) { loadTimes.add(Maps.immutableEntry(service, stopWatch.elapsed(MILLISECONDS))); } } } finally { monitor.leave(); } Collections.sort( loadTimes, Ordering.<Long>natural() .onResultOf( new Function<Entry<Service, Long>, Long>() { @Override public Long apply(Map.Entry<Service, Long> input) { return input.getValue(); } })); ImmutableMap.Builder<Service, Long> builder = ImmutableMap.builder(); for (Entry<Service, Long> entry : loadTimes) { builder.put(entry); } return builder.build(); } /** * Updates the state with the given service transition. * * <p>This method performs the main logic of ServiceManager in the following steps. * * <ol> * <li>Update the {@link #servicesByState()} * <li>Update the {@link #startupTimers} * <li>Based on the new state queue listeners to run * <li>Run the listeners (outside of the lock) * </ol> */ void transitionService(final Service service, State from, State to) { checkNotNull(service); checkArgument(from != to); monitor.enter(); try { transitioned = true; if (!ready) { return; } // Update state. checkState( servicesByState.remove(from, service), "Service %s not at the expected location in the state map %s", service, from); checkState( servicesByState.put(to, service), "Service %s in the state map unexpectedly at %s", service, to); // Update the timer Stopwatch stopwatch = startupTimers.get(service); if (stopwatch == null) { // This means the service was started by some means other than ServiceManager.startAsync stopwatch = Stopwatch.createStarted(); startupTimers.put(service, stopwatch); } if (to.compareTo(RUNNING) >= 0 && stopwatch.isRunning()) { // N.B. if we miss the STARTING event then we may never record a startup time. stopwatch.stop(); if (!(service instanceof NoOpService)) { logger.log(Level.FINE, "Started {0} in {1}.", new Object[] {service, stopwatch}); } } // Queue our listeners // Did a service fail? if (to == FAILED) { fireFailedListeners(service); } if (states.count(RUNNING) == numberOfServices) { // This means that the manager is currently healthy. N.B. If other threads call isHealthy // they are not guaranteed to get 'true', because any service could fail right now. fireHealthyListeners(); } else if (states.count(TERMINATED) + states.count(FAILED) == numberOfServices) { fireStoppedListeners(); } } finally { monitor.leave(); // Run our executors outside of the lock executeListeners(); } } @GuardedBy("monitor") void fireStoppedListeners() { STOPPED_CALLBACK.enqueueOn(listeners); } @GuardedBy("monitor") void fireHealthyListeners() { HEALTHY_CALLBACK.enqueueOn(listeners); } @GuardedBy("monitor") void fireFailedListeners(final Service service) { new Callback<Listener>("failed({service=" + service + "})") { @Override void call(Listener listener) { listener.failure(service); } }.enqueueOn(listeners); } /** Attempts to execute all the listeners in {@link #listeners}. */ void executeListeners() { checkState( !monitor.isOccupiedByCurrentThread(), "It is incorrect to execute listeners with the monitor held."); // iterate by index to avoid concurrent modification exceptions for (int i = 0; i < listeners.size(); i++) { listeners.get(i).execute(); } } @GuardedBy("monitor") void checkHealthy() { if (states.count(RUNNING) != numberOfServices) { IllegalStateException exception = new IllegalStateException( "Expected to be healthy after starting. The following services are not running: " + Multimaps.filterKeys(servicesByState, not(equalTo(RUNNING)))); throw exception; } } }
/** * Returns a new {@code TypeResolver} with type variables in {@code formal} mapping to types in * {@code actual}. * * <p>For example, if {@code formal} is a {@code TypeVariable T}, and {@code actual} is {@code * String.class}, then {@code new TypeResolver().where(formal, actual)} will {@linkplain * #resolveType resolve} {@code ParameterizedType List<T>} to {@code List<String>}, and resolve * {@code Map<T, Something>} to {@code Map<String, Something>} etc. Similarly, {@code formal} and * {@code actual} can be {@code Map<K, V>} and {@code Map<String, Integer>} respectively, or they * can be {@code E[]} and {@code String[]} respectively, or even any arbitrary combination * thereof. * * @param formal The type whose type variables or itself is mapped to other type(s). It's almost * always a bug if {@code formal} isn't a type variable and contains no type variable. Make * sure you are passing the two parameters in the right order. * @param actual The type that the formal type variable(s) are mapped to. It can be or contain yet * other type variables, in which case these type variables will be further resolved if * corresponding mappings exist in the current {@code TypeResolver} instance. */ public TypeResolver where(Type formal, Type actual) { Map<TypeVariable<?>, Type> mappings = Maps.newHashMap(); populateTypeMappings(mappings, checkNotNull(formal), checkNotNull(actual)); return where(mappings); }
private static final class TypeMappingIntrospector extends TypeVisitor { private static final WildcardCapturer wildcardCapturer = new WildcardCapturer(); private final Map<TypeVariable<?>, Type> mappings = Maps.newHashMap(); /** * Returns type mappings using type parameters and type arguments found in the generic * superclass and the super interfaces of {@code contextClass}. */ static ImmutableMap<TypeVariable<?>, Type> getTypeMappings(Type contextType) { TypeMappingIntrospector introspector = new TypeMappingIntrospector(); introspector.visit(wildcardCapturer.capture(contextType)); return ImmutableMap.copyOf(introspector.mappings); } @Override void visitClass(Class<?> clazz) { visit(clazz.getGenericSuperclass()); visit(clazz.getGenericInterfaces()); } @Override void visitParameterizedType(ParameterizedType parameterizedType) { Class<?> rawClass = (Class<?>) parameterizedType.getRawType(); TypeVariable<?>[] vars = rawClass.getTypeParameters(); Type[] typeArgs = parameterizedType.getActualTypeArguments(); checkState(vars.length == typeArgs.length); for (int i = 0; i < vars.length; i++) { map(vars[i], typeArgs[i]); } visit(rawClass); visit(parameterizedType.getOwnerType()); } @Override void visitTypeVariable(TypeVariable<?> t) { visit(t.getBounds()); } @Override void visitWildcardType(WildcardType t) { visit(t.getUpperBounds()); } private void map(final TypeVariable<?> var, final Type arg) { if (mappings.containsKey(var)) { // Mapping already established // This is possible when following both superClass -> enclosingClass // and enclosingclass -> superClass paths. // Since we follow the path of superclass first, enclosing second, // superclass mapping should take precedence. return; } // First, check whether var -> arg forms a cycle for (Type t = arg; t != null; t = mappings.get(t)) { if (var.equals(t)) { // cycle detected, remove the entire cycle from the mapping so that // each type variable resolves deterministically to itself. // Otherwise, a F -> T cycle will end up resolving both F and T // nondeterministically to either F or T. for (Type x = arg; x != null; x = mappings.remove(x)) {} return; } } mappings.put(var, arg); } }