/* $Id$ */
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.manifoldcf.agents.system;
import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.agents.interfaces.*;
import java.io.*;
import java.util.*;
public class AgentsDaemon
{
public static final String _rcsid = "@(#)$Id$";
/** Agent shutdown signal name */
public static final String agentShutdownSignal = "_AGENTRUN_";
/** Agent service name prefix (followed by agent class name) */
public static final String agentServicePrefix = "AGENT_";
/** The agents thread, which starts and stops agents daemons to keep them consistent with the database, and
* also takes on process cleanup where necessary. */
protected AgentsThread agentsThread = null;
/** The idle cleanup thread. */
protected IdleCleanupThread idleCleanupThread = null;
/** Process ID for this agents daemon. */
protected final String processID;
/** This is the place we keep track of the agents we've started. */
protected final Map<String,IAgent> runningHash = new HashMap<String,IAgent>();
// There are a number of different ways of running the agents framework.
// (1) Repeatedly call checkAgents(), and when all done make sure to call stopAgents().
// (2) Call registerAgentsShutdownHook(), then repeatedly run checkAgents(), Agent shutdown happens on JVM exit.
// (3) Call runAgents(), which will wait for someone else to call assertAgentsShutdownSignal(). Before exit, stopAgents() must be called.
// (4) Call registerAgentsShutdownHook(), then call runAgents(), which will wait for someone else to call assertAgentsShutdownSignal(). Shutdown happens on JVM exit.
/** Create an agents daemon object.
*@param processID is the process ID of this agents daemon. Process ID's must be unique
* for all agents daemons.
*/
public AgentsDaemon(String processID)
{
this.processID = processID;
}
/** Assert shutdown signal for the current agents daemon.
*/
public static void assertAgentsShutdownSignal(IThreadContext threadContext)
throws ManifoldCFException
{
ILockManager lockManager = LockManagerFactory.make(threadContext);
lockManager.setGlobalFlag(agentShutdownSignal);
}
/** Clear shutdown signal for the current agents daemon.
*/
public static void clearAgentsShutdownSignal(IThreadContext threadContext)
throws ManifoldCFException
{
ILockManager lockManager = LockManagerFactory.make(threadContext);
lockManager.clearGlobalFlag(agentShutdownSignal);
}
/** Register agents shutdown hook.
* Call this ONCE before calling startAgents or checkAgents the first time, if you want automatic cleanup of agents on JVM stop.
*/
public void registerAgentsShutdownHook(IThreadContext threadContext)
throws ManifoldCFException
{
// Create the shutdown hook for agents. All activity will be keyed off of runningHash, so it is safe to do this under all conditions.
org.apache.manifoldcf.core.system.ManifoldCF.addShutdownHook(new AgentsShutdownHook());
}
/** Run agents process.
* This method will not return until a shutdown signal is sent.
*/
public void runAgents(IThreadContext threadContext)
throws ManifoldCFException
{
ILockManager lockManager = LockManagerFactory.make(threadContext);
// Don't come up at all if shutdown signal in force
if (lockManager.checkGlobalFlag(agentShutdownSignal))
return;
// Create and start agents thread.
startAgents(threadContext);
while (true)
{
// Any shutdown signal yet?
if (lockManager.checkGlobalFlag(agentShutdownSignal))
break;
try
{
ManifoldCF.sleep(5000L);
}
catch (InterruptedException e)
{
break;
}
}
}
/** Start agents thread for this agents daemon object.
*/
public void startAgents(IThreadContext threadContext)
throws ManifoldCFException
{
// Create idle cleanup thread.
idleCleanupThread = new IdleCleanupThread(processID);
agentsThread = new AgentsThread();
// Create and start agents thread.
idleCleanupThread.start();
agentsThread.start();
}
/** Stop all started agents running under this agents daemon.
*/
public void stopAgents(IThreadContext threadContext)
throws ManifoldCFException
{
// Shut down agents background thread.
while (agentsThread != null || idleCleanupThread != null)
{
if (agentsThread != null)
agentsThread.interrupt();
if (idleCleanupThread != null)
idleCleanupThread.interrupt();
if (agentsThread != null && !agentsThread.isAlive())
agentsThread = null;
if (idleCleanupThread != null && !idleCleanupThread.isAlive())
idleCleanupThread = null;
}
// Shut down running agents services directly.
ILockManager lockManager = LockManagerFactory.make(threadContext);
synchronized (runningHash)
{
// This is supposedly safe; iterator remove is used
Iterator<String> iter = runningHash.keySet().iterator();
while (iter.hasNext())
{
String className = iter.next();
IAgent agent = runningHash.get(className);
// Stop it
agent.stopAgent(threadContext);
lockManager.endServiceActivity(getAgentsClassServiceType(className), processID);
iter.remove();
agent.cleanUp(threadContext);
}
}
// Done.
OutputConnectorPoolFactory.make(threadContext).flushUnusedConnectors();
TransformationConnectorPoolFactory.make(threadContext).flushUnusedConnectors();
}
protected static String getAgentsClassServiceType(String agentClassName)
{
return agentServicePrefix + agentClassName;
}
/** Agents thread. This runs in background until interrupted, at which point
* it shuts down. Its responsibilities include cleaning up after dead processes,
* as well as starting newly-registered agent processes, and terminating ones that disappear.
*/
protected class AgentsThread extends Thread
{
public AgentsThread()
{
super();
setName("Agents thread");
setDaemon(true);
}
public void run()
{
try
{
IThreadContext threadContext = ThreadContextFactory.make();
while (true)
{
try
{
if (Thread.currentThread().isInterrupted())
throw new ManifoldCFException("Interrupted",ManifoldCFException.INTERRUPTED);
checkAgents(threadContext);
ManifoldCF.sleep(5000L);
}
catch (InterruptedException e)
{
break;
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED)
break;
if (e.getErrorCode() == ManifoldCFException.SETUP_ERROR)
{
System.err.println("Misconfigured ManifoldCF agents - shutting down");
Logging.agents.fatal("AgentThread configuration exception tossed: "+e.getMessage(),e);
System.exit(-200);
}
Logging.agents.error("Exception tossed: "+e.getMessage(),e);
}
catch (OutOfMemoryError e)
{
System.err.println("Agents process ran out of memory - shutting down");
e.printStackTrace(System.err);
System.exit(-200);
}
catch (Throwable e)
{
Logging.agents.fatal("Error tossed: "+e.getMessage(),e);
}
}
}
catch (Throwable e)
{
// Severe error on initialization
System.err.println("Agents process could not start - shutting down");
Logging.agents.fatal("AgentThread initialization error tossed: "+e.getMessage(),e);
System.exit(-300);
}
}
}
/** Start all not-running agents.
*@param threadContext is the thread context.
*/
protected void checkAgents(IThreadContext threadContext)
throws ManifoldCFException
{
ILockManager lockManager = LockManagerFactory.make(threadContext);
// Get agent manager
IAgentManager manager = AgentManagerFactory.make(threadContext);
synchronized (runningHash)
{
String[] classes = manager.getAllAgents();
Set<String> currentAgentClasses = new HashSet<String>();
int i = 0;
while (i < classes.length)
{
String className = classes[i++];
if (runningHash.get(className) == null)
{
// Start this agent
IAgent agent = AgentFactory.make(className);
String serviceType = getAgentsClassServiceType(className);
agent.initialize(threadContext);
try
{
// Throw a lock, so that cleanup processes and startup processes don't collide.
lockManager.registerServiceBeginServiceActivity(serviceType, processID, new CleanupAgent(threadContext, agent, processID));
// There is a potential race condition where the agent has been started but hasn't yet appeared in runningHash.
// But having runningHash be the synchronizer for this activity will prevent any problems.
agent.startAgent(threadContext, processID);
// Successful!
runningHash.put(className,agent);
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() != ManifoldCFException.INTERRUPTED)
{
agent.cleanUp(threadContext);
lockManager.endServiceActivity(serviceType, processID);
}
throw e;
}
}
currentAgentClasses.add(className);
}
// Go through running hash and look for agents processes that have left
Iterator<String> runningAgentsIterator = runningHash.keySet().iterator();
while (runningAgentsIterator.hasNext())
{
String runningAgentClass = runningAgentsIterator.next();
if (!currentAgentClasses.contains(runningAgentClass))
{
// Shut down this one agent.
IAgent agent = runningHash.get(runningAgentClass);
// Stop it
agent.stopAgent(threadContext);
lockManager.endServiceActivity(getAgentsClassServiceType(runningAgentClass), processID);
runningAgentsIterator.remove();
agent.cleanUp(threadContext);
}
}
}
synchronized (runningHash)
{
// For every class we're supposed to be running, find registered but no-longer-active instances and clean
// up after them.
for (String agentsClass : runningHash.keySet())
{
IAgent agent = runningHash.get(agentsClass);
IServiceCleanup cleanup = new CleanupAgent(threadContext, agent, processID);
String agentsClassServiceType = getAgentsClassServiceType(agentsClass);
while (!lockManager.cleanupInactiveService(agentsClassServiceType, cleanup))
{
// Loop until no more inactive services
}
}
}
}
/** Agent cleanup class. This provides functionality to clean up after agents processes
* that have gone away, or initialize an entire cluster.
*/
protected static class CleanupAgent implements IServiceCleanup
{
protected final IAgent agent;
protected final IThreadContext threadContext;
protected final String processID;
public CleanupAgent(IThreadContext threadContext, IAgent agent, String processID)
{
this.agent = agent;
this.threadContext = threadContext;
this.processID = processID;
}
/** Clean up after the specified service. This method will block any startup of the specified
* service for as long as it runs.
*@param serviceName is the name of the service.
*/
@Override
public void cleanUpService(String serviceName)
throws ManifoldCFException
{
agent.cleanUpAgentData(threadContext, processID, serviceName);
}
/** Clean up after ALL services of the type on the cluster.
*/
@Override
public void cleanUpAllServices()
throws ManifoldCFException
{
agent.cleanUpAllAgentData(threadContext, processID);
}
/** Perform cluster initialization - that is, whatever is needed presuming that the
* cluster has been down for an indeterminate period of time, but is otherwise in a clean
* state.
*/
@Override
public void clusterInit()
throws ManifoldCFException
{
agent.clusterInit(threadContext);
}
}
/** Agents shutdown hook class */
protected class AgentsShutdownHook implements IShutdownHook
{
public AgentsShutdownHook()
{
}
@Override
public void doCleanup(IThreadContext threadContext)
throws ManifoldCFException
{
// Shutting down in this way must prevent startup from taking place.
stopAgents(threadContext);
}
}
}