/*
* 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 net.grinder.engine.agent;
import net.grinder.common.UncheckedInterruptedException;
import net.grinder.engine.common.EngineException;
import net.grinder.util.thread.Condition;
import net.grinder.util.thread.ExecutorFactory;
import net.grinder.util.thread.InterruptibleRunnable;
import net.grinder.util.thread.InterruptibleRunnableAdapter;
import org.slf4j.Logger;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
/**
* WorkerLauncher which redirect stdout/stderr stream to user.
*
* @author JunHo Yoon
* @since 3.0
*/
public class ErrorStreamRedirectWorkerLauncher {
private final ExecutorService m_executor;
private final WorkerFactory m_workerFactory;
private final Condition m_notifyOnFinish;
private final Logger m_logger;
/**
* Fixed size array with a slot for all potential workers. Synchronise on m_workers before
* accessing entries. If an entry is null and its index is less than m_nextWorkerIndex, the
* worker has finished or the WorkerLauncher has been shutdown.
*/
private final Worker[] m_workers;
/**
* The next worker to start. Only increases.
*/
private int m_nextWorkerIndex = 0;
private OutputStream errStream;
/**
* Constructor.
*
* @param numberOfWorkers worker count
* @param workerFactory worker factory
* @param notifyOnFinish synchronization condition
* @param logger logger
* @param errStream redirect stream
*/
public ErrorStreamRedirectWorkerLauncher(int numberOfWorkers, WorkerFactory workerFactory,
Condition notifyOnFinish, Logger logger, OutputStream errStream) {
this(ExecutorFactory.createThreadPool("WorkerLauncher", 1), numberOfWorkers, workerFactory, notifyOnFinish,
logger);
this.errStream = errStream;
}
/**
* Package scope for unit tests.
*
* @param executor executors
* @param numberOfWorkers worker count
* @param workerFactory worker factory
* @param notifyOnFinish synchronization condition
* @param logger logger
*/
ErrorStreamRedirectWorkerLauncher(ExecutorService executor, int numberOfWorkers, WorkerFactory workerFactory,
Condition notifyOnFinish, Logger logger) {
m_executor = executor;
m_workerFactory = workerFactory;
m_notifyOnFinish = notifyOnFinish;
m_logger = logger;
m_workers = new Worker[numberOfWorkers];
}
/**
* Start all workers.
*
* @throws EngineException engine exception
*/
public void startAllWorkers() throws EngineException {
startSomeWorkers(m_workers.length - m_nextWorkerIndex);
}
/**
* Start some of all workers.
*
* @param numberOfWorkers worker count
* @return true if all workers is not available.
* @throws EngineException engine exception
*/
public boolean startSomeWorkers(int numberOfWorkers) throws EngineException {
final int numberToStart = Math.min(numberOfWorkers, m_workers.length - m_nextWorkerIndex);
for (int i = 0; i < numberToStart; ++i) {
final int workerIndex = m_nextWorkerIndex;
final Worker worker = m_workerFactory.create(errStream, errStream);
synchronized (m_workers) {
m_workers[workerIndex] = worker;
}
try {
m_executor.execute(new InterruptibleRunnableAdapter(new WaitForWorkerTask(workerIndex)));
} catch (RejectedExecutionException e) {
m_logger.error("Failed to wait for " + worker.getIdentity().getName(), e);
worker.destroy();
return false;
}
m_logger.info("worker " + worker.getIdentity().getName() + " started");
++m_nextWorkerIndex;
}
return m_workers.length > m_nextWorkerIndex;
}
private final class WaitForWorkerTask implements InterruptibleRunnable {
private final int m_workerIndex;
public WaitForWorkerTask(int workerIndex) {
m_workerIndex = workerIndex;
}
public void interruptibleRun() {
final Worker worker;
synchronized (m_workers) {
worker = m_workers[m_workerIndex];
}
assert worker != null;
try {
worker.waitFor();
} catch (UncheckedInterruptedException e) {
// We're taking our worker down with us.
worker.destroy();
}
synchronized (m_workers) {
m_workers[m_workerIndex] = null;
}
if (allFinished()) {
synchronized (m_notifyOnFinish) {
m_notifyOnFinish.notifyAll();
}
}
}
}
/**
* Check if all workers are finished.
*
* @return true if all finished
*/
public boolean allFinished() {
if (m_nextWorkerIndex < m_workers.length) {
return false;
}
synchronized (m_workers) {
for (Worker m_worker : m_workers) {
if (m_worker != null) {
return false;
}
}
}
return true;
}
/**
* Request shutdown of the worker launcher threads. Returns immediately.
*/
public void shutdown() {
m_executor.shutdown();
}
/**
* Block to start workers anymore.
*/
public void dontStartAnyMore() {
m_nextWorkerIndex = m_workers.length;
}
/**
* Destroy all workers.
*/
public void destroyAllWorkers() {
dontStartAnyMore();
synchronized (m_workers) {
for (Worker m_worker : m_workers) {
if (m_worker != null) {
m_worker.destroy();
}
}
}
}
}