Skip to content

Graph refactor initial components - #370

Open
WilliamRoebuck wants to merge 15 commits into
eclipse-score:mainfrom
etas-contrib:feature/process-mon-refactor-1
Open

Graph refactor initial components#370
WilliamRoebuck wants to merge 15 commits into
eclipse-score:mainfrom
etas-contrib:feature/process-mon-refactor-1

Conversation

@WilliamRoebuck

Copy link
Copy Markdown
Contributor

This PR introduces some initial components required for an upcoming refactor to the Graph and ProcessInfoNode classes. Firstly, we want to introduce some components that are more easily understood and unit tested. The next PR will integrate these into existing code. Excluding interfaces and data-only classes, the new components are:

  • ComponentEventQueue - Allows us to handle component state changes sequentially in Graph
  • ProcessMonitor - Decouples ProcessInfoNode from Graph and allows us to simplify its APIs

The refactor aims to address the following concerns:

  • Race conditions throughout Graph
  • Support for different component types than POSIX processes, e.g. containers
  • Circular dependencies impeding unit test development
  • Outdated "PG state" based transition logic
  • Overlapping & confusing class responsibilities

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

License Check Results

🚀 The license check job ran with the Bazel command:

bazel run --lockfile_mode=error //:license-check

Status: ⚠️ Needs Review

Click to expand output
[License Check Output]
Extracting Bazel installation...
Starting local Bazel server (8.6.0) and connecting to it...
INFO: Invocation ID: b1999356-4e3f-4d11-86c1-a557fb2f34c8
Computing main repo mapping: 
Computing main repo mapping: 
Loading: 
Loading: 0 packages loaded
Loading: 0 packages loaded
Loading: 0 packages loaded
    currently loading: 
Loading: 0 packages loaded
    currently loading: 
Loading: 0 packages loaded
    currently loading: 
Analyzing: target //:license-check (1 packages loaded, 0 targets configured)
Analyzing: target //:license-check (1 packages loaded, 0 targets configured)

Analyzing: target //:license-check (33 packages loaded, 10 targets configured)

Analyzing: target //:license-check (85 packages loaded, 10 targets configured)

Analyzing: target //:license-check (144 packages loaded, 2767 targets configured)

Analyzing: target //:license-check (152 packages loaded, 5482 targets configured)

Analyzing: target //:license-check (156 packages loaded, 7404 targets configured)

Analyzing: target //:license-check (164 packages loaded, 8271 targets configured)

Analyzing: target //:license-check (164 packages loaded, 8271 targets configured)

Analyzing: target //:license-check (164 packages loaded, 8271 targets configured)

Analyzing: target //:license-check (167 packages loaded, 10159 targets configured)

Analyzing: target //:license-check (168 packages loaded, 10283 targets configured)

INFO: Analyzed target //:license-check (169 packages loaded, 10409 targets configured).
[12 / 16] JavaToolchainCompileClasses external/rules_java+/toolchains/platformclasspath_classes; 0s disk-cache, processwrapper-sandbox ... (2 actions, 1 running)
[15 / 16] [Prepa] Building license.check.license_check.jar ()
INFO: Found 1 target...
Target //:license.check.license_check up-to-date:
  bazel-bin/license.check.license_check
  bazel-bin/license.check.license_check.jar
INFO: Elapsed time: 24.709s, Critical Path: 2.71s
INFO: 16 processes: 12 internal, 3 processwrapper-sandbox, 1 worker.
INFO: Build completed successfully, 16 total actions
INFO: Running command line: bazel-bin/license.check.license_check ./formatted.txt <args omitted>
usage: org.eclipse.dash.licenses.cli.Main [-batch <int>] [-cd <url>]
       [-confidence <int>] [-ef <url>] [-excludeSources <sources>] [-help] [-lic
       <url>] [-project <shortname>] [-repo <url>] [-review] [-summary <file>]
       [-timeout <seconds>] [-token <token>]

