From 5c25ecc8df083c0c16c4498c0f91265484113695 Mon Sep 17 00:00:00 2001 From: indyteo Date: Fri, 21 Mar 2025 15:41:35 +0100 Subject: [PATCH 1/2] fix(scheduler): Add spurious wakeup guard --- .../ignfab/minalac/generator/utils/execution/Scheduler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ignfab/minalac/generator/utils/execution/Scheduler.java b/src/main/java/com/ignfab/minalac/generator/utils/execution/Scheduler.java index e018a7892..fb8682f43 100644 --- a/src/main/java/com/ignfab/minalac/generator/utils/execution/Scheduler.java +++ b/src/main/java/com/ignfab/minalac/generator/utils/execution/Scheduler.java @@ -153,8 +153,10 @@ public void waitUntilAllTasksFinished(long timeout, TimeUnit unit) throws Interr // Pause this thread by sleeping until being waked up by the end / failure of tasks // The synchronized block is mandatory to take ownership of the lock synchronized (lock) { - // TODO Spurious wakeup guard - lock.wait(unit.toMillis(timeout)); + // Spurious wakeup guard + long timeoutAt = System.currentTimeMillis() + unit.toMillis(timeout); + while (error == null && !tasks.isEmpty() && System.currentTimeMillis() < timeoutAt) + lock.wait(timeoutAt - System.currentTimeMillis()); } // Propagates the task failure, if any if (error != null) From f02d3e52af6332e968b59e5815b4224b59c2bcda Mon Sep 17 00:00:00 2001 From: indyteo Date: Fri, 21 Mar 2025 21:15:32 +0100 Subject: [PATCH 2/2] feat(scheduler): Add a validation process to tasks scheduling [WIP] --- .../generator/SampleImplementation.java | 8 +++ .../generator/utils/execution/Scheduler.java | 56 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/main/java/com/ignfab/minalac/generator/SampleImplementation.java b/src/main/java/com/ignfab/minalac/generator/SampleImplementation.java index eab6d467d..e51b7c40d 100644 --- a/src/main/java/com/ignfab/minalac/generator/SampleImplementation.java +++ b/src/main/java/com/ignfab/minalac/generator/SampleImplementation.java @@ -31,6 +31,7 @@ import com.ignfab.minalac.generator.parameters.renderers.HeightmapRendererParams; import com.ignfab.minalac.generator.parameters.renderers.LevelingRendererParams; import com.ignfab.minalac.generator.parameters.renderers.VectorRendererParams; +import com.ignfab.minalac.generator.utils.execution.Scheduler; import com.ignfab.minalac.generator.utils.execution.TaskFailedException; import com.ignfab.minalac.generator.utils.network.HttpTrustAllSSL; import com.ignfab.minalac.generator.utils.world3d.WorldBBox3d; @@ -93,6 +94,13 @@ public static void main(String[] args) throws FactoryException, InterruptedExcep Generation generation = parser.parse(cli.readParameters()).create(); + try { + generation.scheduler().validate(); + } catch (Scheduler.IllegalSchedulerException e) { + e.printStackTrace(); + return; + } + System.out.println("Initialization: " + Duration.between(initializationStart, Instant.now()).toSeconds() + "s"); if (cli.generationDisabled()) { System.out.println("Total: " + Duration.between(start, Instant.now()).toSeconds() + "s"); diff --git a/src/main/java/com/ignfab/minalac/generator/utils/execution/Scheduler.java b/src/main/java/com/ignfab/minalac/generator/utils/execution/Scheduler.java index fb8682f43..6ea32f8b4 100644 --- a/src/main/java/com/ignfab/minalac/generator/utils/execution/Scheduler.java +++ b/src/main/java/com/ignfab/minalac/generator/utils/execution/Scheduler.java @@ -3,7 +3,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -162,4 +164,58 @@ public void waitUntilAllTasksFinished(long timeout, TimeUnit unit) throws Interr if (error != null) throw error; } + + public void validate() throws IllegalSchedulerException { + Validator validator = new Validator(tasks); + validator.buildGraph(); + validator.checkForCycles(); + } + + private static class Validator { + private final Map tasksById; + + public Validator(Collection tasks) { + tasksById = new HashMap<>(tasks.size()); + for (ScheduledTask task : tasks) + tasksById.put(task.getId(), new TaskNode(task, new ArrayList<>())); + } + + private TaskNode require(String id) throws IllegalSchedulerException { + TaskNode node = tasksById.get(id); + if (node == null) + throw new IllegalSchedulerException("Task with id " + id + " not found"); + return node; + } + + public void buildGraph() throws IllegalSchedulerException { + for (TaskNode node : tasksById.values()) + for (String condition : node.task().getConditions()) + node.requirements().add(require(condition)); + } + + public void checkForCycles() throws IllegalSchedulerException { + for (TaskNode node : tasksById.values()) + if (node.hasRequirement(node)) + throw new IllegalSchedulerException("Task with id " + node.taskId() + " has cyclic dependency"); + } + } + + private record TaskNode(ScheduledTask task, List requirements) { + public String taskId() { + return task.getId(); + } + + public boolean hasRequirement(TaskNode node) { + for (TaskNode requirement : requirements) + if (requirement.taskId().equals(node.taskId()) || requirement.hasRequirement(node)) + return true; + return false; + } + } + + public static class IllegalSchedulerException extends Exception { + public IllegalSchedulerException(String message) { + super(message); + } + } }