+#### Abortable example
+
+For long-running scripts that process many nodes, it's important to support graceful abortion. This allows users to stop the script execution without leaving the repository in an inconsistent state.
+
+```groovy
+void doRun() {
+ repo.queryRaw("SELECT * FROM [nt:base] WHERE ISDESCENDANTNODE('/content/acme/us/en')").forEach { resource ->
+ // Safe point
+ context.checkAborted()
+
+ // Process resource
+ // TODO resource.save() etc.
+ }
+}
+```
+
+Alternatively, you can use `context.isAborted()` for manual control:
+
+```groovy
+void doRun() {
+ def assets = repo.queryRaw("SELECT * FROM [dam:Asset] WHERE ISDESCENDANTNODE('/content/dam')").iterator()
+ for (asset in assets) {
+ if (context.isAborted()) {
+ // Do clean when aborted
+ break
+ }
+ }
+ // Still remember to propagate abort status
+ context.checkAborted()
+}
+```
+
### History
All code executions are logged in the history. You can see the status of each execution, including whether it was successful or failed. The history also provides detailed logs for each execution, including any errors that occurred.
diff --git a/core/src/main/java/dev/vml/es/acm/core/code/AbortException.java b/core/src/main/java/dev/vml/es/acm/core/code/AbortException.java
new file mode 100644
index 00000000..278d900d
--- /dev/null
+++ b/core/src/main/java/dev/vml/es/acm/core/code/AbortException.java
@@ -0,0 +1,12 @@
+package dev.vml.es.acm.core.code;
+
+public class AbortException extends RuntimeException {
+
+ public AbortException(String message) {
+ super(message);
+ }
+
+ public AbortException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java b/core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java
index 97a2c33d..66b4c2c3 100644
--- a/core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java
+++ b/core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java
@@ -151,6 +151,23 @@ public void setSkipped(boolean skipped) {
this.skipped = skipped;
}
+ public boolean isAborted() {
+ return getCodeContext()
+ .getOsgiContext()
+ .getService(ExecutionQueue.class)
+ .isAborted(getId());
+ }
+
+ public void abort() {
+ throw new AbortException("Execution aborted gracefully!");
+ }
+
+ public void checkAborted() throws AbortException {
+ if (isAborted()) {
+ abort();
+ }
+ }
+
public Inputs getInputs() {
return inputs;
}
@@ -174,6 +191,7 @@ public Conditions getConditions() {
private void customizeBinding() {
Binding binding = getCodeContext().getBinding();
+ binding.setVariable("context", this);
binding.setVariable("schedules", schedules);
binding.setVariable("arguments", inputs); // TODO deprecated
binding.setVariable("inputs", inputs);
diff --git a/core/src/main/java/dev/vml/es/acm/core/code/ExecutionQueue.java b/core/src/main/java/dev/vml/es/acm/core/code/ExecutionQueue.java
index 90732afa..8f8a2e4f 100644
--- a/core/src/main/java/dev/vml/es/acm/core/code/ExecutionQueue.java
+++ b/core/src/main/java/dev/vml/es/acm/core/code/ExecutionQueue.java
@@ -10,6 +10,7 @@
import dev.vml.es.acm.core.util.StreamUtils;
import java.util.*;
import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -52,7 +53,12 @@ public class ExecutionQueue implements JobExecutor, EventListener {
@AttributeDefinition(
name = "Async Poll Interval",
description = "Interval in milliseconds to poll for job status.")
- long asyncPollInterval() default 500L;
+ long asyncPollInterval() default 750L;
+
+ @AttributeDefinition(
+ name = "Abort Timeout",
+ description = "Time in milliseconds to wait for graceful abort before forcing it.")
+ long abortTimeout() default -1;
}
@Reference
@@ -69,6 +75,8 @@ public class ExecutionQueue implements JobExecutor, EventListener {
private ExecutorService jobAsyncExecutor;
+ private final MapThis action will abort current code execution.
-Be aware that aborting execution may leave data in an inconsistent state.
+
+ context.checkAborted().
+
+
+ context.checkAborted() at safe checkpoints (e.g., at the beginning of each loop iteration) to enable graceful termination and prevent data corruption.
+