Skip to content

Comments

extract step execution to commons#48

Draft
benrejebmoh wants to merge 1 commit intomainfrom
extract-step-execution-to-commons
Draft

extract step execution to commons#48
benrejebmoh wants to merge 1 commit intomainfrom
extract-step-execution-to-commons

Conversation

@benrejebmoh
Copy link

PR Summary

We need to use the step execution service logic in other services, so common behaviour is extracted into the lib monitor-commons

Signed-off-by: benrejebmoh <mohamed.ben-rejeb@rte-france.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a publisher-based pattern for step execution tracking in the monitor commons. Three new interfaces (ReportPublisher, StepStatusPublisher, and StepExecutionInterface) define contracts for publishing reports and step status updates, with default orchestration methods. The StepExecutionService is refactored to implement this interface and delegate to publishers instead of handling notifications directly.

Changes

Cohort / File(s) Summary
Publisher Interfaces
monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/ReportPublisher.java, monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepStatusPublisher.java
New functional interfaces for publishing reports and step status updates. ReportPublisher defines sendReport(R reportInfos), StepStatusPublisher defines updateStepStatus(UUID, ProcessExecutionStep).
Step Execution Interface
monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepExecutionInterface.java
New generic interface providing getters/setters for executionId and publisher access, plus three default orchestration methods: skipStep, executeStep (records status, executes runnable, sends report, handles completion/failure), and updateStepStatus.
Service Refactoring
monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/StepExecutionService.java
Implements StepExecutionInterface, refactored to use StepStatusPublisher and ReportPublisher instead of direct internal handling. Constructor updated to accept NotificationService and ReportService. Method signatures changed to use generic context types. Removes internal status update helper in favor of publisher delegation.

Sequence Diagram

sequenceDiagram
    participant Client
    participant StepExecutionService
    participant StepStatusPublisher
    participant ReportPublisher
    
    Client->>StepExecutionService: executeStep(context, step)
    StepExecutionService->>StepStatusPublisher: updateStepStatus(executionId, RUNNING)
    StepExecutionService->>Client: Execute stepExecution runnable
    Client-->>StepExecutionService: Runnable completes
    StepExecutionService->>ReportPublisher: sendReport(reportInfos)
    StepExecutionService->>StepStatusPublisher: updateStepStatus(executionId, COMPLETED)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Three publishers hop into the stage,
Step status and reports, all the rage!
With orchestration methods, so clean and bright,
The service now delegates with all its might,
A pattern refined—how delightfully right! 🎭

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: extracting step execution logic to a commons library for reuse across multiple services.
Description check ✅ Passed The description clearly explains the purpose of the PR: extracting step execution service logic into monitor-commons for reuse by other services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch extract-step-execution-to-commons

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@benrejebmoh benrejebmoh marked this pull request as draft February 24, 2026 15:49
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
25.9% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/StepExecutionService.java (1)

68-69: context.getReportInfos() called twice — cache in a local variable.

On Line 68, Objects.requireNonNull(context.getReportInfos()) validates and discards the reference; Line 69 calls it again. Cache the result so the null guard and the value use the same object reference.

