Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The test verifies the following scenarios end-to-end:
| `cpp_supervised_app` | C++ app with alive supervision and crash recovery |
| `rust_supervised_app` | Rust app with alive supervision and crash recovery |
| `cpp_lifecycle_app` | C++ app demonstrating basic lifecycle management |
| `cpp_supervised_app_simple` | C++ app demonstrating the plain `Alive` API (report Running, then periodic `ReportAlive()` heartbeats) without deadline/checkpoint supervision |
| `control_daemon` | State Manager app for requesting RunTarget transitions |

## Launch Manager Configuration
Expand Down
38 changes: 38 additions & 0 deletions examples/cpp_supervised_app_simple/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
load("@rules_cc//cc:defs.bzl", "cc_binary")

cc_binary(
name = "cpp_supervised_app_simple",
srcs = [
"main.cpp",
],
copts = select({
"@platforms//os:qnx": [],
"@platforms//os:linux": [],
}),
linkopts = select({
"@platforms//os:qnx": [
"-lsocket",
],
"@platforms//os:linux": [
"-lpthread",
"-lrt",
],
}),
visibility = ["//visibility:public"],
deps = [
"//score/launch_manager:alive_cc",
"//score/launch_manager:lifecycle_cc",
],
)
126 changes: 126 additions & 0 deletions examples/cpp_supervised_app_simple/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/********************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

#include <unistd.h>
#include <atomic>
#include <chrono>
#include <csignal>
#include <cstdint>
#include <iostream>
#include <optional>
#include <thread>

#ifdef __linux__
#include <linux/prctl.h>
#include <sys/prctl.h>
#endif

#include <score/mw/lifecycle/alive.h>
#include <score/mw/lifecycle/report_running.h>

/// @brief CLI configuration options for the cpp_supervised_app_simple process
struct Config
{
std::uint32_t sleepInMs{50};
};

std::optional<Config> parseOptions(int argc, char* const* argv) noexcept
{
Config config{};
int c;
while ((c = getopt(argc, argv, "s:h")) != -1)
{
switch (static_cast<char>(c))
{
case 's':
config.sleepInMs = std::stoi(optarg);
break;

case 'h':
std::cout << "Usage: \n\
-s <Sleep time in ms between alive notifications>\n";
return std::nullopt;

default:
break;
}
}
return config;
}

std::atomic<bool> exitRequested{false};
void signalHandler(int signal)
{
if (signal == SIGINT || signal == SIGTERM)
{
exitRequested = true;
}
}

void set_process_name()

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 could remove this. This is not required for the functionality. In some other sample app we had this only for some demo purpose.

{
const char* identifier = getenv("PROCESSIDENTIFIER");
if (identifier != nullptr)
{
#if defined(__QNXNTO__)
if (pthread_setname_np(pthread_self(), identifier) != 0)
{
std::cerr << "Failed to set QNX thread name" << std::endl;
}
#elif defined(__linux__)
if (prctl(PR_SET_NAME, identifier) < 0)
{
std::cerr << "Failed to set process name to " << identifier << std::endl;
}
#endif
}
}

/// @brief Minimal example of the Alive API: report Running once, then report
/// alive notifications periodically until the Launch Manager requests shutdown.
/// Unlike cpp_supervised_app, this example has no deadline/checkpoint
/// supervision -- it only demonstrates the plain alive heartbeat.
int main(int argc, char** argv)
{
set_process_name();

signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);

const auto config = parseOptions(argc, argv);
if (!config)
{
return EXIT_FAILURE;
}

std::optional<score::mw::lifecycle::Alive> alive;
try
{
alive.emplace("cpp_supervised_app_simple");
}
catch (const std::exception& e)
{
std::cerr << "Failed to create Alive instance: " << e.what() << std::endl;
return EXIT_FAILURE;
}

score::mw::lifecycle::report_running();

while (!exitRequested)
{
alive->ReportAlive();
std::this_thread::sleep_for(std::chrono::milliseconds(config->sleepInMs));
}

return EXIT_SUCCESS;
}
1 change: 1 addition & 0 deletions examples/demo_verification/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ integration_test(
"//examples/control_application:lmcontrol",
"//examples/cpp_lifecycle_app",
"//examples/cpp_supervised_app",
"//examples/cpp_supervised_app_simple",
"//examples/rust_supervised_app",
"//score/launch_manager",
],
Expand Down
17 changes: 16 additions & 1 deletion examples/demo_verification/lifecycle_demo_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@
"PROCESSIDENTIFIER": "lifecycle_app"
}
}
},
"cpp_supervised_app_simple": {
"component_properties": {
"binary_name": "cpp_supervised_app_simple",
"process_arguments": [
"-s50"
]
},
"deployment_config": {
"environmental_variables": {
"PROCESSIDENTIFIER": "cpp_supervised_app_simple",
"IDENTIFIER": "cpp_supervised_app_simple"
}
}
}
},
"run_targets": {
Expand All @@ -136,7 +150,8 @@
"cpp_supervised_app",
"rust_supervised_app",
"c_supervised_app",
"lifecycle_app"
"lifecycle_app",
"cpp_supervised_app_simple"
],
"recovery_action": {
"switch_run_target": {
Expand Down
39 changes: 31 additions & 8 deletions examples/demo_verification/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,53 @@
"rust_supervised_app",
"cpp_lifecycle_app",
"c_supervised_app",
"cpp_supervised_app_simple",
)


def _cmdline_pattern(binary):
"""Anchored ERE matching an exact argv token equal to `binary`, either
bare or with a path prefix (e.g. "/tmp/tests/examples/cpp_supervised_app").

Anchoring matters once `cpp_supervised_app_simple` is in the mix: a plain
substring search for "cpp_supervised_app" also matches inside
"cpp_supervised_app_simple"'s cmdline, which would silently break both
_assert_running (false positive) and _assert_not_running (false negative
after killing the base app) for the shorter name.
"""
return f"^({binary}|.*/{binary})$"


