From fbbae5c89434fcd9250182efc44ea2d5a3e3d828 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 9 Dec 2024 22:44:46 +0100 Subject: [PATCH 1/7] Add a separate state for waiting processes This will indicate a process that is created but not yet actively processed. This may be the case when the maximum number of parallel processes in the queue of the processes is currently execeeded. Fixes: SIRI-681 --- src/main/java/sirius/biz/process/ProcessState.java | 7 +++++++ src/main/resources/biz_de.properties | 1 + 2 files changed, 8 insertions(+) diff --git a/src/main/java/sirius/biz/process/ProcessState.java b/src/main/java/sirius/biz/process/ProcessState.java index be0cd9c55..d8ab8b1a5 100644 --- a/src/main/java/sirius/biz/process/ProcessState.java +++ b/src/main/java/sirius/biz/process/ProcessState.java @@ -20,6 +20,13 @@ public enum ProcessState { */ STANDBY, + /** + * Represents a process which is waiting for the execution to start. + *

+ * This may be the case when the number of parallel processes is limited and the process is waiting for a slot. + */ + WAITING, + /** * Represents a process which is actively running. */ diff --git a/src/main/resources/biz_de.properties b/src/main/resources/biz_de.properties index 1d82c5eee..c4619d9f4 100644 --- a/src/main/resources/biz_de.properties +++ b/src/main/resources/biz_de.properties @@ -868,6 +868,7 @@ ProcessState.CANCELED = Abgebrochen ProcessState.RUNNING = Aktiv ProcessState.STANDBY = Standby ProcessState.TERMINATED = Beendet +ProcessState.WAITING = In Warteschlange Processes.messageLimitReached = Die Nachricht '${type}' trat insgesamt ${count} Mal auf. Für bessere Übersichtlichkeit wurden aber nur die ersten ${limit} Vorkommen aufgezeichnet. Processes.restarted = Der Prozess wurde erneut gestartet: ${reason} ProfileController.invalidOldPassword = Das alte Password ist inkorrekt. From 7c9dceffe9645e9f56afd158339530967ecdfa75 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 9 Dec 2024 22:46:58 +0100 Subject: [PATCH 2/7] Create processes in the state waiting instead of running This uses the new state actively for newly initialized processes. Fixes: SIRI-681 --- src/main/java/sirius/biz/process/Processes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/sirius/biz/process/Processes.java b/src/main/java/sirius/biz/process/Processes.java index 1b02478e2..1498961c3 100644 --- a/src/main/java/sirius/biz/process/Processes.java +++ b/src/main/java/sirius/biz/process/Processes.java @@ -163,7 +163,7 @@ public String createProcess(@Nullable String type, process.setUserName(user.getUserName()); process.setTenantId(user.getTenantId()); process.setTenantName(user.getTenantName()); - process.setState(ProcessState.RUNNING); + process.setState(ProcessState.WAITING); process.setProcessType(type); process.setStarted(LocalDateTime.now()); process.setPersistencePeriod(persistencePeriod); From adabac1dfa14eff78087c323e37d8b1099f9134c Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 9 Dec 2024 22:49:21 +0100 Subject: [PATCH 3/7] Track the creation timestamp separately from the started timestamp A process may wait a while before it is actually executed when some other processes are queued before it. We track both timestamps to be able to calculate the waiting time of processes, which may be a useful metric for analysis and optimization. Fixes: SIRI-681 --- src/main/java/sirius/biz/process/Process.java | 18 ++++++++++++++++++ .../java/sirius/biz/process/Processes.java | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/sirius/biz/process/Process.java b/src/main/java/sirius/biz/process/Process.java index 290b2ad08..706703196 100644 --- a/src/main/java/sirius/biz/process/Process.java +++ b/src/main/java/sirius/biz/process/Process.java @@ -197,6 +197,15 @@ public class Process extends SearchableEntity { public static final Mapping ADMIN_TIMINGS = Mapping.named("adminTimings"); private final StringIntMap adminTimings = new StringIntMap(); + /** + * Contains the timestamp when the process was created / initialized. + *

+ * Note, for standby processes, this contains the timestamp of the last invocation. + */ + public static final Mapping CREATED = Mapping.named("created"); + @NullAllowed + private LocalDateTime created; + /** * Contains the timestamp when the process was started. *

@@ -589,6 +598,15 @@ public StringIntMap getAdminTimings() { return adminTimings; } + public LocalDateTime getCreated() { + return created; + } + + public Process setCreated(LocalDateTime created) { + this.created = created; + return this; + } + public LocalDateTime getStarted() { return started; } diff --git a/src/main/java/sirius/biz/process/Processes.java b/src/main/java/sirius/biz/process/Processes.java index 1498961c3..155b28d00 100644 --- a/src/main/java/sirius/biz/process/Processes.java +++ b/src/main/java/sirius/biz/process/Processes.java @@ -165,7 +165,7 @@ public String createProcess(@Nullable String type, process.setTenantName(user.getTenantName()); process.setState(ProcessState.WAITING); process.setProcessType(type); - process.setStarted(LocalDateTime.now()); + process.setCreated(LocalDateTime.now()); process.setPersistencePeriod(persistencePeriod); process.getContext().modify().putAll(context); @@ -399,6 +399,7 @@ private Process createStandbyProcessInLock(String type, String title, String ten process.setTenantId(tenantId); process.setTenantName(tenantName); process.setState(ProcessState.STANDBY); + process.setCreated(LocalDateTime.now()); process.setStarted(LocalDateTime.now()); elastic.update(process); From d3b2ec69cbcbf6003ac0790f36a8ee448f7e44d2 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 9 Dec 2024 22:52:26 +0100 Subject: [PATCH 4/7] Mark the process as running when execution is started This switches the process from state waiting to state running. Fixes: SIRI-681 --- .../biz/jobs/batch/BatchProcessTaskExecutor.java | 2 ++ .../java/sirius/biz/process/ProcessContext.java | 5 +++++ .../java/sirius/biz/process/ProcessEnvironment.java | 5 +++++ src/main/java/sirius/biz/process/Processes.java | 13 +++++++++++++ 4 files changed, 25 insertions(+) diff --git a/src/main/java/sirius/biz/jobs/batch/BatchProcessTaskExecutor.java b/src/main/java/sirius/biz/jobs/batch/BatchProcessTaskExecutor.java index 383a32f2f..16a43837c 100644 --- a/src/main/java/sirius/biz/jobs/batch/BatchProcessTaskExecutor.java +++ b/src/main/java/sirius/biz/jobs/batch/BatchProcessTaskExecutor.java @@ -61,6 +61,7 @@ protected boolean shouldExecutePartially() { } protected void executeInProcess(String factoryId, ProcessContext process) { + process.markRunning(); process.log(ProcessLog.info().withNLSKey("BatchProcessTaskExecutor.started")); try { jobs.findFactory(factoryId, BatchProcessJobFactory.class).executeTask(process); @@ -76,6 +77,7 @@ protected void executeInProcess(String factoryId, ProcessContext process) { } protected void partiallyExecuteInProcess(String factoryId, ProcessContext process) { + process.markRunning(); process.log(ProcessLog.info().withNLSKey("BatchProcessTaskExecutor.started")); Watch watch = Watch.start(); try { diff --git a/src/main/java/sirius/biz/process/ProcessContext.java b/src/main/java/sirius/biz/process/ProcessContext.java index 30436b62b..b6561d1ee 100644 --- a/src/main/java/sirius/biz/process/ProcessContext.java +++ b/src/main/java/sirius/biz/process/ProcessContext.java @@ -159,6 +159,11 @@ public interface ProcessContext extends TaskContextAdapter { */ boolean isErroneous(); + /** + * Marks the process as running. + */ + void markRunning(); + /** * Marks the process as completed. *

diff --git a/src/main/java/sirius/biz/process/ProcessEnvironment.java b/src/main/java/sirius/biz/process/ProcessEnvironment.java index 3c8b8c395..e9826e9b7 100644 --- a/src/main/java/sirius/biz/process/ProcessEnvironment.java +++ b/src/main/java/sirius/biz/process/ProcessEnvironment.java @@ -269,6 +269,11 @@ public boolean isErroneous() { return processes.fetchProcess(processId).map(Process::isErrorneous).orElse(true); } + @Override + public void markRunning() { + processes.markRunning(processId); + } + @Override public void markCompleted(int computationTimeInSeconds) { processes.reportLimitedMessages(processId, messageCountsPerType, limitsPerType); diff --git a/src/main/java/sirius/biz/process/Processes.java b/src/main/java/sirius/biz/process/Processes.java index 155b28d00..07e337458 100644 --- a/src/main/java/sirius/biz/process/Processes.java +++ b/src/main/java/sirius/biz/process/Processes.java @@ -504,6 +504,19 @@ protected boolean updateState(String processId, ProcessState newState) { return modify(processId, null, process -> process.setState(newState)); } + /** + * Marks a process as running. + * + * @param processId the process to update + * @return true if the process was successfully modified, false otherwise + */ + protected boolean markRunning(String processId) { + return modify(processId, process -> process.getState() == ProcessState.WAITING, process -> { + process.setStarted(LocalDateTime.now()); + process.setState(ProcessState.RUNNING); + }); + } + /** * Marks a process as canceled. *

From 5d0c72938191dd756837fa9cd2f461f83af5ffbc Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 9 Dec 2024 22:54:45 +0100 Subject: [PATCH 5/7] Calculate the time the process had to wait before execution started This may be useful for analysing whether some optimization on the queues or parallelization settings could be made. Fixes: SIRI-681 --- src/main/java/sirius/biz/process/Process.java | 18 ++++++++++++++++++ .../java/sirius/biz/process/Processes.java | 1 + 2 files changed, 19 insertions(+) diff --git a/src/main/java/sirius/biz/process/Process.java b/src/main/java/sirius/biz/process/Process.java index 706703196..7dde36f46 100644 --- a/src/main/java/sirius/biz/process/Process.java +++ b/src/main/java/sirius/biz/process/Process.java @@ -229,6 +229,15 @@ public class Process extends SearchableEntity { @NullAllowed private LocalDateTime completed; + /** + * Contains the waiting time in seconds. + *

+ * This is the time between the creation of the process and the actual start of the process. + */ + public static final Mapping WAITING_TIME = Mapping.named("waitingTime"); + @NullAllowed + private int waitingTime; + /** * Contains the estimated computation time performed in this process in seconds. *

@@ -707,6 +716,15 @@ public void setWarnings(boolean warnings) { this.warnings = warnings; } + public int getWaitingTime() { + return waitingTime; + } + + public Process setWaitingTime(int waitingTime) { + this.waitingTime = waitingTime; + return this; + } + public int getComputationTime() { return computationTime; } diff --git a/src/main/java/sirius/biz/process/Processes.java b/src/main/java/sirius/biz/process/Processes.java index 07e337458..5b5cdd49f 100644 --- a/src/main/java/sirius/biz/process/Processes.java +++ b/src/main/java/sirius/biz/process/Processes.java @@ -513,6 +513,7 @@ protected boolean updateState(String processId, ProcessState newState) { protected boolean markRunning(String processId) { return modify(processId, process -> process.getState() == ProcessState.WAITING, process -> { process.setStarted(LocalDateTime.now()); + process.setWaitingTime((int) Duration.between(process.getCreated(), process.getStarted()).getSeconds()); process.setState(ProcessState.RUNNING); }); } From fd89c0fe29bebb73e80f59ad387d18845950a407 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 9 Dec 2024 22:56:44 +0100 Subject: [PATCH 6/7] Ensure waiting processes are rendered properly and provide the correct actions They are currently rendered like running processes but without the spinning active icon next to the process name. Also a waiting process can be cancelled and debugging can be toggled. Fixes: SIRI-681 --- src/main/java/sirius/biz/process/Process.java | 2 +- .../java/sirius/biz/process/ProcessEnvironment.java | 4 +++- src/main/java/sirius/biz/process/Processes.java | 13 ++++++++++--- .../templates/biz/process/process.html.pasta | 4 ++-- .../templates/biz/process/processes.html.pasta | 4 ++-- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/java/sirius/biz/process/Process.java b/src/main/java/sirius/biz/process/Process.java index 7dde36f46..253dea595 100644 --- a/src/main/java/sirius/biz/process/Process.java +++ b/src/main/java/sirius/biz/process/Process.java @@ -405,7 +405,7 @@ public boolean shouldAutorefresh() { */ public String getStateColor() { return switch (state) { - case RUNNING -> "blue"; + case WAITING, RUNNING -> "blue"; case STANDBY -> "violet-light"; case TERMINATED -> "green"; case CANCELED -> "yellow"; diff --git a/src/main/java/sirius/biz/process/ProcessEnvironment.java b/src/main/java/sirius/biz/process/ProcessEnvironment.java index e9826e9b7..ef2af5eb3 100644 --- a/src/main/java/sirius/biz/process/ProcessEnvironment.java +++ b/src/main/java/sirius/biz/process/ProcessEnvironment.java @@ -330,7 +330,9 @@ public void cancel() { @Override public boolean isActive() { return processes.fetchProcess(processId) - .map(proc -> proc.getState() == ProcessState.RUNNING || proc.getState() == ProcessState.STANDBY) + .map(process -> process.getState() == ProcessState.WAITING + || process.getState() == ProcessState.RUNNING + || process.getState() == ProcessState.STANDBY) .orElse(false) && tasks.isRunning(); } diff --git a/src/main/java/sirius/biz/process/Processes.java b/src/main/java/sirius/biz/process/Processes.java index 5b5cdd49f..844cdb7df 100644 --- a/src/main/java/sirius/biz/process/Processes.java +++ b/src/main/java/sirius/biz/process/Processes.java @@ -527,7 +527,9 @@ protected boolean markRunning(String processId) { * @return true if the process was successfully modified, false otherwise */ protected boolean markCanceled(String processId) { - return modify(processId, process -> process.getState() == ProcessState.RUNNING, process -> { + return modify(processId, process -> { + return process.getState() == ProcessState.WAITING || process.getState() == ProcessState.RUNNING; + }, process -> { process.setErrorneous(true); process.setCanceled(LocalDateTime.now()); process.setState(ProcessState.CANCELED); @@ -572,7 +574,8 @@ protected boolean markWarnings(String processId) { */ protected boolean changeDebugging(String processId, boolean debuggingEnabled) { return modify(processId, - process -> process.getState() == ProcessState.RUNNING + process -> process.getState() == ProcessState.WAITING + || process.getState() == ProcessState.RUNNING || process.getState() == ProcessState.STANDBY, process -> process.setDebugging(debuggingEnabled)); } @@ -1018,7 +1021,11 @@ public void execute(String processId, Consumer task) { */ public boolean hasActiveProcesses() { try { - return queryProcessesForCurrentUser().eq(Process.STATE, ProcessState.RUNNING).exists(); + return queryProcessesForCurrentUser().where(elastic.filters() + .oneInField(Process.STATE, + List.of(ProcessState.WAITING, + ProcessState.RUNNING)) + .build()).exists(); } catch (Exception exception) { Exceptions.handle(Log.SYSTEM, exception); return false; diff --git a/src/main/resources/default/templates/biz/process/process.html.pasta b/src/main/resources/default/templates/biz/process/process.html.pasta index b58b998a9..08a3479bd 100644 --- a/src/main/resources/default/templates/biz/process/process.html.pasta +++ b/src/main/resources/default/templates/biz/process/process.html.pasta @@ -49,7 +49,7 @@ - + - + - + @@ -54,7 +54,7 @@ - + Date: Mon, 9 Dec 2024 23:00:36 +0100 Subject: [PATCH 7/7] Sort processes by creation and started timestamp The later sort criteria has to be left intact as all already executed processes don't have the new creation timestamp field and would be sorted incorrectly without the tie-breaker. Fixes: SIRI-681 --- src/main/java/sirius/biz/process/Processes.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/sirius/biz/process/Processes.java b/src/main/java/sirius/biz/process/Processes.java index 844cdb7df..f780dc2a7 100644 --- a/src/main/java/sirius/biz/process/Processes.java +++ b/src/main/java/sirius/biz/process/Processes.java @@ -1038,7 +1038,8 @@ public boolean hasActiveProcesses() { * @return a query for all processes visible to the current user */ public ElasticQuery queryProcessesForCurrentUser() { - ElasticQuery query = elastic.select(Process.class).orderDesc(Process.STARTED); + ElasticQuery query = + elastic.select(Process.class).orderDesc(Process.CREATED).orderDesc(Process.STARTED); UserInfo user = UserContext.getCurrentUser(); if (!user.hasPermission(ProcessController.PERMISSION_MANAGE_ALL_PROCESSES)) {