/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package acs.benchmark.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import si.ijs.maci.ComponentSpec;
import alma.ACS.ACSComponentOperations;
import alma.acs.container.ContainerServices;
/**
* <code>ConcurrentComponentAccessUtil</code> extends {@link ComponentAccessUtil}, allowing to
* instantiate and release components concurrently.
* <P>
* The class owns a thread pool to execute the tasks concurrently. The size of the thread pool
* can be set in the constructor; otherwise a default value of {@link ConcurrentComponentAccessUtil#defaultThreadNumber}
* is used.
* <P>
* This class allows to release components in parallel by executing each release in one of the
* threads of the pool.
* <P>
* Life cycle:
* <UL>
* <LI>start() must be executed before using methods from this class
* <LI>stop() must be executed when the object is not needed anymore.
* </UL>
*
* @author acaproni
*/
public class ConcurrentComponentAccessUtil extends ComponentAccessUtil {
/**
* The class to get a component in a thread of the pool
*/
private class ComponentActivator<V extends ACSComponentOperations> implements Callable<V> {
/**
* The ComponentSpec of the dynamic component to get
*/
private final ComponentSpec compSpec;
/**
* The class of the idl interface
*/
private final Class<V> idlOpInterface;
/**
* Constructor
*
* @param compSpec The ComponentSpec of the dynamic component to get
* @param idlOpInterface The class of the idl interface
*/
public ComponentActivator(ComponentSpec compSpec, Class<V> idlOpInterface) {
this.compSpec=compSpec;
this.idlOpInterface=idlOpInterface;
}
@Override
public V call() throws Exception {
V comp = getDynamicComponent(compSpec, idlOpInterface);
logger.info("ComponentActivator got component " + compSpec.component_name);
return comp;
}
}
/**
* The class to release a component in a thread
*/
private class ComponentDeactivator implements Callable<Void> {
private final String compName;
/**
* @param componentName
* @param sync may be null
*/
public ComponentDeactivator(String componentName) {
this.compName=componentName;
}
@Override
public Void call() {
// here, from a separate thread, we do a *synchronous* comp release
ConcurrentComponentAccessUtil.this.releaseComponent(compName, true);
return null;
}
}
/**
* The thread pool to add statistics to the FutureTask.
*/
public class BenchmarkTreadPoolExecutor extends ThreadPoolExecutor {
public BenchmarkTreadPoolExecutor(int corePoolSize,
int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
/**
* The {@link FutureTask} is of type {@link InstrumentedFutureTask}
*/
@Override
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new InstrumentedFutureTask<T>(runnable, value);
}
/**
* The {@link FutureTask} is of type {@link InstrumentedFutureTask}
*/
@Override
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new InstrumentedFutureTask<T>(callable);
}
/**
* Set the start execution time
*/
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
if (r instanceof InstrumentedFutureTask) {
InstrumentedFutureTask ift=(InstrumentedFutureTask)r;
ift.startTime=System.currentTimeMillis();
}
}
/**
* Set the end execution time
*/
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (r instanceof InstrumentedFutureTask) {
InstrumentedFutureTask ift=(InstrumentedFutureTask)r;
ift.endTime=System.currentTimeMillis();
}
super.afterExecute(r, t);
}
}
/**
* An instrumented future task contains fields useful for getting statistics.
* @param <T>
*/
public class InstrumentedFutureTask<T> extends FutureTask<T> {
/**
* The start time of the execution of the task
*/
private volatile long startTime=0;
/**
* The end time of the execution of the task
*/
private volatile long endTime=0;
public InstrumentedFutureTask(Callable<T> callable) {
super(callable);
}
public InstrumentedFutureTask(Runnable runnable, T result) {
super(runnable, result);
}
/**
* @return the startTime
*/
public long getStartTime() {
return startTime;
}
/**
* @return the endTime
*/
public long getEndTime() {
return endTime;
}
/**
* Return the execution time calculated from the start and the end time.
* <P>
* The execution time is available only when the task has been executed.
*
* @return The execution
* @throws Exception If the execution time is read before the task terminates
*/
public long getExecutionTime() throws Exception {
if (isDone()) {
return endTime-startTime;
}
throw new Exception("Execution time not yet available");
}
}
/**
* The default number of threads.
* <P>
* The number of threads is set in the constructor. The default is used if such
* a number is not specified in the constructor.
*/
private static final int defaultThreadNumber = 100;
/**
* Number of threads to concurrently start/release components
*/
private final int totThreads;
/**
* The executor service to concurrently activate the components
*/
private final BenchmarkTreadPoolExecutor executor;
/**
* Constructor.
* <P>
* Build the ConcurrentComponentAccessUtil with a default number of threads
*
* @param contSrv The {@link ContainerServices}
*/
public ConcurrentComponentAccessUtil(ContainerServices contSrv) {
this(contSrv, defaultThreadNumber);
}
/**
* Constructor.
*
* @param contSrv The {@link ContainerServices}
* @param threadPoolSize The number of threads in the thread pool
*/
public ConcurrentComponentAccessUtil(ContainerServices contSrv, int threadPoolSize) {
this(contSrv, threadPoolSize, false);
}
/**
* Constructor.
*
* @param contSrv The {@link ContainerServices}
* @param threadPoolSize The number of threads in the thread pool
* @param prestartCoreThreads
*/
public ConcurrentComponentAccessUtil(ContainerServices contSrv, int threadPoolSize, boolean prestartCoreThreads) {
super(contSrv);
totThreads = threadPoolSize;
executor = new BenchmarkTreadPoolExecutor(totThreads, totThreads, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(), contSrv.getThreadFactory());
executor.allowCoreThreadTimeOut(true);
if (prestartCoreThreads) {
int n = executor.prestartAllCoreThreads();
logger.info(n+" threads pre-started");
}
}
/**
* Life cycle method: this method must be called before using methods from this class
*/
public void start() {
// TODO: Say if it's intended to have this empty, but keep the method for future use.
}
/**
* Life cycle method: this method must be called when the object is not needed
* anymore
*/
public void stop() {
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.MINUTES)) {
executor.shutdownNow();
}
} catch (InterruptedException ie) {
executor.shutdownNow();
}
}
/**
* Releases a component in a dedicated thread of the pool.
* <p>
* Note that <code>ComponentAccessUtil#releaseComponent(compName, waitForCompRelease=false)</code>
* is very similar in the sense that it is also asynchronous, making an async call to the manager
* instead of spawning a new thread already here in the client.
* The main difference though is that in this method we can measure the time it takes to release a component,
* which is important for performance tests.
*
* @param name The name of the component to deactivate
* @return handle to sync up with the finishing of the component release.
*/
public Future<Void> releaseComponentConcurrent(String name) {
ComponentDeactivator deactivator = new ComponentDeactivator(name);
return executor.submit(deactivator);
}
/**
* Concurrently releases the components with the passed names.
* <p>
* This method overrides the base class version, bringing in a thread pool for parallel
* component release, instead of sequentially releaseing every component.
* Mostly makes sense for <code>waitCompsTermination == true</code>.
* <p>
* To avoid hanging tests, a generous timeout of 30 minutes gets applied.
*
* @param compNames
* The names of the components to release
* @param waitCompsTermination
* if <code>true</code> the method waits for all the threads to terminate before returning, otherwise
* returns immediately
*/
@Override
public void releaseComponents(Collection<String> compNames, boolean waitCompsTermination) {
List<Future<Void>> compReleaseFutures = new ArrayList<Future<Void>>();
for (String compName : compNames) {
compReleaseFutures.add(releaseComponentConcurrent(compName));
}
if (waitCompsTermination) {
logger.info("Awaiting termination of " + compReleaseFutures.size() + " component release requests (with a 30 min timeout).");
boolean printErrorLogs = true;
for (Future<Void> future : compReleaseFutures) {
try {
// The following get() call will block until the component release has finished,
// either normally or with an error, or if a timeout occurred.
future.get(30, TimeUnit.MINUTES);
} catch (InterruptedException ex) {
logger.log(Level.WARNING, "Async component release interrupted.", ex);
} catch (CancellationException ex) {
logger.log(Level.WARNING, "Async component release cancelled.", ex);
} catch (ExecutionException ex) {
logger.log(Level.WARNING, "Async component release failed with exception ", ex.getCause());
} catch (TimeoutException ex) {
if (printErrorLogs) {
logger.log(Level.WARNING, "Async component release timed out.", ex);
printErrorLogs = false;
}
}
}
}
}
/**
* The parallel version of {@link ComponentAccessUtil#getDynamicComponent(ComponentSpec, Class)}
* gets the dynamic component in a thread of the pool.
*
* @param compSpec The spec to start the dynamic component
* @param idlOpInterface The idl interface of the component
* @return the future task to be able to get the result of the activation of the component
*/
public <T extends ACSComponentOperations> Future<T> getDynamicComponentConcurrent(
ComponentSpec compSpec,
Class<T> idlOpInterface) {
ComponentActivator<T> activator = new ComponentActivator<T>(compSpec, idlOpInterface);
return executor.submit(activator);
}
}