/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* 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 io.janusproject.kernel.bic;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
import io.janusproject.services.executor.EarlyExitException;
import io.janusproject.services.executor.ExecutorService;
import io.janusproject.services.executor.JanusRunnable;
import io.sarl.core.AgentTask;
import io.sarl.core.Logging;
import io.sarl.core.Schedules;
import io.sarl.lang.core.Agent;
import io.sarl.lang.core.AgentTrait;
import io.sarl.lang.core.Behavior;
import io.sarl.lang.core.Capacities;
import io.sarl.lang.core.SREutils;
import io.sarl.lang.core.Skill;
import io.sarl.lang.util.ClearableReference;
/**
* Skill that permits to execute tasks with an executor service.
*
* @author $Author: srodriguez$
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public class SchedulesSkill extends BuiltinSkill implements Schedules {
private static int installationOrder = -1;
@Inject
private ExecutorService executorService;
private final Map<String, TaskDescription> tasks = new TreeMap<>();
private ClearableReference<Skill> skillBufferLogging;
/**
* @param agent - the owner of this skill.
*/
SchedulesSkill(Agent agent) {
super(agent);
}
/** Replies the Logging skill as fast as possible.
*
* @return the skill
*/
protected final Logging getLoggingSkill() {
if (this.skillBufferLogging == null || this.skillBufferLogging.get() == null) {
this.skillBufferLogging = $getSkill(Logging.class);
}
return $castSkill(Logging.class, this.skillBufferLogging);
}
/** Replies the mutex for synchronizing on the task list.
*
* @return the mutex.
*/
protected final Object getTaskListMutex() {
return this.tasks;
}
@Override
public int getInstallationOrder() {
if (installationOrder < 0) {
installationOrder = installationOrder(this);
}
return installationOrder;
}
@Override
protected String attributesToString() {
return super.attributesToString() + ", tasks = " + this.tasks; //$NON-NLS-1$
}
/**
* Remove any reference to the given task.
*
* <p>This function is not thread-safe.
*
* @param name - name of the task.
*/
private void finishTask(AgentTask task, boolean updateSkillReferences, boolean updateAgentTraitReferences) {
assert task != null;
if (updateSkillReferences) {
this.tasks.remove(task.getName());
}
if (updateAgentTraitReferences) {
final Object initiator = task.getInitiator();
if (initiator instanceof AgentTrait) {
final AgentTraitData data = SREutils.getSreSpecificData((AgentTrait) initiator, AgentTraitData.class);
if (data != null) {
data.removeTask(task);
}
}
}
}
@Override
public Collection<String> getActiveTasks() {
synchronized (getTaskListMutex()) {
//TODO: Avoid copy of collection
return Lists.newArrayList(this.tasks.keySet());
}
}
/**
* Replies the names of the active futures.
*
* @return the names of the active futures.
*/
Collection<Future<?>> getActiveFutures() {
synchronized (getTaskListMutex()) {
return Lists.newArrayList(Iterables.transform(this.tasks.values(), (it) -> it.getFuture()));
}
}
/** Unregister the tasks associated to the given behavior.
*
* @param behavior the behavior.
*/
synchronized void unregisterTasksForBehavior(Behavior behavior) {
final AgentTraitData data = SREutils.getSreSpecificData(behavior, AgentTraitData.class);
if (data != null) {
final Iterable<AgentTask> iterable = data.resetTaskList();
for (final AgentTask taskToCancel : iterable) {
cancel(taskToCancel, Schedules.$DEFAULT_VALUE$CANCEL_0, false);
}
}
}
private void cancelAllRunningTasks() {
Future<?> future;
AgentTask task;
for (final Entry<String, TaskDescription> taskDescription : this.tasks.entrySet()) {
final TaskDescription pair = taskDescription.getValue();
if (pair != null) {
future = pair.getFuture();
if (future != null) {
future.cancel(true);
}
task = pair.getTask();
if (task != null) {
finishTask(task, false, true);
}
}
}
this.tasks.clear();
}
@Override
protected void uninstall(UninstallationStage stage) {
if (stage == UninstallationStage.PRE_DESTROY_EVENT) {
// Cancel the tasks as soon as possible in the uninstallation process
synchronized (getTaskListMutex()) {
cancelAllRunningTasks();
}
} else {
synchronized (getTaskListMutex()) {
// Cancel the tasks that were creating during the destruction stage (in the Destroy event handler)
cancelAllRunningTasks();
}
}
}
@Override
public AgentTask in(long delay, Procedure1<? super Agent> procedure) {
return in(Schedules.$DEFAULT_VALUE$IN_0, delay, procedure);
}
@Override
public AgentTask in(AgentTask task, long delay, Procedure1<? super Agent> procedure) {
TaskDescription pair;
synchronized (getTaskListMutex()) {
pair = preRunTask(task, procedure);
}
final AgentTask runnableTask = pair != null ? pair.getTask() : task;
final ScheduledFuture<?> sf = this.executorService.schedule(
new AgentTaskRunner(runnableTask, false),
delay, TimeUnit.MILLISECONDS);
synchronized (getTaskListMutex()) {
pair = postRunTask(pair, task, sf);
}
return pair.getTask();
}
private TaskDescription preRunTask(AgentTask task, Procedure1<? super Agent> procedure) {
final TaskDescription pair;
final AgentTask rtask;
if (task == null) {
pair = createTaskIfNecessary(null);
rtask = pair.getTask();
} else {
rtask = task;
pair = this.tasks.get(task.getName());
if (pair != null) {
pair.setTask(rtask);
}
}
rtask.setProcedure(procedure);
return pair;
}
private TaskDescription postRunTask(TaskDescription description, AgentTask task, Future<?> future) {
final TaskDescription pair;
if (description == null) {
pair = new TaskDescription(task, future);
this.tasks.put(task.getName(), pair);
} else {
pair = description;
pair.setFuture(future);
}
return pair;
}
private TaskDescription createTaskIfNecessary(String name) {
TaskDescription pair = null;
final String realName;
if (Strings.isNullOrEmpty(name)) {
realName = "task-" + UUID.randomUUID().toString(); //$NON-NLS-1$
} else {
realName = name;
synchronized (getTaskListMutex()) {
pair = this.tasks.get(realName);
}
}
if (pair == null) {
final AgentTrait caller = Capacities.getCaller();
final AgentTask task = new AgentTask(caller);
task.setName(realName);
task.setGuard(AgentTask.TRUE_GUARD);
pair = new TaskDescription(task);
synchronized (getTaskListMutex()) {
this.tasks.put(realName, pair);
if (caller != null) {
AgentTraitData data = SREutils.getSreSpecificData(caller, AgentTraitData.class);
if (data == null) {
data = new AgentTraitData();
SREutils.setSreSpecificData(caller, data);
}
data.addTask(task);
}
}
}
return pair;
}
@Override
public AgentTask task(String name) {
return createTaskIfNecessary(name).getTask();
}
@Override
public final boolean cancel(AgentTask task) {
return cancel(task, Schedules.$DEFAULT_VALUE$CANCEL_0, true);
}
@Override
public final boolean cancel(AgentTask task, boolean mayInterruptIfRunning) {
return cancel(task, mayInterruptIfRunning, true);
}
/** Cancel the given task with finer control on the reference updates.
*
* @param task the task to cancel.
* @param mayInterruptIfRunning indicates if the task's thread could be interrupt.
* @param updateAgentTraitReferences indicates if the references in the task's agent trait may be updates, if
* they exist.
* @return {@code true} if the task is cancelled, {@code false} if not.
*/
protected boolean cancel(AgentTask task, boolean mayInterruptIfRunning, boolean updateAgentTraitReferences) {
if (task != null) {
final String name = task.getName();
synchronized (getTaskListMutex()) {
final TaskDescription pair = this.tasks.get(name);
if (pair != null) {
final Future<?> future = pair.getFuture();
if (future != null && !future.isDone() && !future.isCancelled() && future.cancel(mayInterruptIfRunning)) {
finishTask(task, true, updateAgentTraitReferences);
return true;
}
}
}
}
return false;
}
@Override
public boolean isCanceled(AgentTask task) {
if (task != null) {
final String name = task.getName();
final Future<?> future = getActiveFuture(name);
if (future != null) {
return future.isCancelled();
}
}
return false;
}
/** Replies the future object for the given task.
*
* @param taskName the name of the task.
* @return the future.
* @since 0.5
*/
Future<?> getActiveFuture(String taskName) {
synchronized (getTaskListMutex()) {
final TaskDescription pair = this.tasks.get(taskName);
if (pair != null) {
return pair.getFuture();
}
}
return null;
}
@Override
public AgentTask every(long period, Procedure1<? super Agent> procedure) {
return every(Schedules.$DEFAULT_VALUE$EVERY_0, period, procedure);
}
@Override
public AgentTask every(AgentTask task, long period, Procedure1<? super Agent> procedure) {
TaskDescription description;
synchronized (getTaskListMutex()) {
description = preRunTask(task, procedure);
}
final AgentTask runnableTask = description != null ? description.getTask() : task;
final ScheduledFuture<?> sf = this.executorService.scheduleAtFixedRate(
new AgentTaskRunner(runnableTask, true),
0, period, TimeUnit.MILLISECONDS);
synchronized (getTaskListMutex()) {
description = postRunTask(description, task, sf);
}
return description.getTask();
}
@Override
public AgentTask atFixedDelay(long delay, Procedure1<? super Agent> procedure) {
return atFixedDelay(Schedules.$DEFAULT_VALUE$ATFIXEDDELAY_0, delay, procedure);
}
@Override
public AgentTask atFixedDelay(AgentTask task, long delay, Procedure1<? super Agent> procedure) {
TaskDescription description;
synchronized (getTaskListMutex()) {
description = preRunTask(task, procedure);
}
final AgentTask runnableTask = description != null ? description.getTask() : task;
final Future<?> future;
if (delay <= 0) {
future = this.executorService.submit(new AgentInfiniteLoopTask(runnableTask));
} else {
future = this.executorService.scheduleWithFixedDelay(
new AgentTaskRunner(runnableTask, true),
0, delay, TimeUnit.MILLISECONDS);
}
synchronized (getTaskListMutex()) {
description = postRunTask(description, task, future);
}
return description.getTask();
}
@Override
public AgentTask execute(Procedure1<? super Agent> procedure) {
return execute(Schedules.$DEFAULT_VALUE$EXECUTE_0, procedure);
}
@Override
public synchronized AgentTask execute(AgentTask task, Procedure1<? super Agent> procedure) {
TaskDescription description;
synchronized (getTaskListMutex()) {
description = preRunTask(task, procedure);
}
final AgentTask runnableTask = description != null ? description.getTask() : task;
final Future<?> future = this.executorService.submit(new AgentTaskRunner(runnableTask, false));
synchronized (getTaskListMutex()) {
description = postRunTask(description, task, future);
}
return description.getTask();
}
/**
* Implementation of an agent task.
*
* @author $Author: srodriguez$
* @author $Author: sgalland$
* @version $Name$ $Revision$ $Date$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
@SuppressWarnings("synthetic-access")
private class AgentTaskRunner extends JanusRunnable {
private final WeakReference<AgentTask> agentTaskRef;
private WeakReference<Future<?>> future;
private final boolean isPeriodic;
AgentTaskRunner(AgentTask task, boolean isPeriodic) {
assert task != null;
this.agentTaskRef = new WeakReference<>(task);
this.isPeriodic = isPeriodic;
}
/** Set the future of this task.
*
* @param future the future.
* @since 0.5
*/
void setFuture(Future<?> future) {
this.future = future == null ? null : new WeakReference<>(future);
}
/** Replies the future of this task.
*
* @return the future.
* @since 0.5
*/
private Future<?> getFuture() {
final WeakReference<Future<?>> safeFutureReference = this.future;
return safeFutureReference == null ? null : safeFutureReference.get();
}
@Override
public void run() {
final AgentTask task = this.agentTaskRef.get();
if (task == null) {
throw new RuntimeException(Messages.SchedulesSkill_0);
}
final Future<?> future = getFuture();
if (future != null && (future.isDone() || future.isCancelled())) {
setFuture(null);
return;
}
boolean mustBeCanceled = false;
try {
final Agent owner = getOwner();
final Function1<? super Agent, ? extends Boolean> guard = task.getGuard();
if (guard == null || guard.apply(owner).booleanValue()) {
final Procedure1<? super Agent> procedure = task.getProcedure();
if (procedure != null) {
procedure.apply(owner);
}
} else {
mustBeCanceled = true;
}
} catch (EarlyExitException ex) {
// Be silent
} catch (Throwable ex) {
getLoggingSkill().error(Messages.SchedulesSkill_1, ex, toString(), ex.getLocalizedMessage());
mustBeCanceled = true;
} finally {
if (mustBeCanceled || !this.isPeriodic) {
synchronized (getTaskListMutex()) {
finishTask(task, true, true);
}
}
}
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("name", this.agentTaskRef.get().getName()) //$NON-NLS-1$
.add("agent", getOwner().getID()).toString(); //$NON-NLS-1$
}
}
/**
* Implementation of an agent infinite loop task.
*
* @author $Author: srodriguez$
* @version $Name$ $Revision$ $Date$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
@SuppressWarnings("synthetic-access")
private class AgentInfiniteLoopTask extends JanusRunnable {
private WeakReference<AgentTask> agentTaskRef;
AgentInfiniteLoopTask(AgentTask task) {
this.agentTaskRef = new WeakReference<>(task);
}
private boolean canRun() {
final AgentTask task = this.agentTaskRef.get();
if (task != null) {
final Future<?> future = getActiveFuture(task.getName());
return future != null && !future.isDone() && !future.isCancelled();
}
return false;
}
private Function1<? super Agent, ? extends Boolean> getGuard() {
final AgentTask task = this.agentTaskRef.get();
if (task != null) {
return task.getGuard();
}
return null;
}
private Procedure1<? super Agent> getProcedure() {
final AgentTask task = this.agentTaskRef.get();
if (task != null) {
return task.getProcedure();
}
return null;
}
@Override
public void run() {
try {
final Agent owner = getOwner();
while (canRun()) {
final Function1<? super Agent, ? extends Boolean> guard = getGuard();
if (guard == null || guard.apply(owner).booleanValue()) {
final Procedure1<? super Agent> procedure = getProcedure();
if (procedure != null) {
procedure.apply(owner);
}
} else {
// Break the loop without introducing a local boolean variable
break;
}
Thread.yield();
}
} catch (EarlyExitException ex) {
// Ignore
} catch (Throwable ex) {
getLoggingSkill().error(Messages.SchedulesSkill_1, ex, toString(), ex.getLocalizedMessage());
} finally {
final AgentTask task = this.agentTaskRef.get();
if (task != null) {
synchronized (getTaskListMutex()) {
finishTask(task, true, true);
}
}
}
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("name", this.agentTaskRef.get().getName()) //$NON-NLS-1$
.add("agent", getOwner().getID()).toString(); //$NON-NLS-1$
}
}
/**
* Description of a task.
*
* @author $Author: sgalland$
* @version $Name$ $Revision$ $Date$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 0.5
*/
private static class TaskDescription {
/** Agent task.
*/
private AgentTask task;
/** The scheduled future associated to the task.
*/
private Future<?> future;
TaskDescription(AgentTask task) {
this.task = task;
}
TaskDescription(AgentTask task, Future<?> future) {
this.task = task;
this.future = future;
}
@Override
public String toString() {
return Objects.toString(this.task);
}
public AgentTask getTask() {
return this.task;
}
public void setTask(AgentTask task) {
this.task = task;
}
public Future<?> getFuture() {
return this.future;
}
public void setFuture(Future<?> future) {
this.future = future;
}
}
}