Skip to content

Feature/load new lifecycle config#208

Draft
NicolasFussberger wants to merge 18 commits into
eclipse-score:mainfrom
etas-contrib:feature/load-new-lifecycle-config
Draft

Feature/load new lifecycle config#208
NicolasFussberger wants to merge 18 commits into
eclipse-score:mainfrom
etas-contrib:feature/load-new-lifecycle-config

Conversation

@NicolasFussberger
Copy link
Copy Markdown
Contributor

@NicolasFussberger NicolasFussberger commented May 19, 2026

  • Introduces an interface in src/launch_manager_daemon/config for loading a launch manager configuration file
  • Introduce an implementation of this interface using flatbuffer
  • Introduce unit tests for the new code

Note: The code is not actually in use yet. The python script which does the translation from the user-facing json configuration to the flatbuffer json format is not part of the PR and will be added in a follow up PR.
Furthermore, the launch manager code needs to be adapted to use the new structure.

Background:

  • In the last months we had defined a new user-facing configuration format (see src/launch_manager_daemon/config/config_schema/launch_manager.schema.json).
  • However, this new format is currently still mapped to the old configuration files (to processes / process groups). So the code is not actually reading the new configuration file

Relates To: #209

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 19, 2026

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.4.2) and connecting to it...
INFO: Invocation ID: 18448b84-b23d-42bb-90f0-11b5cebf5262
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 (30 packages loaded, 10 targets configured)

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

Analyzing: target //:license-check (87 packages loaded, 13 targets configured)

Analyzing: target //:license-check (141 packages loaded, 2727 targets configured)

Analyzing: target //:license-check (150 packages loaded, 5404 targets configured)

Analyzing: target //:license-check (155 packages loaded, 5453 targets configured)

Analyzing: target //:license-check (158 packages loaded, 7525 targets configured)

Analyzing: target //:license-check (162 packages loaded, 10142 targets configured)

Analyzing: target //:license-check (162 packages loaded, 10142 targets configured)

INFO: Analyzed target //:license-check (163 packages loaded, 10268 targets configured).
[12 / 16] JavaToolchainCompileClasses external/rules_java+/toolchains/platformclasspath_classes; 0s disk-cache, processwrapper-sandbox ... (2 actions running)
[14 / 16] JavaToolchainCompileBootClasspath external/rules_java+/toolchains/platformclasspath.jar; 0s disk-cache, processwrapper-sandbox
[15 / 16] Building license.check.license_check.jar (); 0s disk-cache, multiplex-worker
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: 23.691s, Critical Path: 2.69s
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>]

@github-actions
Copy link
Copy Markdown

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

Comment thread MODULE.bazel
module_name = "score_baselibs",
commit = "498a4b256c9073602140243d30c33b106e279f75",
remote = "https://github.com/eclipse-score/baselibs.git",
)
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.

@paulquiring Any idea when a new baselibs release will be available containing the new flatbuffer functionality?

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.

Comment thread src/launch_manager_daemon/config/include/config.hpp
Comment thread src/launch_manager_daemon/config/include/config.hpp
Comment thread src/launch_manager_daemon/config/include/config.hpp Outdated
Comment thread src/launch_manager_daemon/config/include/config.hpp
Comment thread src/launch_manager_daemon/config/include/config.hpp Outdated
}

private:
friend class Builder;
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.

A comment would have been helpful to mention that this saves the need to write getters in the builder class.

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.

Discussed in meeting. The friend is needed to access the private constructor of the Config class.
This is in line with typical builder pattern implementation

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.

Moving the Builder class to the outer scope so that it is not a nested class anymore.
Nested class is not required and may be discouraged by some SCA rules.

Done in f70d64b