♻️ Proposed fix
 public void executeStep(ProcessStepExecutionContext<C> context, ProcessStep<C> step) {
     setExecutionId(context.getProcessExecutionId());
+    ReportInfos reportInfos = Objects.requireNonNull(context.getReportInfos());
     executeStep(
             context.getStepExecutionId(),
             step.getType().getName(),
             context.getStepOrder(),
             context.getStartedAt(),
-            Objects.requireNonNull(context.getReportInfos()).reportUuid(),
-            context.getReportInfos(),
+            reportInfos.reportUuid(),
+            reportInfos,
             context.getResultInfos(),
             () -> step.execute(context)
     );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/StepExecutionService.java`
around lines 68 - 69, The code calls context.getReportInfos() twice—once inside
Objects.requireNonNull(...) and again on the next line—so cache the value in a
local variable (e.g., ReportInfos reportInfos =
Objects.requireNonNull(context.getReportInfos())) and then use
reportInfos.reportUuid() and reportInfos for the subsequent arguments; update
the block in StepExecutionService where
Objects.requireNonNull(context.getReportInfos()) and context.getReportInfos()
are used to reference the single cached variable.
monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepExecutionInterface.java (1)

65-68: sendReport inside the try block couples report-delivery failure to step failure.

If stepExecution.run() succeeds but getReportPublisher().sendReport(reportInfos) throws on Line 67, the catch on Line 69 fires and the step is marked FAILED — even though the computation completed successfully. Consider whether report-delivery failure should be treated as a step failure or handled separately (e.g., its own retry/dead-letter flow).

♻️ Proposed separation of concerns
 try {
     stepExecution.run();
-    getReportPublisher().sendReport(reportInfos);
     updateStepStatus(stepExecutionId, stepTypeName, stepOrder, startedAt, reportUuid, resultInfos, StepStatus.COMPLETED);
 } catch (Exception e) {
     try {
         updateStepStatus(stepExecutionId, stepTypeName, stepOrder, startedAt, reportUuid, resultInfos, StepStatus.FAILED);
     } catch (Exception statusEx) {
         e.addSuppressed(statusEx);
     }
     throw e;
 }
+// Report delivery is a separate concern; handle its failure independently.
+getReportPublisher().sendReport(reportInfos);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepExecutionInterface.java`
around lines 65 - 68, The call to getReportPublisher().sendReport(reportInfos)
is inside the try that surrounds stepExecution.run(), so a report-delivery
failure will incorrectly mark the step as FAILED; move or isolate sendReport so
it cannot change step outcome: keep stepExecution.run() and
updateStepStatus(..., StepStatus.COMPLETED) inside the primary try for the
computation, then perform getReportPublisher().sendReport(reportInfos) in a
separate try/catch (or a retry/dead-letter flow) that logs or records
report-delivery errors without calling updateStepStatus with a failure status;
reference stepExecution.run(), getReportPublisher().sendReport(reportInfos),
updateStepStatus(...) and StepStatus.COMPLETED when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepExecutionInterface.java`:
- Around line 65-73: When handling failures in the try/catch around
stepExecution.run(), ensure the original exception from stepExecution.run() is
not lost if updateStepStatus(...) throws: inside the catch(Exception e) block
catch and suppress any exception thrown by updateStepStatus(stepExecutionId,
stepTypeName, stepOrder, startedAt, reportUuid, resultInfos, StepStatus.FAILED)
(optionally logging it via the same logger/reporting mechanism) and then rethrow
the original exception `e`; i.e., wrap the call to updateStepStatus in its own
try/catch so updateStepStatus failures cannot overwrite the original exception
thrown by stepExecution.run().

In
`@monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/StepExecutionService.java`:
- Around line 27-31: StepExecutionService stores executionId as instance state
causing a race between setExecutionId(...) and the interface default methods
(skipStep/executeStep) that call getExecutionId(); fix by removing shared
mutable state: either (A) replace the executionId field with a ThreadLocal<UUID>
(e.g., executionIdHolder) and ensure you call executionIdHolder.remove() in a
finally block after each step, updating setExecutionId/getExecutionId to use the
ThreadLocal, or (B, preferred) change the StepExecutionInterface API to remove
getExecutionId()/setExecutionId() and add UUID executionId as the first
parameter to skipStep and executeStep and propagate that parameter through all
implementing classes (including StepExecutionService) so no singleton holds
per-thread state.

---

Nitpick comments:
In
`@monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepExecutionInterface.java`:
- Around line 65-68: The call to getReportPublisher().sendReport(reportInfos) is
inside the try that surrounds stepExecution.run(), so a report-delivery failure
will incorrectly mark the step as FAILED; move or isolate sendReport so it
cannot change step outcome: keep stepExecution.run() and updateStepStatus(...,
StepStatus.COMPLETED) inside the primary try for the computation, then perform
getReportPublisher().sendReport(reportInfos) in a separate try/catch (or a
retry/dead-letter flow) that logs or records report-delivery errors without
calling updateStepStatus with a failure status; reference stepExecution.run(),
getReportPublisher().sendReport(reportInfos), updateStepStatus(...) and
StepStatus.COMPLETED when making this change.

In
`@monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/StepExecutionService.java`:
- Around line 68-69: The code calls context.getReportInfos() twice—once inside
Objects.requireNonNull(...) and again on the next line—so cache the value in a
local variable (e.g., ReportInfos reportInfos =
Objects.requireNonNull(context.getReportInfos())) and then use
reportInfos.reportUuid() and reportInfos for the subsequent arguments; update
the block in StepExecutionService where
Objects.requireNonNull(context.getReportInfos()) and context.getReportInfos()
are used to reference the single cached variable.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e3181e and e948dcf.

📒 Files selected for processing (4)
  • monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/ReportPublisher.java
  • monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepExecutionInterface.java
  • monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepStatusPublisher.java
  • monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/StepExecutionService.java

Comment on lines +65 to +73
try {
stepExecution.run();
getReportPublisher().sendReport(reportInfos);
updateStepStatus(stepExecutionId, stepTypeName, stepOrder, startedAt, reportUuid, resultInfos, StepStatus.COMPLETED);
} catch (Exception e) {
updateStepStatus(stepExecutionId, stepTypeName, stepOrder, startedAt, reportUuid, resultInfos, StepStatus.FAILED);
throw e;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Original exception e can be silently lost if updateStepStatus in the catch block throws.

If stepExecution.run() fails and updateStepStatus(..., FAILED) on Line 70 also throws (e.g., the notification service is unreachable), the throw e on Line 71 is never reached. The caller sees only the secondary updateStepStatus exception, and the root cause is permanently lost.

🐛 Proposed fix — suppress secondary exception
 } catch (Exception e) {
-    updateStepStatus(stepExecutionId, stepTypeName, stepOrder, startedAt, reportUuid, resultInfos, StepStatus.FAILED);
-    throw e;
+    try {
+        updateStepStatus(stepExecutionId, stepTypeName, stepOrder, startedAt, reportUuid, resultInfos, StepStatus.FAILED);
+    } catch (Exception statusEx) {
+        e.addSuppressed(statusEx);
+    }
+    throw e;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-commons/src/main/java/org/gridsuite/monitor/commons/steps/StepExecutionInterface.java`
around lines 65 - 73, When handling failures in the try/catch around
stepExecution.run(), ensure the original exception from stepExecution.run() is
not lost if updateStepStatus(...) throws: inside the catch(Exception e) block
catch and suppress any exception thrown by updateStepStatus(stepExecutionId,
stepTypeName, stepOrder, startedAt, reportUuid, resultInfos, StepStatus.FAILED)
(optionally logging it via the same logger/reporting mechanism) and then rethrow
the original exception `e`; i.e., wrap the call to updateStepStatus in its own
try/catch so updateStepStatus failures cannot overwrite the original exception
thrown by stepExecution.run().

Comment on lines +27 to +31
public class StepExecutionService<C extends ProcessConfig> implements StepExecutionInterface<ReportInfos> {

private final NotificationService notificationService;
private final ReportService reportService;
@Getter
@Setter
private UUID executionId;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Race condition on executionId in a singleton bean.

StepExecutionService is a Spring @Service (singleton). The pattern in skipStep and executeStep is: setExecutionId(...) on Lines 52/62 → delegate to interface default method → default method calls getExecutionId(). Between the write and the read, a second concurrent request can overwrite executionId, so Thread A's default method retrieves Thread B's ID.

The simplest fix is a ThreadLocal; a cleaner fix removes executionId from instance state entirely and threads it through call parameters.

🔒 Option A — ThreadLocal (minimal change)
-@Getter
-@Setter
-private UUID executionId;
+private final ThreadLocal<UUID> executionIdHolder = new ThreadLocal<>();
+
+@Override
+public UUID getExecutionId() {
+    return executionIdHolder.get();
+}
+
+@Override
+public void setExecutionId(UUID executionId) {
+    executionIdHolder.set(executionId);
+}

Make sure executionIdHolder.remove() is called after each step (e.g., in a finally block) to avoid stale values on pooled threads.

♻️ Option B — pass executionId explicitly through the interface (preferred, requires interface change)

Remove getExecutionId() / setExecutionId() from StepExecutionInterface and add executionId as the first parameter to skipStep and executeStep, eliminating the shared-state problem entirely.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public class StepExecutionService<C extends ProcessConfig> implements StepExecutionInterface<ReportInfos> {
private final NotificationService notificationService;
private final ReportService reportService;
@Getter
@Setter
private UUID executionId;
public class StepExecutionService<C extends ProcessConfig> implements StepExecutionInterface<ReportInfos> {
private final ThreadLocal<UUID> executionIdHolder = new ThreadLocal<>();
`@Override`
public UUID getExecutionId() {
return executionIdHolder.get();
}
`@Override`
public void setExecutionId(UUID executionId) {
executionIdHolder.set(executionId);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/StepExecutionService.java`
around lines 27 - 31, StepExecutionService stores executionId as instance state
causing a race between setExecutionId(...) and the interface default methods
(skipStep/executeStep) that call getExecutionId(); fix by removing shared
mutable state: either (A) replace the executionId field with a ThreadLocal<UUID>
(e.g., executionIdHolder) and ensure you call executionIdHolder.remove() in a
finally block after each step, updating setExecutionId/getExecutionId to use the
ThreadLocal, or (B, preferred) change the StepExecutionInterface API to remove
getExecutionId()/setExecutionId() and add UUID executionId as the first
parameter to skipStep and executeStep and propagate that parameter through all
implementing classes (including StepExecutionService) so no singleton holds
per-thread state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant