package org.hotswap.agent.command.impl;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.hotswap.agent.annotation.handler.WatchEventCommand;
import org.hotswap.agent.command.Command;
import org.hotswap.agent.command.MergeableCommand;
import org.hotswap.agent.command.Scheduler;
import org.hotswap.agent.logging.AgentLogger;
/**
* Default command scheduler implementation.
*
* @author Jiri Bubnik
*/
public class SchedulerImpl implements Scheduler {
private static AgentLogger LOGGER = AgentLogger.getLogger(SchedulerImpl.class);
int DEFAULT_SCHEDULING_TIMEOUT = 100;
// TODO : Some commands must be executed in the order in which they are put to scheduler. Therefore
// there could be a LinkedHashMap and CommandExecutor should be singleton for commands that
// must be executed in order. There is an issue related to this problem
// https://github.com/HotswapProjects/HotswapAgent/issues/39 which requires concurrent using
final Map<Command, DuplicateScheduleConfig> scheduledCommands = new ConcurrentHashMap<Command, DuplicateScheduleConfig>();
final Set<Command> runningCommands = Collections.synchronizedSet(new HashSet<Command>());
Thread runner;
boolean stopped;
@Override
public void scheduleCommand(Command command) {
scheduleCommand(command, DEFAULT_SCHEDULING_TIMEOUT);
}
@Override
public void scheduleCommand(Command command, int timeout) {
scheduleCommand(command, timeout, DuplicateSheduleBehaviour.WAIT_AND_RUN_AFTER);
}
@Override
public void scheduleCommand(Command command, int timeout, DuplicateSheduleBehaviour behaviour) {
synchronized (scheduledCommands) {
Command targetCommand = command;
if (scheduledCommands.containsKey(command) && (command instanceof MergeableCommand)) {
// get existing equals command and merge it
for (Command scheduledCommand : scheduledCommands.keySet()) {
if (command.equals(scheduledCommand)) {
targetCommand = ((MergeableCommand) command).merge(scheduledCommand);
break;
}
}
}
// map may already contain equals command, put will replace it and reset timer
scheduledCommands.put(targetCommand, new DuplicateScheduleConfig(System.currentTimeMillis() + timeout, behaviour));
LOGGER.trace("{} scheduled for execution in {}ms", targetCommand, timeout);
}
}
/**
* One cycle of the scheduler agent. Process all commands which are not currently
* running and time lower than current milliseconds.
*
* @return true if the agent should continue (false for fatal error)
*/
private boolean processCommands() {
Long currentTime = System.currentTimeMillis();
synchronized (scheduledCommands) {
for (Iterator<Map.Entry<Command, DuplicateScheduleConfig>> it = scheduledCommands.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<Command, DuplicateScheduleConfig> entry = it.next();
DuplicateScheduleConfig config = entry.getValue();
Command command = entry.getKey();
// if timeout
if (config.getTime() < currentTime) {
// command is currently running
if (runningCommands.contains(command)) {
if (config.getBehaviour().equals(DuplicateSheduleBehaviour.SKIP)) {
LOGGER.debug("Skipping duplicate running command {}", command);
it.remove();
} else if (config.getBehaviour().equals(DuplicateSheduleBehaviour.RUN_DUPLICATE)) {
executeCommand(command);
it.remove();
}
} else {
executeCommand(command);
it.remove();
}
}
}
}
return true;
}
/**
* Execute this command in a separate thread.
*
* @param command the command to execute
*/
private void executeCommand(Command command) {
if (command instanceof WatchEventCommand)
LOGGER.trace("Executing {}", command); // too much output for debug
else
LOGGER.debug("Executing {}", command);
runningCommands.add(command);
new CommandExecutor(command) {
@Override
public void finished() {
runningCommands.remove(command);
}
}.start();
}
@Override
public void run() {
runner = new Thread() {
@Override
public void run() {
for (; ; ) {
if (stopped || !processCommands())
break;
// wait for 100 ms
try {
sleep(100);
} catch (InterruptedException e) {
break;
}
}
}
};
runner.setDaemon(true);
runner.start();
}
@Override
public void stop() {
stopped = true;
}
private static class DuplicateScheduleConfig {
// time when to run
long time;
// behaviour in case of conflict (running same command in progress)
DuplicateSheduleBehaviour behaviour;
private DuplicateScheduleConfig(long time, DuplicateSheduleBehaviour behaviour) {
this.time = time;
this.behaviour = behaviour;
}
public long getTime() {
return time;
}
public DuplicateSheduleBehaviour getBehaviour() {
return behaviour;
}
}
}