All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openbp.server.engine.EngineRunner Maven / Gradle / Ivy

There is a newer version: 0.9.11
Show newest version
package org.openbp.server.engine;

import java.util.Iterator;

import org.openbp.common.logger.LogUtil;
import org.openbp.server.context.LifecycleRequest;
import org.openbp.server.context.LifecycleState;
import org.openbp.server.context.TokenContext;
import org.openbp.server.context.TokenContextService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * The task of the engine runner is to scan for tasks that are ready to execute and invoke the process engine on them.
 * Derived classes of this base class may provide strategies that distribute the processes that are due for
 * execution to a number of threads.
 *
 * Note that all execution methods of this class keep track of currently executing contexts,
 * allowing to determine the overall state of the process engine system concerning context execution.
 * For details, see the {@link #getNumberOfExecutingContexts} and {@link #waitForStop} methods.
 *
 * @author Heiko Erhardt
 */
public abstract class EngineRunner
{
	@Autowired
	private Engine engine;

	/** Engine runner exception handler */
	private EngineRunnerExceptionHandler engineRunnerExceptionHandler;

	/** System name provider */
	private SystemNameProvider systemNameProvider = new DefaultSystemNameProvider();

	/** Number of executing contexts */
	private int numberOfExecutingContexts;

	private Object numberSemaphore = new Object();

	/** Maximum number of records to fetch in each iteration of the main loop or 0 for all */
	private int fetchSize;

	/** Idle time for {@link #mainExecutionLoop()} in milli seconds When there are no contexts available for execution */
	private int idleTime;

	/** Flag if the execution loop shall be stopped */
	private boolean executionLoopStopRequested;

	/** Flag if the main execution loop is currently running */
	private boolean executionLoopRuning;

	/**
	 * Default constructor.
	 */
	public EngineRunner()
	{
	}

	/**
	 * Main execution loop.
	 * Use this method in the thread that reads pending token contexts and distributes them for execution.
	 * The method returns after the {@link #requestExecutionLoopEnd} has been called and the {@link #setIdleTime} has elapsed.
	 */
	public void mainExecutionLoop()
	{
		mainExecutionLoop(idleTime);
	}

	/**
	 * Main execution loop.
	 * Use this method in the thread that reads pending token contexts and distributes them for execution.
	 * The method returns after the {@link #requestExecutionLoopEnd} has been called and the sleepTime has elapsed.
	 * @param sleepTime When there are no contexts available for execution,
	 * the method will sleep for the supplied sleep time in milli seconds.
	 */
	public void mainExecutionLoop(final int sleepTime)
	{
		try
		{
			executionLoopRuning = true;

			while (! executionLoopStopRequested)
			{
				executePendingContextsInDifferentThread();

				// This will clear the o/r mapper cache, so we won't have caching problems.
				getEngine().getTokenContextService().clearCache();

				try
				{
					Thread.sleep(sleepTime);
				}
				catch (InterruptedException e)
				{
				}
			}
		}
		finally
		{
			executionLoopRuning = false;
		}
	}

	/**
	 * Checks if the main execution loop is currently running.
	 */
	public boolean isExecutionLoopRuning()
	{
		return executionLoopRuning;
	}

	/**
	 * Requests the end of the main execution loop.
	 * The method will request the end of the main execution loop.
	 * Use the {@link #waitForStop} method to check if execution has ended.
	 */
	public void requestExecutionLoopEnd()
	{
		if (isExecutionLoopRuning())
		{
			executionLoopStopRequested = true;
		}
	}

	/**
	 * Tries to revoke a requested termination of the main execution loop.
	 * Use this method to cancel a pending termination of the main execution loop caused by a call to {@link #requestExecutionLoopEnd}.
	 * You may check if the execution loop is still running by calling the method {@link #isExecutionLoopRuning}.
	 */
	public void revokeExecutionLoopEnd()
	{
		executionLoopStopRequested = false;
	}

	// Check all 100 ms for execution stop
	private static final long STOP_CHECK_INTERVAL = 100L;

	/**
	 * Requests the end of the main execution loop and wait until all currently
	 * executing contexts have come to an halt.
	 * The method will return if either all contexts have finished executing
	 * (i. e. reached a wait state or have ended) or if the specified timeout has elapsed.
	 *
	 * @param timeoutMS Timeout in milliseconds.
	 * If this value is 0, the method will just check if everything has completed, but will not wait for any processes.
	 * If this value is -1, no timeout will apply (i. e. the method will definately wait
	 * until all context executions have finished).
	 * @return true If no context is currently executing, false if the timeout has elapsed
	 */
	public boolean waitForStop(long timeoutMS)
	{
		// Make sure we request the end of the execution loop
		requestExecutionLoopEnd();

		// Poll for end of execution
		long remaining = timeoutMS;
		for (;;)
		{
			if (! isExecutionLoopRuning() && getNumberOfExecutingContexts() == 0)
				return true;

			if (timeoutMS >= 0)
			{
				// Timeout check
				if (remaining <= 0)
				{
					// Timeout elapsed
					break;
				}

				try
				{
					long sleepTime = STOP_CHECK_INTERVAL;
					if (sleepTime > remaining)
						sleepTime = remaining;
					remaining -= sleepTime;

					Thread.sleep(sleepTime);
				}
				catch (InterruptedException e)
				{
				}
			}
		}

		return false;
	}

	/**
	 * Executes token contexts that are ready for execution in a different thread.
	 * The method will query the executable contexts using the token context service.
	 * Each context retrieved will be executed using the thread distribution
	 * strategy of the particular {@link EngineRunner} implementation.
	 * The method returns after it has processed (i. e. distributed to the threads)
	 * the last element of the context list returned by the
	 * {@link TokenContextService#getExecutableContexts} method.
	 * Since the execution will be asynchronously, this does not mean that the contexts
	 * have finished their execution at this point in time.
	 * @return The number of contexts that have been retrieved for execution and passed to the thread pool
	 */
	public int executePendingContextsInDifferentThread()
	{
		TokenContextService tcs = getEngine().getTokenContextService();
		int nAcceptedContexts = 0;

		try
		{
			for (Iterator it = tcs.getExecutableContexts(fetchSize); it.hasNext() && ! executionLoopStopRequested;)
			{
				TokenContext context = (TokenContext) it.next();

				if (! runContext(context))
					break;

				++nAcceptedContexts;
			}
		}
		finally
		{
			// Reset the token context service
			getEngine().rollback();
		}

		return nAcceptedContexts;
	}

	/**
	 * Executes token contexts that are ready for execution immediately in this thread.
	 * The method will query the executable contexts using the token context service.
	 * Each context retrieved will be executed directly.
	 * The method returns after it has processed (i. e. executed)
	 * the last element of the context list returned by the
	 * {@link TokenContextService#getExecutableContexts} method.
	 * Since this will block the thread, this method is intended for test cases primarily.
	 * @return true if the method has found contexts for execution, false otherwise
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public boolean executePendingContextsInThisThread()
	{
		boolean found = false;

		getEngine().begin();
		try
		{
			// If an exception happens during context execution,
			// we will leave the loop and check for executable contexts again the next time.
			for (Iterator it = getEngine().getTokenContextService().getExecutableContexts(0); it.hasNext() && ! executionLoopStopRequested;)
			{
				TokenContext context = (TokenContext) it.next();
				found = true;

				executeContextInThisThread(context);
			}
		}
		catch (RuntimeException e)
		{
			getEngine().rollback();
			throw e;
		}
		finally
		{
			getEngine().commit();
		}

		return found;
	}

	/**
	 * Executes the given context immediately in this thread.
	 * Since this will block the thread, this method is intended for test cases and for situations where
	 * the completion of a process execution is needed in order to continue the program.
	 *
	 * @param context Context to execute
	 */
	public void executeContextInThisThread(TokenContext context)
	{
		try
		{
			increaseNumberOfExecutingContexts();

			if (context.getLifecycleRequest() != LifecycleRequest.RESUME)
			{
				LogUtil.debug(getClass(), "Trying to run context that does not have a resumption request: $0.", context);
				return;
			}

			// We don't commit here because Engine.executeContext will do this promptly for us, so no need for two successive commits.
			engine.changeTokenState(context, LifecycleState.SELECTED, context.getLifecycleRequest());
			context.setNodeId(systemNameProvider.getSystemName());

			getEngine().executeContext(context);
		}
		finally
		{
			decreaseNumberOfExecutingContexts();
		}
	}

	/**
	 * Tries to run the given context
	 *
	 * @param context Context to execute
	 * @return true if the calling method may proceed to the next context,
	 * false to stop iterating the available contexts
	 * (e. g. since the execution thread pool is currently full).
	 */
	protected abstract boolean runContext(TokenContext context);

	/**
	 * Prepares a runnable object that can be used to execute a token context.
	 * The method will also the the lifecycle state of the context to {@link LifecycleState#SELECTED}.
	 *
	 * @param context Context to execute
	 * @return The new EngineRunnable object that refers the given context or null if an engine event observer
	 * of the vetoed the context execution 
	 */
	protected EngineRunnable prepareEngineRunnable(TokenContext context)
	{
		if (engine.hasActiveObservers(CancelableEngineEvent.SHALL_EXECUTE_TOKEN, context))
		{
			CancelableEngineEvent event = new CancelableEngineEvent(CancelableEngineEvent.SHALL_EXECUTE_TOKEN, context, engine);
			engine.fireEngineEvent(event);
			if (event.isCanceled())
			{
				// Event observer vetoed context execution
				return null;
			}
		}

		// Update the state of the context
		engine.changeTokenState(context, LifecycleState.SELECTED, context.getLifecycleRequest());
		context.setNodeId(systemNameProvider.getSystemName());

		getEngine().getTokenContextService().saveContext(context);
		engine.commit();

		// Run it using the engine thread pool
		Object id = context.getId();
		EngineRunnable runnable = new EngineRunnable(this, id);
		return runnable;
	}

	/**
	 * Gets the maximum number of records to fetch in each iteration of the main loop or 0 for all.
	 */
	public int getFetchSize()
	{
		return fetchSize;
	}

	/**
	 * Sets the maximum number of records to fetch in each iteration of the main loop or 0 for all.
	 */
	public void setFetchSize(int fetchSize)
	{
		this.fetchSize = fetchSize;
	}

	/**
	 * Gets the idle time for {@link #mainExecutionLoop()} in milli seconds When there are no contexts available for execution.
	 */
	public int getIdleTime()
	{
		return idleTime;
	}

	/**
	 * Sets the idle time for {@link #mainExecutionLoop()}  in milli seconds When there are no contexts available for execution.
	 */
	public void setIdleTime(int idleTime)
	{
		this.idleTime = idleTime;
	}

	/**
	 * Gets the number of executing contexts.
	 */
	public int getNumberOfExecutingContexts()
	{
		synchronized(numberSemaphore)
		{
			return numberOfExecutingContexts;
		}
	}

	/**
	 * Increases the number of currently executing contexts.
	 * Usually, you should not call this method from the application code!
	 */
	public void increaseNumberOfExecutingContexts()
	{
		synchronized(numberSemaphore)
		{
			++numberOfExecutingContexts;
		}
	}

	/**
	 * Decreases the number of currently executing contexts.
	 * Usually, you should not call this method from the application code!
	 */
	public void decreaseNumberOfExecutingContexts()
	{
		synchronized(numberSemaphore)
		{
			--numberOfExecutingContexts;
		}
	}

	/**
	 * Gets the engine.
	 */
	public Engine getEngine()
	{
		return engine;
	}

	/**
	 * Sets the engine.
	 */
	public void setEngine(final Engine engine)
	{
		this.engine = engine;
	}

	/**
	 * Gets the engine runner exception handler.
	 */
	public EngineRunnerExceptionHandler getEngineRunnerExceptionHandler()
	{
		return engineRunnerExceptionHandler;
	}

	/**
	 * Sets the engine runner exception handler.
	 */
	public void setEngineRunnerExceptionHandler(EngineRunnerExceptionHandler engineRunnerExceptionHandler)
	{
		this.engineRunnerExceptionHandler = engineRunnerExceptionHandler;
	}

	/**
	 * Gets the system name provider.
	 */
	public SystemNameProvider getSystemNameProvider()
	{
		return systemNameProvider;
	}

	/**
	 * Sets the system name provider.
	 */
	public void setSystemNameProvider(SystemNameProvider systemNameProvider)
	{
		this.systemNameProvider = systemNameProvider;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy