From 95de1656c2365663961ade9600c76a65c467fa96 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Thu, 23 Oct 2025 12:54:30 +0200 Subject: [PATCH 1/5] Add demo for Stop/Forced Shutdown in any state --- Demos/api/Orchestration/CMakeLists.txt | 1 + Demos/api/Orchestration/Shutdown.cpp | 196 +++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 Demos/api/Orchestration/Shutdown.cpp diff --git a/Demos/api/Orchestration/CMakeLists.txt b/Demos/api/Orchestration/CMakeLists.txt index 3250bff88..8679858b8 100644 --- a/Demos/api/Orchestration/CMakeLists.txt +++ b/Demos/api/Orchestration/CMakeLists.txt @@ -6,4 +6,5 @@ make_silkit_demo(SilKitDemoAutonomous Autonomous.cpp OFF) make_silkit_demo(SilKitDemoCoordinated Coordinated.cpp OFF) make_silkit_demo(SilKitDemoSimStep SimStep.cpp OFF) make_silkit_demo(SilKitDemoSimStepAsync SimStepAsync.cpp OFF) +make_silkit_demo(SilKitDemoShutdown Shutdown.cpp OFF) diff --git a/Demos/api/Orchestration/Shutdown.cpp b/Demos/api/Orchestration/Shutdown.cpp new file mode 100644 index 000000000..40aa1b5bd --- /dev/null +++ b/Demos/api/Orchestration/Shutdown.cpp @@ -0,0 +1,196 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// + +#include +#include +#include +#include +#include +#include +#include + +#include "silkit/SilKit.hpp" +#include "silkit/services/orchestration/string_utils.hpp" +#include "silkit/experimental/participant/ParticipantExtensions.hpp" + +using namespace std::chrono_literals; +using namespace SilKit::Services::Orchestration; + +std::unique_ptr timeoutThread; +std::atomic_bool participantDeleted{false}; +std::atomic_bool timerActive{false}; +std::atomic_bool startWorkPromiseSet{false}; +std::unique_ptr participant; + +void OnTimerTimeout() +{ + std::cout << "Timeout reached, destroying participant (nongraceful)." << std::endl; + timerActive = false; + if (participant) + { + participantDeleted = true; + participant.reset(); + } +} + +void StartTimeoutTimer(std::chrono::seconds timeout) +{ + timerActive = true; + timeoutThread = std::make_unique([timeout]() { + for (auto remaining = timeout.count(); remaining > 0; --remaining) + { + if (!timerActive) + return; + std::cout << "Nongraceful stop in " << remaining << " seconds..." << std::endl; + std::this_thread::sleep_for(1s); + } + if (timerActive) + { + OnTimerTimeout(); + } + }); +} + +std::atomic_bool stopRequested{false}; + +// Try to stop if running, otherwise request a stop and start a timer +void TryStop(ILifecycleService* lifecycleService) +{ + if (stopRequested) + { + return; + } + + stopRequested = true; + + auto state = lifecycleService->State(); + + if (state == ParticipantState::Running || state == ParticipantState::Paused) + { + lifecycleService->Stop("Stop while running/paused."); // graceful + } + else + { + StartTimeoutTimer(std::chrono::seconds{5}); + } +} + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; + return -1; + } + std::string participantName(argv[1]); + + try + { + // Setup participant, lifecycle, time synchronization and logging. + const std::string registryUri = "silkit://localhost:8500"; + const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; + auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); + + participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); + auto logger = participant->GetLogger(); + + // Create a coordinated lifecycle + auto* lifecycleService = + participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Coordinated}); + + // Future / promise to control entrance of the main loop in the worker thread + std::promise startWorkPromise; + std::future startWorkFuture; + startWorkFuture = startWorkPromise.get_future(); + + + // Wait for the "Running" state on this participant and handle the requested stop + auto* systemMonitor = participant->CreateSystemMonitor(); + systemMonitor->AddParticipantStatusHandler( + [lifecycleService, participantName, logger](const ParticipantStatus& status) { + + if (participantName != status.participantName) + return; + + std::stringstream ss; + ss << "Participant state: " << status.state; + logger->Info(ss.str()); + + if (status.state == ParticipantState::ServicesCreated) + { + logger->Info("TryStop in ServicesCreated"); + //TryStop(lifecycleService); + } + + if (stopRequested && (status.state == ParticipantState::Running || status.state == ParticipantState::Paused)) + { + lifecycleService->Stop("Requested stop."); // graceful + } + }); + + // The worker thread is 'unleashed' in the starting handler... + lifecycleService->SetStartingHandler([&startWorkPromise]() { + startWorkPromiseSet = true; + startWorkPromise.set_value(); + }); + + // Start the worker thread and wait for the go from the starting handler. + std::atomic workerThreadDone{false}; + auto workerThread = + std::thread([&startWorkFuture, &workerThreadDone, logger]() { + + startWorkFuture.get(); + while (!workerThreadDone) + { + std::this_thread::sleep_for(1s); + if (!participantDeleted) + logger->Info("Simulation running."); + }; + }); + + // Start and wait until the sil-kit-system-controller is stopped. + logger->Info( + "Start the participant lifecycle and wait for the sil-kit-system-controller to start the simulation."); + auto finalStateFuture = lifecycleService->StartLifecycle(); + + try + { + finalStateFuture.get(); + } + catch (const std::exception& /*e*/) + { + std::cout << "Participant already destroyed" << std::endl; + } + + if (participant) + { + participantDeleted = true; + participant.reset(); + } + + // Clean up the worker/timeout thread. + workerThreadDone = true; + if (!startWorkPromiseSet) + { + startWorkPromise.set_value(); + } + + if (workerThread.joinable()) + { + workerThread.join(); + } + + if (timeoutThread && timeoutThread.get()->joinable()) + { + timeoutThread.get()->join(); + } + + } + catch (const std::exception& error) + { + std::cerr << "Something went wrong: " << error.what() << std::endl; + return -2; + } + + return 0; +} From 0e123d81313fde2c46ee9f712d5b1620f8883be2 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Thu, 23 Oct 2025 12:55:33 +0200 Subject: [PATCH 2/5] Try to stop in state ServicesCreated --- Demos/api/Orchestration/Shutdown.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Demos/api/Orchestration/Shutdown.cpp b/Demos/api/Orchestration/Shutdown.cpp index 40aa1b5bd..ff5155856 100644 --- a/Demos/api/Orchestration/Shutdown.cpp +++ b/Demos/api/Orchestration/Shutdown.cpp @@ -119,7 +119,7 @@ int main(int argc, char** argv) if (status.state == ParticipantState::ServicesCreated) { logger->Info("TryStop in ServicesCreated"); - //TryStop(lifecycleService); + TryStop(lifecycleService); } if (stopRequested && (status.state == ParticipantState::Running || status.state == ParticipantState::Paused)) From b6e954ed6d893c318885ecfaa3699ba7f0bf4e6c Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Thu, 23 Oct 2025 13:10:13 +0200 Subject: [PATCH 3/5] Remove workerThread --- Demos/api/Orchestration/Shutdown.cpp | 62 ++++------------------------ 1 file changed, 9 insertions(+), 53 deletions(-) diff --git a/Demos/api/Orchestration/Shutdown.cpp b/Demos/api/Orchestration/Shutdown.cpp index ff5155856..22b4ecd38 100644 --- a/Demos/api/Orchestration/Shutdown.cpp +++ b/Demos/api/Orchestration/Shutdown.cpp @@ -17,9 +17,9 @@ using namespace std::chrono_literals; using namespace SilKit::Services::Orchestration; std::unique_ptr timeoutThread; -std::atomic_bool participantDeleted{false}; std::atomic_bool timerActive{false}; -std::atomic_bool startWorkPromiseSet{false}; +std::atomic_bool stopRequested{false}; +std::atomic_bool participantIsOperational{true}; std::unique_ptr participant; void OnTimerTimeout() @@ -28,12 +28,12 @@ void OnTimerTimeout() timerActive = false; if (participant) { - participantDeleted = true; + participantIsOperational = false; participant.reset(); } } -void StartTimeoutTimer(std::chrono::seconds timeout) +void StartForceStopTimer(std::chrono::seconds timeout) { timerActive = true; timeoutThread = std::make_unique([timeout]() { @@ -51,9 +51,7 @@ void StartTimeoutTimer(std::chrono::seconds timeout) }); } -std::atomic_bool stopRequested{false}; -// Try to stop if running, otherwise request a stop and start a timer void TryStop(ILifecycleService* lifecycleService) { if (stopRequested) @@ -71,7 +69,7 @@ void TryStop(ILifecycleService* lifecycleService) } else { - StartTimeoutTimer(std::chrono::seconds{5}); + StartForceStopTimer(std::chrono::seconds{5}); } } @@ -98,12 +96,6 @@ int main(int argc, char** argv) auto* lifecycleService = participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Coordinated}); - // Future / promise to control entrance of the main loop in the worker thread - std::promise startWorkPromise; - std::future startWorkFuture; - startWorkFuture = startWorkPromise.get_future(); - - // Wait for the "Running" state on this participant and handle the requested stop auto* systemMonitor = participant->CreateSystemMonitor(); systemMonitor->AddParticipantStatusHandler( @@ -128,31 +120,12 @@ int main(int argc, char** argv) } }); - // The worker thread is 'unleashed' in the starting handler... - lifecycleService->SetStartingHandler([&startWorkPromise]() { - startWorkPromiseSet = true; - startWorkPromise.set_value(); + lifecycleService->SetShutdownHandler([]() { + // Cancel ForceStopTimer timer if shutdown happened already + timerActive = false; }); - // Start the worker thread and wait for the go from the starting handler. - std::atomic workerThreadDone{false}; - auto workerThread = - std::thread([&startWorkFuture, &workerThreadDone, logger]() { - - startWorkFuture.get(); - while (!workerThreadDone) - { - std::this_thread::sleep_for(1s); - if (!participantDeleted) - logger->Info("Simulation running."); - }; - }); - - // Start and wait until the sil-kit-system-controller is stopped. - logger->Info( - "Start the participant lifecycle and wait for the sil-kit-system-controller to start the simulation."); auto finalStateFuture = lifecycleService->StartLifecycle(); - try { finalStateFuture.get(); @@ -162,24 +135,7 @@ int main(int argc, char** argv) std::cout << "Participant already destroyed" << std::endl; } - if (participant) - { - participantDeleted = true; - participant.reset(); - } - - // Clean up the worker/timeout thread. - workerThreadDone = true; - if (!startWorkPromiseSet) - { - startWorkPromise.set_value(); - } - - if (workerThread.joinable()) - { - workerThread.join(); - } - + // Clean up the timeout thread. if (timeoutThread && timeoutThread.get()->joinable()) { timeoutThread.get()->join(); From 191238d40a0ac5b1b17238f40fa23d737e979725 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Thu, 23 Oct 2025 14:25:07 +0200 Subject: [PATCH 4/5] Fix joining timeout thread --- Demos/api/Orchestration/Shutdown.cpp | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/Demos/api/Orchestration/Shutdown.cpp b/Demos/api/Orchestration/Shutdown.cpp index 22b4ecd38..1120b3ba2 100644 --- a/Demos/api/Orchestration/Shutdown.cpp +++ b/Demos/api/Orchestration/Shutdown.cpp @@ -75,6 +75,8 @@ void TryStop(ILifecycleService* lifecycleService) int main(int argc, char** argv) { + int rc = 0; + if (argc != 2) { std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; @@ -126,27 +128,19 @@ int main(int argc, char** argv) }); auto finalStateFuture = lifecycleService->StartLifecycle(); - try - { - finalStateFuture.get(); - } - catch (const std::exception& /*e*/) - { - std::cout << "Participant already destroyed" << std::endl; - } - - // Clean up the timeout thread. - if (timeoutThread && timeoutThread.get()->joinable()) - { - timeoutThread.get()->join(); - } - + finalStateFuture.get(); } catch (const std::exception& error) { std::cerr << "Something went wrong: " << error.what() << std::endl; - return -2; + rc = -2; + } + + // Clean up the timeout thread. + if (timeoutThread && timeoutThread->joinable()) + { + timeoutThread->join(); } - return 0; + return rc; } From 682f04645b1e1e6371ff27f79d152143cc73acdd Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Fri, 24 Oct 2025 09:21:43 +0200 Subject: [PATCH 5/5] Fix license header --- Demos/api/Orchestration/Shutdown.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Demos/api/Orchestration/Shutdown.cpp b/Demos/api/Orchestration/Shutdown.cpp index 1120b3ba2..80bb3051b 100644 --- a/Demos/api/Orchestration/Shutdown.cpp +++ b/Demos/api/Orchestration/Shutdown.cpp @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2024 Vector Informatik GmbH // +// SPDX-License-Identifier: MIT #include #include @@ -86,7 +87,7 @@ int main(int argc, char** argv) try { - // Setup participant, lifecycle, time synchronization and logging. + // Setup participant, lifecycle and logging. const std::string registryUri = "silkit://localhost:8500"; const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); @@ -118,7 +119,7 @@ int main(int argc, char** argv) if (stopRequested && (status.state == ParticipantState::Running || status.state == ParticipantState::Paused)) { - lifecycleService->Stop("Requested stop."); // graceful + lifecycleService->Stop("Requested stop."); // Graceful } }); @@ -129,6 +130,7 @@ int main(int argc, char** argv) auto finalStateFuture = lifecycleService->StartLifecycle(); finalStateFuture.get(); + } catch (const std::exception& error) {