/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio;
import alluxio.resource.LockResource;
import alluxio.util.CommonUtils;
import alluxio.util.WaitForOptions;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.concurrent.ThreadSafe;
/**
* Class for registering individual {@link Server}s that run within an Alluxio process. The intended
* use of this class is for the Alluxio process to register the individual {@link Server}s using an
* instance of this class. The registry is passed as an argument to constructors of individual
* {@link Server}s who are expected to add themselves to the registry.
*
* The reason for using an instance as opposed to a static class is to enable dependency
* injection in tests.
*
* @param <T> the type of the {@link Server}
* @param <U> the type of the {@link Server#start} method options
*/
@ThreadSafe
public class Registry<T extends Server<U>, U> {
private static final int DEFAULT_GET_TIMEOUT_MS = 5000;
private final Map<Class<? extends Server>, T> mRegistry = new HashMap<>();
private final Lock mLock = new ReentrantLock();
/**
* Creates a new instance of {@link Registry}.
*/
public Registry() {}
/**
* Convenience method for calling {@link #get(Class, int)} with a default timeout.
*
* @param clazz the class of the {@link Server} to get
* @param <W> the type of the {@link Server} to get
* @return the {@link Server} instance
*/
public <W extends T> W get(final Class<W> clazz) {
return get(clazz, DEFAULT_GET_TIMEOUT_MS);
}
/**
* Attempts to look up the {@link Server} for the given class. Because {@link Server}s can be
* looked up and added to the registry in parallel, the lookup is retried until the given timeout
* period has elapsed.
*
* @param clazz the class of the {@link Server} to get
* @param timeoutMs timeout for looking up the server
* @param <W> the type of the {@link Server} to get
* @return the {@link Server} instance
*/
public <W extends T> W get(final Class<W> clazz, int timeoutMs) {
CommonUtils.waitFor("server " + clazz.getName() + " to be created",
new Function<Void, Boolean>() {
@Override
public Boolean apply(Void input) {
try (LockResource r = new LockResource(mLock)) {
return mRegistry.get(clazz) != null;
}
}
}, WaitForOptions.defaults().setTimeout(timeoutMs));
T server = mRegistry.get(clazz);
if (!(clazz.isInstance(server))) {
throw new RuntimeException("Server is not an instance of " + clazz.getName());
}
return clazz.cast(server);
}
/**
* @param clazz the class of the {@link Server} to add
* @param server the {@link Server} to add
* @param <W> the type of the {@link Server} to add
*/
public <W extends T> void add(Class<W> clazz, T server) {
try (LockResource r = new LockResource(mLock)) {
mRegistry.put(clazz, server);
}
}
/**
* @return a list of all the registered {@link Server}s, order by dependency relation
*/
public List<T> getServers() {
List<T> servers = new ArrayList<>(mRegistry.values());
Collections.sort(servers, new DependencyComparator());
return servers;
}
/**
* Starts all {@link Server}s in dependency order. If A depends on B, A is started before B.
*
* If a {@link Server} fails to start, already-started {@link Server}s will be stopped.
*
* @param options the start options
*/
public void start(U options) throws IOException {
List<T> servers = new ArrayList<>();
for (T server : getServers()) {
try {
server.start(options);
servers.add(server);
} catch (IOException e) {
for (T started : servers) {
started.stop();
}
throw e;
}
}
}
/**
* Stops all {@link Server}s in reverse dependency order. If A depends on B, B is stopped
* before A.
*/
public void stop() throws IOException {
for (T server : Lists.reverse(getServers())) {
server.stop();
}
}
/**
* Computes a transitive closure of the {@link Server} dependencies.
*
* @param server the {@link Server} to compute transitive dependencies for
* @return the transitive dependencies
*/
private Set<T> getTransitiveDeps(T server) {
Set<T> result = new HashSet<>();
Deque<T> queue = new ArrayDeque<>();
queue.add(server);
while (!queue.isEmpty()) {
Set<Class<? extends Server>> deps = queue.pop().getDependencies();
if (deps == null) {
continue;
}
for (Class<? extends Server> clazz : deps) {
T dep = mRegistry.get(clazz);
if (dep == null) {
continue;
}
if (dep.equals(server)) {
throw new RuntimeException("Dependency cycle encountered");
}
if (result.contains(dep)) {
continue;
}
queue.add(dep);
result.add(dep);
}
}
return result;
}
/**
* Used for computing topological sort of {@link Server}s with respect to the dependency relation.
*/
private final class DependencyComparator implements Comparator<T> {
/**
* Creates a new instance of {@link DependencyComparator}.
*/
DependencyComparator() {}
@Override
public int compare(T left, T right) {
Set<T> leftDeps = getTransitiveDeps(left);
Set<T> rightDeps = getTransitiveDeps(right);
if (leftDeps.contains(right)) {
return 1;
}
if (rightDeps.contains(left)) {
return -1;
}
return left.getName().compareTo(right.getName());
}
}
}