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/Process.java b/src/main/java/sirius/biz/process/Process.java index 290b2ad08..253dea595 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. *

@@ -220,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. *

@@ -387,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"; @@ -589,6 +607,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; } @@ -689,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/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..ef2af5eb3 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); @@ -325,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/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/java/sirius/biz/process/Processes.java b/src/main/java/sirius/biz/process/Processes.java index 1b02478e2..f780dc2a7 100644 --- a/src/main/java/sirius/biz/process/Processes.java +++ b/src/main/java/sirius/biz/process/Processes.java @@ -163,9 +163,9 @@ 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.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); @@ -503,6 +504,20 @@ 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.setWaitingTime((int) Duration.between(process.getCreated(), process.getStarted()).getSeconds()); + process.setState(ProcessState.RUNNING); + }); + } + /** * Marks a process as canceled. *

@@ -512,7 +527,9 @@ protected boolean updateState(String processId, ProcessState newState) { * @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); @@ -557,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)); } @@ -1003,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; @@ -1016,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)) { 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. 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 @@ - +