Comment thread src/launch_manager_daemon/config/include/config.hpp
Comment thread src/launch_manager_daemon/config/src/flatbuffer_config_loader.cpp
{
if (ev != nullptr && ev->key() != nullptr)
{
result.emplace(ev->key()->str(), safeString(ev->value()));
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 guess the key cannot be nullptr?

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.

In the flatbuffer schema I have defined it as

// A key-value pair representing an environment variable.
table EnvironmentalVariable {
  key:string (required);
  value:string (required);
}

So I think if an environment variable is there, the key and value must be available.

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.

Consider adding asserts for values that are never expected to be nullptr.
safestring can be removed for value() because it is required in the schema

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.

Asserts added in 99cdb9c


SwitchRunTargetAction convertRequiredSwitchRunTargetAction(const fb::SwitchRunTargetAction* sa)
{
return convertSwitchRunTargetAction(sa).value();
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.

value can throw.
Image

.value_or(std::string{}) as alternative?

Copy link
Copy Markdown
Contributor Author

@NicolasFussberger NicolasFussberger May 21, 2026

Choose a reason for hiding this comment

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

It should never happen because the SwitchRunTargetRecoveryAction is defined as required in this context:

// Configuration for a Run Target representing an operational mode of the system.
table RunTarget {
 ....
  // Recovery action when a component in this Run Target fails.
  recovery_action:SwitchRunTargetAction (required);
}

However to be defensive against potential future changes on the schema, we could:

  • Add a try/catch within parseFlatbuffer and return score::cpp::make_unexpected(IConfigLoader::Error::InvalidFormat); in case there is any exception.
  • And/or put in an assertion
    auto opt=convertSwitchRunTargetAction(sa)
    assert(opt.has_value() && "SwitchRunTargetAction is null although it is expected to be a mandatory configuration");
    return opt.value();

What do you think?

Copy link
Copy Markdown
Contributor Author

@NicolasFussberger NicolasFussberger May 21, 2026

Choose a reason for hiding this comment

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

Discussed: We shall add asserts

Done in 99cdb9c

Comment thread MODULE.bazel
module_name = "score_baselibs",
commit = "498a4b256c9073602140243d30c33b106e279f75",
remote = "https://github.com/eclipse-score/baselibs.git",
)
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.

constexpr int32_t kExpectedSchemaVersion = 1;
constexpr double kSecondsToMilliseconds = 1000.0;

uint32_t secondsToMs(double seconds)
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.

#include <iostream>
#include <cstdint>
#include <string_view>
#include <cassert>

constexpr double kSecondsToMilliseconds = 1000.0;

uint32_t secondsToMs(double seconds)
{
    return static_cast<uint32_t>(seconds * kSecondsToMilliseconds);
}

int main () {
    assert(secondsToMs(0.000500) == 0);// holds is this a problem ?
    assert(secondsToMs(0.001000) == 1);
}

if this is a problem we might need to change the fbs

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 guess this is not an issue.

The user is configuring all time values in seconds

Though we should reconsider whether the want to switch the configuration to ms.

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.

How to safeguard that the user does not configure anything < 1ms?

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.

Adding an assertion for now, to be moved to schema validation later

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.

Assertion added in a1f2860

Assertion fails if configured seconds > 0 but is rounded down to 0ms.

Comment thread src/launch_manager_daemon/config/src/new_lm_flatcfg.fbs
@paulquiring
Copy link
Copy Markdown
Contributor

review from my side is finished.


uint32_t secondsToMs(double seconds)
{
return static_cast<uint32_t>(seconds * kSecondsToMilliseconds);
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.

Could this result in UB if seconds < 0 or > UINT32_MAX?

Schema validator should take care about negative input, but static analysis or compiler warnings may not know about this. However we don't have cap on max timeout in schema.

Maybe we can log warning if value is too small or too big and apply some boundary values

Config::Builder builder;

// initial_run_target is a required field, guaranteed non-null by the schema and verifier.
builder.setInitialRunTarget(config->initial_run_target()->str());
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 have mixed feelings about defensive coding, but if somebody prepares crazy config (bypassing verifier), we will crash instead of printing error message and exiting.

However if somebody is doing this, then maybe, they should not expect much support from launch manager.

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 had similar discussion with Paul. For fields that are marked as required in the flatbuffer schema, flatbuffer will ensure that this is not nullptr.

Nevertheless, we decided for adding additional asserts to check that those are not nullptr.
Adding further nullptr checks seemed redundant and would make covering this code via tests almost impossible.

Is that approach fine for you?


struct Sandbox
{
uint32_t uid{};
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.

uid_t

Suggested change
uint32_t uid{};
uid_t uid{};

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 here efe69a0

struct Sandbox
{
uint32_t uid{};
uint32_t gid{};
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
uint32_t gid{};
gid_t gid{};

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 here efe69a0

{
uint32_t ready_timeout_ms{};
uint32_t shutdown_timeout_ms{};
std::unordered_map<std::string, std::string> environmental_variables;
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.

from: https://linux.die.net/man/3/execve
we need a format like this:

char *env[] = { "HOME=/usr/home", "LOGNAME=home", (char *)0 };

this will do the job

std::vector<const char*> environmental_variables = {"HOME=/usr/home", "LOGNAME=home" , nullptr };
execve(... , const_cast<char* const*>(environmental_variables .data()));

Copy link
Copy Markdown
Contributor Author

@NicolasFussberger NicolasFussberger May 22, 2026

Choose a reason for hiding this comment

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

With std::vector<const char*> I think there is the question where do the strings live in case they are not constant char sequences. We would require a combination of

    std::vector<const char*> pointers_;
    std::vector<std::string> strings_;

Copy link
Copy Markdown
Contributor Author

@NicolasFussberger NicolasFussberger May 22, 2026

Choose a reason for hiding this comment

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

Please tell me if this to too overcomplicated. But I see the use case that we might want to add an environment variable before starting the process that was not configured by the user. We had this use case already in the past.

This could be achieved rather cleanly with a dedicated class, that stores both std::vector<const char*> and std::vector<std::string>

class EnvironmentVariable
{
  public:
    EnvironmentVariable(std::string_view key, std::string_view value);

    std::string_view key() const;
    std::string_view value() const;
    const char* c_str() const;

  private:
    std::string entry_; // internal storage in "key=value" format.
    std::size_t key_length_;
};

class Environment
{
  public:
    using const_iterator = std::vector<EnvironmentVariable>::const_iterator;

    Environment() = default;

    Environment(const Environment&) = delete;
    Environment& operator=(const Environment&) = delete;
    Environment(Environment&& other) noexcept;
    Environment& operator=(Environment&& other) noexcept;

    ~Environment() = default;

    void reserve(std::size_t count);
    
    void add(std::string_view key, std::string_view value);

    const_iterator begin() const;
    const_iterator end() const;
    std::size_t size() const;

    // Access format expected by execve systemcall
    const std::vector<const char*>& envp() const;

  private:
    void rebuildPointers();
    std::vector<EnvironmentVariable> entries_;
    std::vector<const char*> pointers_;
};

The alternative that I see is to keep doing what we do know:
char* envp_[static_cast<std::size_t>(score::lcm::internal::kEnvArraySize)];

However, than we have fixed size and need to deal with the scenario this size may be exceeded by the configuration. Still there is the problem of string ownership for which I believe we will need to wrap it in a dedicated class.

{
uint32_t uid{};
uint32_t gid{};
std::vector<uint32_t> supplementary_group_ids;
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
std::vector<uint32_t> supplementary_group_ids;
std::vector<gid_t > supplementary_group_ids;

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 here efe69a0

gid_t gid{};
std::vector<gid_t> supplementary_group_ids;
std::optional<std::string> security_policy;
std::string scheduling_policy;
Copy link
Copy Markdown
Contributor Author

@NicolasFussberger NicolasFussberger May 22, 2026

Choose a reason for hiding this comment

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

Do we want to translate scheduling_policy already to an integer?

I think for logging it might be nice to have the string available, so you can log "SCHED_OTHER" instead of "scheduling policy: 1".

Though I don't have a string opinion. Storing the same string over and over again is also not nice.

Probably makes sense to already translate it to int

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.

3 participants