@WilliamRoebuck
WilliamRoebuck force-pushed the feature/process-mon-refactor-1 branch from fe8eca0 to 04b715d Compare July 24, 2026 15:00
@WilliamRoebuck
WilliamRoebuck force-pushed the feature/process-mon-refactor-1 branch from 04b715d to 5001193 Compare July 24, 2026 15:02
@WilliamRoebuck
WilliamRoebuck force-pushed the feature/process-mon-refactor-1 branch from 5001193 to 9c02cd9 Compare July 24, 2026 15:11
@github-actions

Copy link
Copy Markdown

The created documentation from the pull request is available at: docu-html

@MaciejKaszynski MaciejKaszynski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, would really like to split this into separate folders because it's a bit unclear if we are at the component level or process level.

#include <cstdint>

namespace score {
namespace score

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the namespace should be score::mw::lifecycle so here score::mw::lifecycle::internal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This whole PR could update this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we use this namespace for the new code and then update the other namespaces with a separate PR?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would say let's just fix what is touched with this PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in bcbda43

{

namespace lcm {
namespace lcm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should also enable the concat-nested-namespaces clang-tidy rule to concatenate the nested namepsaces

256U, ///< Maximum number of ControlClient instances that should be created by state manager. If state manager create more instances than kMaxInstances, those instances will always return kCommunicationError when used
enum class ControlClientLimits : uint16_t
{
kControlClientMaxInstances = 256U, ///< Maximum number of ControlClient instances that should be created by state

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think you actually touched this line but could you convert this to just a variable and have the enum become a namespace. I think then this all would be less weird.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've reverted the changes in this file now, it wouldn't make sense to change this in this PR

TEST_F(ComponentEventQueueTest, WaitForEventsReturnsFalseOnEmptyQueue)
{
RecordProperty("Description", "Verify waitForEvents returns false promptly when no event has been pushed.");
EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{1}));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{1}));
EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{0}));

The queue should handle 0 here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Resolved in c87ac1c

namespace score::lcm::internal
{

class IComponentEventReceiver

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

briefs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in acaf3c9


/// @brief Start work on @p task and push the result to the event queue if the task completes
void doWork(Task task) override;
/// @brief Notify @p component that it has terminated with status @p status. If this is an error or finishes a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you space stuff out so it's more readable.

Suggested change
/// @brief Notify @p component that it has terminated with status @p status. If this is an error or finishes a
/// @brief Notify @p component that it has terminated with status @p status. If this is an error or finishes a

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in acaf3c9

void terminated(IComponent& component, int32_t status) override;

private:
void taskFinished(const Task& task, const score::cpp::expected_blank<IComponent::ComponentError>& error);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

still need briefs on private :) also feel like the name should be markFinished.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actually you might want to just make this a lambda in doWork

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Resolved in ccdddfe

using namespace testing;
using namespace score::lcm::internal;

class MockComponent : public IComponent

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you make this into a separate header for easier re-use.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in dc6aa46

class IComponentController
{
public:
virtual void doWork(Task task) = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are you sure about copying, seems like we could just always take a && here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Resolved in cd69ca9


/// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed
/// exclusively by the main thread, backed by a fixed-capacity MpscBoundedQueue.
class ComponentEventQueue final : public IComponentEventReceiver

@NicolasFussberger NicolasFussberger Jul 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we rename this interface to e.g. IComponentEventPublisher?
It looks like its for publishing events into the queue, not actually for reading events from the queue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, the name seems to be incorrect. Should we have two interfaces IComponentEventPublisher and IComponentEventSubscriber? And the queue would implement both

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in acaf3c9 and 2052521

};

/// @brief Capacity of the ComponentEventQueue.
constexpr std::size_t kComponentEventQueueSize = 1024U;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe copy the way it is done in MpscBoundedQueue, and make the ComponentEventQueue take the capacity as a type parameter:

template <typename T, std::size_t Capacity>
class MpscBoundedQueue

This would allow mocking the capacity in tests, having multiple queues with different capacities, etc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No longer relevant due to #367