def _proc_has_binary(target, binary):
"""True if any process's argv (NUL-separated in /proc/*/cmdline, turned
into one-token-per-line here) has an exact token matching `binary`."""
pattern = _cmdline_pattern(binary)
rc, _ = target.execute(
f"cat /proc/*/cmdline 2>/dev/null | tr '\\0' '\\n' | grep -qE '{pattern}'"
)
return rc == 0


def _assert_running(target, *binaries):
"""Assert each binary has a live entry in /proc/*/cmdline."""
for binary in binaries:
pattern = f"[{binary[0]}]{binary[1:]}"
rc, _ = target.execute(f"grep -qa '{pattern}' /proc/*/cmdline 2>/dev/null")
assert rc == 0, f"{binary} is not running"
assert _proc_has_binary(target, binary), f"{binary} is not running"


def _assert_not_running(target, binary):
"""Assert no live /proc/*/cmdline entry exists for binary."""
pattern = f"[{binary[0]}]{binary[1:]}"
rc, _ = target.execute(f"grep -qa '{pattern}' /proc/*/cmdline 2>/dev/null")
assert rc != 0, f"{binary} is still running; expected it to be stopped"
assert not _proc_has_binary(target, binary), (
f"{binary} is still running; expected it to be stopped"
)


def _send_signal(target, binary, signal):
"""Find binary via /proc/*/cmdline and send signal in a single shell invocation."""
pattern = f"[{binary[0]}]{binary[1:]}"
pattern = _cmdline_pattern(binary)
_, pid_output = target.execute(
f"p=; for c in /proc/[0-9]*/cmdline; do "
f"grep -qa '{pattern}' $c 2>/dev/null && p=$(echo $c | cut -d/ -f3) && break; "
f"tr '\\0' '\\n' < \"$c\" 2>/dev/null | grep -qE '{pattern}' "
f"&& p=$(echo $c | cut -d/ -f3) && break; "
f"done; kill -{signal} $p 2>/dev/null; echo $p"
)
assert pid_output.strip(), f"Failed to find {binary} to send signal {signal}"
Expand Down
Loading