From 2b0f07e786a3a8c5f58597921f5b494c6f820112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=BChler?= <17296905+buehlefs@users.noreply.github.com> Date: Thu, 3 Oct 2019 18:54:19 +0200 Subject: [PATCH 1/3] Refactor program runner to use CompletableFuture Add ability to get running program for a given entity Fix multiple programs allowed to run on the same entity Fix programs with factories only being usable once --- .../entity/program/EntityProgramRunner.java | 16 +++- .../entity/program/EntityProgramState.java | 2 + .../entity/program/RunningProgramInfo.java | 32 ++++++++ .../program/EntityProgramRegistryEntry.java | 21 +++-- .../program/EntityProgramRunningInfo.java | 36 ++++---- .../StandardEntityProgramRegistry.java | 13 +++ .../program/StandardEntityProgramRunner.java | 82 +++++++++++++++---- 7 files changed, 157 insertions(+), 45 deletions(-) create mode 100644 ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/RunningProgramInfo.java diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramRunner.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramRunner.java index 5a81579b2..7b5338e1d 100644 --- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramRunner.java +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramRunner.java @@ -52,7 +52,8 @@ public interface EntityProgramRunner { /** * Check whether the given program can be run to the given entity. *

- * First checks {@link #canRunProgram(String)} and then {@link EntityProgram#canRunOn(Entity)}. + * First checks {@link #canRunProgram(String)} and then {@link EntityProgram#canRunOn(Entity)}. If the entity was + * running a program also checks if that program finished succesfully. *

* * @param program @@ -86,6 +87,19 @@ public interface EntityProgramRunner { */ void run(String program, Entity entity); + /** + * Get the running program of a given entity. + * + * @param entity + * The entity to run the program on + * @return the information object for the running program + * @throws IllegalArgumentException + * if an argument is null + * @throws NoSuchElementException + * if no running program is found for the entity + */ + RunningProgramInfo getRunningProgramInfo(Entity entity); + /** * Force stop all running entity programs. *

diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramState.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramState.java index 3f6872618..34d95cf4b 100644 --- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramState.java +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramState.java @@ -15,6 +15,8 @@ * @author Tim Neumann */ public enum EntityProgramState { + /** When the program has a program factory that creates new instances of this program. */ + IS_FACTORY, /** When the entity program is new and was not started yet. */ NEW, /** When the entity program is currently running. */ diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/RunningProgramInfo.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/RunningProgramInfo.java new file mode 100644 index 000000000..f873ad66b --- /dev/null +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/RunningProgramInfo.java @@ -0,0 +1,32 @@ +/* + * This source file is part of the FIUS ICGE project. + * For more information see github.com/FIUS/ICGE2 + * + * Copyright (c) 2019 the ICGE project authors. + * + * This software is available under the MIT license. + * SPDX-License-Identifier: MIT + */ +package de.unistuttgart.informatik.fius.icge.simulation.entity.program; + +import java.util.concurrent.CompletableFuture; + + +/** + * Info object holding a running program. + * + * @author Fabian Bühler + */ +public interface RunningProgramInfo { + + /** + * @return the state of this object; cannot be null + */ + public EntityProgramState getState(); + + /** + * @return the future running the program + */ + public CompletableFuture getFuture(); + +} diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRegistryEntry.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRegistryEntry.java index 34cd6ac42..576629c1f 100644 --- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRegistryEntry.java +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRegistryEntry.java @@ -22,7 +22,7 @@ */ public class EntityProgramRegistryEntry { private final String name; - private final boolean single; + private final boolean isSingleInstance; private final EntityProgram program; private final Supplier programGenerator; @@ -39,7 +39,7 @@ public class EntityProgramRegistryEntry { public EntityProgramRegistryEntry(final String name, final EntityProgram program) { if ((name == null) || (program == null)) throw new IllegalArgumentException("Argument cannot be null."); this.name = name; - this.single = true; + this.isSingleInstance = true; this.program = program; this.programGenerator = null; } @@ -57,7 +57,7 @@ public EntityProgramRegistryEntry(final String name, final EntityProgram program public EntityProgramRegistryEntry(final String name, final Supplier programGenerator) { if ((name == null) || (programGenerator == null)) throw new IllegalArgumentException("Argument cannot be null."); this.name = name; - this.single = false; + this.isSingleInstance = false; this.program = null; this.programGenerator = programGenerator; } @@ -69,6 +69,13 @@ public String getName() { return this.name; } + /** + * @return false iff the program has a factory to create new instances + */ + public boolean isSingle() { + return this.isSingleInstance; + } + /** * Get the program instance of this info. *

@@ -78,7 +85,7 @@ public String getName() { * @return the program instance */ public EntityProgram getProgram() { - if (this.single) return this.program; + if (this.isSingleInstance) return this.program; final EntityProgram prog = this.programGenerator.get(); if (prog == null) throw new IllegalStateException("Program Generator returned null."); return prog; @@ -86,7 +93,7 @@ public EntityProgram getProgram() { @Override public int hashCode() { - if (this.single) return this.name.hashCode() + this.program.hashCode(); + if (this.isSingleInstance) return this.name.hashCode() + this.program.hashCode(); return this.name.hashCode() + this.programGenerator.hashCode(); } @@ -94,9 +101,9 @@ public int hashCode() { public boolean equals(final Object obj) { if (!(obj instanceof EntityProgramRegistryEntry)) return false; final EntityProgramRegistryEntry other = (EntityProgramRegistryEntry) obj; - if (this.single != other.single) return false; + if (this.isSingleInstance != other.isSingleInstance) return false; if (!this.name.equals(other.name)) return false; - if (this.single) return this.program.equals(other.program); + if (this.isSingleInstance) return this.program.equals(other.program); return this.programGenerator.equals(other.programGenerator); } diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRunningInfo.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRunningInfo.java index b2b8b3283..f36b18102 100644 --- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRunningInfo.java +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRunningInfo.java @@ -9,8 +9,11 @@ */ package de.unistuttgart.informatik.fius.icge.simulation.internal.entity.program; +import java.util.concurrent.CompletableFuture; + import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgram; import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramState; +import de.unistuttgart.informatik.fius.icge.simulation.entity.program.RunningProgramInfo; /** @@ -18,10 +21,11 @@ * * @author Tim Neumann */ -public class EntityProgramRunningInfo { - private EntityProgramState state; - private final EntityProgram program; - private Thread thread; +public class EntityProgramRunningInfo implements RunningProgramInfo { + + private EntityProgramState state; + private final EntityProgram program; + private CompletableFuture future; /** * Initialize @@ -38,9 +42,7 @@ public EntityProgramRunningInfo(final EntityProgram program) { this.state = EntityProgramState.NEW; } - /** - * @return the state of this object; cannot be null - */ + @Override public EntityProgramState getState() { return this.state; } @@ -67,23 +69,17 @@ public EntityProgram getProgram() { return this.program; } - /** - * Get the thread of this object - * - * @return the thread of this object; can be null - */ - public Thread getThread() { - return this.thread; + @Override + public CompletableFuture getFuture() { + return this.future; } /** - * Set the thread of this object - * - * @param thread - * the new thread; may be null + * @param future + * the future to set; may be null */ - public void setThread(final Thread thread) { - this.thread = thread; + public void setFuture(CompletableFuture future) { + this.future = future; } } diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRegistry.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRegistry.java index bfd408376..65304d743 100644 --- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRegistry.java +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRegistry.java @@ -67,4 +67,17 @@ public EntityProgram getEntityProgram(final String name) { return this.programs.get(name).getProgram(); } + /** + * Get the full program entry. + * + * @param name + * the program name + * @return iff the program has a factory to create new instances + */ + public boolean checkIfProgramHasFactory(final String name) { + if (name == null) throw new IllegalArgumentException("An argument is null."); + if (!this.programs.containsKey(name)) throw new NoSuchElementException(); + return !this.programs.get(name).isSingle(); + } + } diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java index 0c90cedf9..f35246012 100644 --- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java @@ -11,11 +11,15 @@ import java.util.HashMap; import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletableFuture; +import de.unistuttgart.informatik.fius.icge.log.Logger; import de.unistuttgart.informatik.fius.icge.simulation.entity.Entity; import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramRegistry; import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramRunner; import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramState; +import de.unistuttgart.informatik.fius.icge.simulation.entity.program.RunningProgramInfo; import de.unistuttgart.informatik.fius.icge.simulation.exception.CannotRunProgramException; import de.unistuttgart.informatik.fius.icge.simulation.exception.UncheckedInterruptedException; @@ -27,9 +31,10 @@ */ public class StandardEntityProgramRunner implements EntityProgramRunner { - private final EntityProgramRegistry registry; + private final StandardEntityProgramRegistry registry; - private final Map programs = new HashMap<>(); + private final Map singlePrograms = new HashMap<>(); + private final Map entityPrograms = new HashMap<>(); /** * Create a new StandardEntityProgramRunner. @@ -37,21 +42,36 @@ public class StandardEntityProgramRunner implements EntityProgramRunner { * @param registry * The EntityProgramRegistry to use */ - public StandardEntityProgramRunner(final EntityProgramRegistry registry) { + public StandardEntityProgramRunner(final StandardEntityProgramRegistry registry) { this.registry = registry; } - private EntityProgramRunningInfo getInfo(final String program) { - if (!this.programs.containsKey(program)) { - this.programs.put(program, new EntityProgramRunningInfo(this.registry.getEntityProgram(program))); + private EntityProgramRunningInfo getSingleInstanceProgramInfo(final String programName) { + if (!this.singlePrograms.containsKey(programName)) { + if (!this.registry.checkIfProgramHasFactory(programName)) { + this.singlePrograms.put(programName, new EntityProgramRunningInfo(this.registry.getEntityProgram(programName))); + } + } + return this.singlePrograms.get(programName); + } + + private EntityProgramRunningInfo getProgramInfo(final String programName) { + EntityProgramRunningInfo programInfo = this.getSingleInstanceProgramInfo(programName); + if (programInfo != null) return this.singlePrograms.get(programName); + if (this.registry.checkIfProgramHasFactory(programName)) { + return new EntityProgramRunningInfo(this.registry.getEntityProgram(programName)); } - return this.programs.get(program); + throw new NoSuchElementException("No Program registered with the name \"" + programName + "\"!"); } @Override public EntityProgramState getState(final String program) { if (program == null) throw new IllegalArgumentException("Argument is null."); - return this.getInfo(program).getState(); + EntityProgramRunningInfo singleProgramInstance = this.getSingleInstanceProgramInfo(program); + if (singleProgramInstance != null) return singleProgramInstance.getState(); + boolean hasFactory = this.registry.checkIfProgramHasFactory(program); + if (hasFactory) return EntityProgramState.IS_FACTORY; + throw new IllegalStateException("Program should either have a specific instance or be instantiated by a factory!"); } private boolean canRunProgram(final EntityProgramRunningInfo info) { @@ -61,10 +81,17 @@ private boolean canRunProgram(final EntityProgramRunningInfo info) { @Override public boolean canRunProgram(final String program) { if (program == null) throw new IllegalArgumentException("Argument is null."); - return this.canRunProgram(this.getInfo(program)); + return this.canRunProgram(this.getProgramInfo(program)); + } + + public boolean entityCanRunProgram(final Entity entity) { + EntityProgramRunningInfo oldInfo = this.entityPrograms.get(entity); + // only allow new programs to run on the entity if the last finished without exception! + return (oldInfo == null) || oldInfo.getState().equals(EntityProgramState.FINISHED); } private boolean canRunProgramOn(final EntityProgramRunningInfo info, final Entity entity) { + if (!this.entityCanRunProgram(entity)) return false; if (!this.canRunProgram(info)) return false; return info.getProgram().canRunOn(entity); } @@ -72,36 +99,57 @@ private boolean canRunProgramOn(final EntityProgramRunningInfo info, final Entit @Override public boolean canRunProgramOn(final String program, final Entity entity) { if ((program == null) || (entity == null)) throw new IllegalArgumentException("Argument is null."); - return this.canRunProgramOn(this.getInfo(program), entity); + return this.canRunProgramOn(this.getProgramInfo(program), entity); } @Override public void run(final String program, final Entity entity) { if ((program == null) || (entity == null)) throw new IllegalArgumentException("Argument is null."); - final var info = this.getInfo(program); + final var info = this.getProgramInfo(program); if (!this.canRunProgramOn(info, entity)) throw new CannotRunProgramException(); + // TODO set thread name final String threadName = "EntityProgramRunner" + "_" + program + "_on_" + entity.toString(); - final Thread thread = new Thread(() -> { + + CompletableFuture future = CompletableFuture.supplyAsync(() -> { try { info.getProgram().run(entity); info.setState(EntityProgramState.FINISHED); } catch (@SuppressWarnings("unused") final UncheckedInterruptedException e) { info.setState(EntityProgramState.KILLED); + } catch (Exception e) { + Logger.simulation.println("----------------------------------------------"); + Logger.simulation + .println("The following exception happened in program " + program + " running on entity " + entity.toString()); + e.printStackTrace(Logger.simulation); + Logger.simulation.println("----------------------------------------------"); + info.setState(EntityProgramState.KILLED); } - }, threadName); + return null; + }); - info.setThread(thread); + info.setFuture(future); info.setState(EntityProgramState.RUNNING); - thread.start(); + + // set the running program in the entityMap + this.entityPrograms.put(entity, info); + } + + @Override + public RunningProgramInfo getRunningProgramInfo(Entity entity) { + if (entity == null) throw new IllegalArgumentException("Entity cannot be null!"); + if ( + !this.entityPrograms.containsKey(entity) + ) throw new NoSuchElementException("No running program for entity " + entity.toString() + "found!"); + return this.entityPrograms.get(entity); } @Override public void forceStop() { - for (final EntityProgramRunningInfo info : this.programs.values()) { - info.getThread().interrupt(); + for (final EntityProgramRunningInfo info : this.entityPrograms.values()) { + info.getFuture().cancel(true); } } From 128c8f17d6d9087e432be17916b735b5db9b5b91 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Thu, 3 Oct 2019 19:33:41 +0200 Subject: [PATCH 2/3] Configure executor for program runner. --- .../program/StandardEntityProgramRunner.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java index f35246012..794d424f5 100644 --- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java @@ -13,10 +13,13 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; +import java.util.concurrent.ForkJoinWorkerThread; import de.unistuttgart.informatik.fius.icge.log.Logger; import de.unistuttgart.informatik.fius.icge.simulation.entity.Entity; -import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramRegistry; import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramRunner; import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramState; import de.unistuttgart.informatik.fius.icge.simulation.entity.program.RunningProgramInfo; @@ -33,9 +36,24 @@ public class StandardEntityProgramRunner implements EntityProgramRunner { private final StandardEntityProgramRegistry registry; + private final ExecutorService executor; + private final Map singlePrograms = new HashMap<>(); private final Map entityPrograms = new HashMap<>(); + private ExecutorService createExecutor() { + final ForkJoinWorkerThreadFactory factory = new ForkJoinWorkerThreadFactory() { + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + worker.setName("EntityProgramRunnerThread-" + worker.getPoolIndex()); + return worker; + } + }; + + return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, false); + } + /** * Create a new StandardEntityProgramRunner. * @@ -44,6 +62,7 @@ public class StandardEntityProgramRunner implements EntityProgramRunner { */ public StandardEntityProgramRunner(final StandardEntityProgramRegistry registry) { this.registry = registry; + this.executor = createExecutor(); } private EntityProgramRunningInfo getSingleInstanceProgramInfo(final String programName) { @@ -110,9 +129,6 @@ public void run(final String program, final Entity entity) { if (!this.canRunProgramOn(info, entity)) throw new CannotRunProgramException(); - // TODO set thread name - final String threadName = "EntityProgramRunner" + "_" + program + "_on_" + entity.toString(); - CompletableFuture future = CompletableFuture.supplyAsync(() -> { try { info.getProgram().run(entity); @@ -128,7 +144,7 @@ public void run(final String program, final Entity entity) { info.setState(EntityProgramState.KILLED); } return null; - }); + }, this.executor); info.setFuture(future); info.setState(EntityProgramState.RUNNING); From d7497cd56d6f45b9e4ef20371f5b5ecfcb9a6a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=BChler?= <17296905+buehlefs@users.noreply.github.com> Date: Fri, 4 Oct 2019 14:29:17 +0200 Subject: [PATCH 3/3] Remove not needed call --- .../internal/entity/program/StandardEntityProgramRunner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java index 794d424f5..490fa6542 100644 --- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java +++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java @@ -75,8 +75,8 @@ private EntityProgramRunningInfo getSingleInstanceProgramInfo(final String progr } private EntityProgramRunningInfo getProgramInfo(final String programName) { - EntityProgramRunningInfo programInfo = this.getSingleInstanceProgramInfo(programName); - if (programInfo != null) return this.singlePrograms.get(programName); + final EntityProgramRunningInfo programInfo = this.getSingleInstanceProgramInfo(programName); + if (programInfo != null) return programInfo; if (this.registry.checkIfProgramHasFactory(programName)) { return new EntityProgramRunningInfo(this.registry.getEntityProgram(programName)); }