Comment on lines +44 to +45
default:
break;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we add SCORE_LANGUAGE_FUTURECPP_UNREACHABLE here? If someone defines a new task type and does not add code to handle it the behaviour is not clear

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Resolved in ccdddfe

Comment on lines +48 to +60
if (res.has_value() && res.value() == IComponent::RequestState::kWaiting)
{
return;
}

if (res.has_value())
{
taskFinished(task, {});
}
else
{
taskFinished(task, score::cpp::make_unexpected(res.error()));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Personally I would find this easier to read if it were nested

Suggested change
if (res.has_value() && res.value() == IComponent::RequestState::kWaiting)
{
return;
}
if (res.has_value())
{
taskFinished(task, {});
}
else
{
taskFinished(task, score::cpp::make_unexpected(res.error()));
}
if (res.has_value())
{
if (res.value() == IComponent::RequestState::kWaiting)
{
return;
}
taskFinished(task, {});
}
else
{
taskFinished(task, score::cpp::make_unexpected(res.error()));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Resolved in ccdddfe

Comment on lines +84 to +91
else if (error.has_value())
{
event_queue_.push(ActivationSuccessful{node_index});
}
else
{
event_queue_.push(ActivationFailed{node_index, error.error()});
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is correct but because of the naming it reads the wrong way round- "if there is an error, push successful"...

Maybe rename the parameter to something like status or result?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Resolved in ccdddfe

Comment thread .clang-tidy Outdated
@@ -1,2 +1,2 @@
Checks: ' -*, bugprone-*, cert-*, clang-analyzer-core.*, clang-analyzer-cplusplus.*, clang-analyzer-security.*, cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-cstyle-cast, hicpp-* '
WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-*,hicpp-*'
WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-*'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why was this changed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I made all of my interfaces show up as errors 😄 . I've refined it now in acaf3c9, I added an option that allows classes with default destructors only

@danth
danth requested a deployment to workflow-approval July 27, 2026 10:42 — with GitHub Actions Waiting
@danth
danth requested a deployment to workflow-approval July 27, 2026 10:42 — with GitHub Actions Waiting
Comment on lines +131 to +135
RecordProperty("Description",
"Verify stop() causes a subsequently-called waitForEvents() to return false immediately "
"rather than blocking, matching the shutdown usage in ProcessGroupManager::deinitialize().");
queue_.stop();
EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{2000}));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test would still pass if it blocks. Or we are relying on the timeout of the test framework to make it fail

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't think there's any way to test this without fragility, I've changed the test slightly: ebc75c3

@danth
danth requested a deployment to workflow-approval July 27, 2026 10:54 — with GitHub Actions Waiting
@danth
danth requested a deployment to workflow-approval July 27, 2026 10:54 — with GitHub Actions Waiting
@danth
danth requested a deployment to workflow-approval July 27, 2026 11:00 — with GitHub Actions Waiting
@danth
danth requested a deployment to workflow-approval July 27, 2026 11:00 — with GitHub Actions Waiting
@danth
danth requested a deployment to workflow-approval July 27, 2026 12:42 — with GitHub Actions Waiting
@danth
danth requested a deployment to workflow-approval July 27, 2026 12:42 — with GitHub Actions Waiting
{

class ComponentEventQueueTest : public ::testing::Test
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we need to add here
RecordProperty("TestType", "interface-test");
RecordProperty("DerivationTechnique", "explorative-testing");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in acaf3c9

WilliamRoebuck and others added 7 commits July 28, 2026 09:38
This resolves several review comments:

- `taskFinished` is no longer present in the header
- Confusing nesting is improved by having a clear switch statement for
  each purpose
- By removing the default case we get a compiler warning if a possible
  value is not handled
@WilliamRoebuck
WilliamRoebuck force-pushed the feature/process-mon-refactor-1 branch from ccdddfe to bcbda43 Compare July 28, 2026 08:56
@WilliamRoebuck
WilliamRoebuck force-pushed the feature/process-mon-refactor-1 branch from 128f839 to 2052521 Compare July 28, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

4 participants