/*
* Copyright (C) 2011 Google Inc.
*
* 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.ros.node;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.apache.commons.logging.Log;
import org.ros.concurrent.DefaultScheduledExecutorService;
import org.ros.log.RosLogFactory;
import org.ros.namespace.GraphName;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
/**
* Executes {@link NodeMain}s in separate threads.
*
* @author damonkohler@google.com (Damon Kohler)
*/
public class DefaultNodeMainExecutor implements NodeMainExecutor {
private static final Log log = RosLogFactory.getLog(DefaultNodeMainExecutor.class);
private final NodeFactory nodeFactory;
private final ScheduledExecutorService scheduledExecutorService;
private final Multimap<GraphName, ConnectedNode> connectedNodes;
private final BiMap<Node, NodeMain> nodeMains;
private class RegistrationListener implements NodeListener {
@Override
public void onStart(ConnectedNode connectedNode) {
registerNode(connectedNode);
}
@Override
public void onShutdown(Node node) {
}
@Override
public void onShutdownComplete(Node node) {
unregisterNode(node);
}
@Override
public void onError(Node node, Throwable throwable) {
log.error("Node error.", throwable);
unregisterNode(node);
}
}
/**
* @return an instance of {@link DefaultNodeMainExecutor} that uses a
* {@link ScheduledExecutorService} that is suitable for both
* executing tasks immediately and scheduling tasks to execute in the
* future
*/
public static NodeMainExecutor newDefault() {
return newDefault(new DefaultScheduledExecutorService());
}
/**
* @return an instance of {@link DefaultNodeMainExecutor} that uses the
* supplied {@link ExecutorService}
*/
public static NodeMainExecutor newDefault(ScheduledExecutorService executorService) {
return new DefaultNodeMainExecutor(new DefaultNodeFactory(executorService), executorService);
}
/**
* @param nodeFactory
* {@link NodeFactory} to use for node creation.
* @param scheduledExecutorService
* {@link NodeMain}s will be executed using this
*/
private DefaultNodeMainExecutor(NodeFactory nodeFactory,
ScheduledExecutorService scheduledExecutorService) {
this.nodeFactory = nodeFactory;
this.scheduledExecutorService = scheduledExecutorService;
connectedNodes =
Multimaps.synchronizedMultimap(HashMultimap.<GraphName, ConnectedNode>create());
nodeMains = Maps.synchronizedBiMap(HashBiMap.<Node, NodeMain>create());
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
DefaultNodeMainExecutor.this.shutdown();
}
}));
}
@Override
public ScheduledExecutorService getScheduledExecutorService() {
return scheduledExecutorService;
}
@Override
public void execute(final NodeMain nodeMain, final NodeConfiguration nodeConfiguration,
final Collection<NodeListener> nodeListeners) {
// NOTE(damonkohler): To avoid a race condition, we have to make our copy
// of the NodeConfiguration in the current thread.
final NodeConfiguration nodeConfigurationCopy = NodeConfiguration.copyOf(nodeConfiguration);
nodeConfigurationCopy.setDefaultNodeName(nodeMain.getDefaultNodeName());
Preconditions.checkNotNull(nodeConfigurationCopy.getNodeName(), "Node name not specified.");
if (log.isDebugEnabled()) {
log.debug("Starting node: " + nodeConfigurationCopy.getNodeName());
}
scheduledExecutorService.execute(new Runnable() {
@Override
public void run() {
Collection<NodeListener> nodeListenersCopy = Lists.newArrayList();
nodeListenersCopy.add(new RegistrationListener());
nodeListenersCopy.add(nodeMain);
if (nodeListeners != null) {
nodeListenersCopy.addAll(nodeListeners);
}
// The new Node will call onStart().
Node node = nodeFactory.newNode(nodeConfigurationCopy, nodeListenersCopy);
nodeMains.put(node, nodeMain);
}
});
}
@Override
public void execute(NodeMain nodeMain, NodeConfiguration nodeConfiguration) {
execute(nodeMain, nodeConfiguration, null);
}
@Override
public void shutdownNodeMain(NodeMain nodeMain) {
Node node = nodeMains.inverse().get(nodeMain);
if (node != null) {
safelyShutdownNode(node);
}
}
@Override
public void shutdown() {
synchronized (connectedNodes) {
for (ConnectedNode connectedNode : connectedNodes.values()) {
safelyShutdownNode(connectedNode);
}
}
}
/**
* Trap and log any exceptions while shutting down the supplied {@link Node}.
*
* @param node
* the {@link Node} to shut down
*/
private void safelyShutdownNode(Node node) {
boolean success = true;
try {
node.shutdown();
} catch (Exception e) {
// Ignore spurious errors during shutdown.
log.error("Exception thrown while shutting down node.", e);
// We don't expect any more callbacks from a node that throws an exception
// while shutting down. So, we unregister it immediately.
unregisterNode(node);
success = false;
}
if (success) {
log.info("Shutdown successful.");
}
}
/**
* Register a {@link ConnectedNode} with the {@link NodeMainExecutor}.
*
* @param connectedNode
* the {@link ConnectedNode} to register
*/
private void registerNode(ConnectedNode connectedNode) {
GraphName nodeName = connectedNode.getName();
synchronized (connectedNodes) {
for (ConnectedNode illegalConnectedNode : connectedNodes.get(nodeName)) {
System.err.println(String.format(
"Node name collision. Existing node %s (%s) will be shutdown.", nodeName,
illegalConnectedNode.getUri()));
illegalConnectedNode.shutdown();
}
connectedNodes.put(nodeName, connectedNode);
}
}
/**
* Unregister a {@link Node} with the {@link NodeMainExecutor}.
*
* @param node
* the {@link Node} to unregister
*/
private void unregisterNode(Node node) {
connectedNodes.get(node.getName()).remove(node);
nodeMains.remove(node);
}
}