Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ styles/
# direnv - folder-specific bash configuration
.envrc

# Personal devcontainer (user-specific extensions, env vars, etc.)
.devcontainer/local/devcontainer.json

# Python
.venv
__pycache__/
Expand Down
30 changes: 20 additions & 10 deletions externals/ipc_dropin/include/ipc_dropin/ringbuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class RingBuffer {
static_assert(std::is_trivially_destructible<T>::value, "T must be trivially destructible");
static_assert(sizeof(T) <= ElementSize, "RingBuffer payload size mismatch: sizeof(T) must be <= ElementSize template parameter");
lock_guard lock(&mutex_);
if (full()) {
if (fullImpl()) {
overflow_flag_.store(true, std::memory_order_relaxed);
return false;
}
Expand All @@ -69,7 +69,7 @@ class RingBuffer {
static_assert(std::is_trivially_destructible<T>::value, "T must be trivially destructible");
static_assert(sizeof(T) <= ElementSize, "RingBuffer payload size mismatch: sizeof(T) must be <= ElementSize template parameter");
lock_guard lock(&mutex_);
if (full()) {
if (fullImpl()) {
overflow_flag_.store(true, std::memory_order_relaxed);
return false;
}
Expand All @@ -85,12 +85,12 @@ class RingBuffer {
static_assert(std::is_trivially_copyable<T>::value, "RingBuffer only supports trivially copyable types");
static_assert(sizeof(T) <= ElementSize, "RingBuffer payload size mismatch: sizeof(T) must be <= ElementSize template parameter");
lock_guard lock(&mutex_);
if (empty()) {
if (emptyImpl()) {
return false;
}
Node& n = nodes_[read_head_];
if (n.size_ != sizeof(T)) {
return false;
return false;
}
std::memcpy(&out, n.storage, n.size_);
advanceReadHead();
Expand All @@ -99,7 +99,7 @@ class RingBuffer {

bool tryPop() {
lock_guard lock(&mutex_);
if (empty()) {
if (emptyImpl()) {
return false;
}
advanceReadHead();
Expand All @@ -111,12 +111,12 @@ class RingBuffer {
static_assert(std::is_trivially_copyable<T>::value, "RingBuffer only supports trivially copyable types");
static_assert(sizeof(T) <= ElementSize, "RingBuffer payload size mismatch: sizeof(T) must be <= ElementSize template parameter");
lock_guard lock(&mutex_);
if (empty()) {
if (emptyImpl()) {
return false;
}
Node& n = nodes_[read_head_];
if (n.size_ != sizeof(T)) {
return false;
return false;
}
out = reinterpret_cast<T*>(n.storage);
return true;
Expand All @@ -130,9 +130,19 @@ class RingBuffer {
return b;
}

bool empty() const noexcept { return size_ == 0; }
bool full() const noexcept { return size_ == Capacity; }
bool empty() {
lock_guard lock(&mutex_);
return emptyImpl();
}

bool full() {
lock_guard lock(&mutex_);
return fullImpl();
}

private:
bool emptyImpl() const noexcept { return size_ == 0; }
bool fullImpl() const noexcept { return size_ == Capacity; }
void advanceWriteHead() noexcept { write_head_ = (write_head_ + 1) % Capacity; ++size_; }
void advanceReadHead() noexcept { read_head_ = (read_head_ + 1) % Capacity; --size_; }

Expand Down Expand Up @@ -160,4 +170,4 @@ class RingBuffer {

} // namespace ipc_dropin

#endif
#endif
13 changes: 13 additions & 0 deletions score/launch_manager/src/daemon/src/common/identifier_hash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "score/mw/launch_manager/common/identifier_hash.hpp"
#include <functional>
#include <mutex>
#include <unordered_map>

namespace score
Expand Down Expand Up @@ -61,19 +62,22 @@ namespace lcm
IdentifierHash::IdentifierHash(const std::string& id)
{
hash_id_ = std::hash<std::string>{}(id);
const std::lock_guard<std::mutex> lock(get_registry_mutex());
get_registry()[hash_id_] = id;
}

IdentifierHash::IdentifierHash(std::string_view id)
{
hash_id_ = std::hash<std::string_view>{}(id);
const std::lock_guard<std::mutex> lock(get_registry_mutex());
get_registry()[hash_id_] = id;
}

IdentifierHash::IdentifierHash(const char* id)
{
const std::string_view sv = (id != nullptr) ? std::string_view(id) : std::string_view("");
hash_id_ = std::hash<std::string_view>{}(sv);
const std::lock_guard<std::mutex> lock(get_registry_mutex());
get_registry()[hash_id_] = sv;
}

Expand Down Expand Up @@ -105,6 +109,7 @@ bool IdentifierHash::operator<(const IdentifierHash& other) const
IdentifierHash::IdentifierHash()
{
hash_id_ = std::hash<std::string_view>{}(std::string_view(""));
const std::lock_guard<std::mutex> lock(get_registry_mutex());
get_registry()[hash_id_] = "";
}

Expand All @@ -120,6 +125,14 @@ std::unordered_map<std::size_t, std::string>& IdentifierHash::get_registry()
return registry;
}

std::mutex& IdentifierHash::get_registry_mutex()
{
/// Static mutex protecting the registry from concurrent access, since IdentifierHash
/// instances are constructed and their string representation is read from multiple threads.
static std::mutex registry_mutex;
return registry_mutex;
}

} // namespace lcm

} // namespace score
10 changes: 10 additions & 0 deletions score/launch_manager/src/daemon/src/common/identifier_hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define IDENTIFIER_HASH_H_

#include <cstddef>
#include <mutex>
#include <ostream>
#include <sstream>
#include <string>
Expand Down Expand Up @@ -121,9 +122,18 @@ class IdentifierHash final
///
/// Static registry, which gets initialized per process.
///
/// @note The registry is mutated by every IdentifierHash constructor and may be read
/// concurrently (e.g. via operator<<) from multiple threads. Callers accessing the
/// registry directly (e.g. tests) must hold get_registry_mutex() for the duration of
/// the access.
///
/// @return A reference to the static unordered_map that serves as the registry.
static std::unordered_map<std::size_t, std::string>& get_registry();

/// @brief Returns the mutex protecting the static registry from concurrent access.
/// @return A reference to the static mutex guarding get_registry().
static std::mutex& get_registry_mutex();

private:
/// internal representation of the ID, that was passed in constructor
std::size_t hash_id_ = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
********************************************************************************/
#include <gtest/gtest.h>
#include <memory>
#include <mutex>
#include <sstream>

#include "score/mw/launch_manager/common/identifier_hash.hpp"
Expand Down Expand Up @@ -75,7 +76,10 @@ TEST_F(IdentifierHashTest, IdentifierHash_invalid_hash_no_string_representation)
IdentifierHash identifierHash(idStr);

// Clear registry to simulate missing entry
IdentifierHash::get_registry().clear();
{
const std::lock_guard<std::mutex> lock(IdentifierHash::get_registry_mutex());
IdentifierHash::get_registry().clear();
}

stringstream strStream;
strStream << identifierHash;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,16 @@ void ProcessInfoNode::unexpectedTermination()
static_cast<void>(sync->send_sync_.post());
}
}
else if (score::lcm::ProcessState::kTerminating == getState())
{
// prevents a spurious graph_->abort() by recognizing that terminateProcess()
// already owns the retry decision when the state is kTerminating.
// explanation:
// terminateProcess() is already active (kRunning timeout path). The do-while retry loop
// in startProcess() owns the restart/abort decision. Calling graph_->abort() here would
// race with that loop and trigger a spurious abort before retries are exhausted.
// terminated() will post on terminator_, unblocking terminateProcess() — nothing else needed.
}
else if (GraphState::kInTransition == graph_state)
{
// process has started, but graph is still in transition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ static constexpr auto kFailedAddSigterm = "Failed to add SIGTERM to set.";
} // namespace

score::mw::lifecycle::LifeCycleManager::LifeCycleManager(std::unique_ptr<score::os::Signal> signal_interface) noexcept
: m_stop_source{}, m_app{nullptr}, m_signal_set{}, m_signal_handler_thread{}, signal_(std::move(signal_interface))
: m_stop_source{}, m_app{nullptr}, m_signal_set{}, m_signal_handler_thread{}, signal_(std::move(signal_interface)) // NOLINT(cppcoreguidelines-prefer-member-initializer)
{
mw::log::LogInfo() << "Initializing LifeCycleManager";
if (!initialize_internal())
Expand Down Expand Up @@ -68,10 +68,10 @@ void score::mw::lifecycle::LifeCycleManager::at_exit() noexcept

std::int32_t score::mw::lifecycle::LifeCycleManager::run(Application& app, const ApplicationContext& context)
{
m_app = &app;
m_app.store(&app, std::memory_order_release);
mw::log::LogInfo() << "LifeCycleManager started";
/* Branching in below line is due to hidden exception handling */
const auto init_status = m_app->Initialize(context); // LCOV_EXCL_BR_LINE
const auto init_status = m_app.load(std::memory_order_relaxed)->Initialize(context); // LCOV_EXCL_BR_LINE

std::string application_name{"None"};
const auto& args = context.get_arguments();
Expand All @@ -91,7 +91,7 @@ std::int32_t score::mw::lifecycle::LifeCycleManager::run(Application& app, const
mw::log::LogInfo() << "Running Application";
report_running();
/* Branching in below line is due to hidden exception handling */
const auto run_status = m_app->Run(m_stop_source.get_token()); // LCOV_EXCL_BR_LINE
const auto run_status = m_app.load(std::memory_order_relaxed)->Run(m_stop_source.get_token()); // LCOV_EXCL_BR_LINE
if (run_status != 0)
{
mw::log::LogError() << "Error occurred during Run";
Expand Down Expand Up @@ -138,7 +138,7 @@ void score::mw::lifecycle::LifeCycleManager::handle_signal()
std::int32_t sig(0);
mw::log::LogInfo() << "Signal handler started";
const auto sigwait_status = signal_->SigWait(m_signal_set, sig); // LCOV_EXCL_BR_LINE
if ((m_app != nullptr) && (sigwait_status.has_value() == true))
if ((m_app.load(std::memory_order_acquire) != nullptr) && (sigwait_status.has_value() == true))
{
mw::log::LogInfo() << "Signal SIGTERM received, requesting to stop the app";
score::cpp::ignore = m_stop_source.request_stop();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <score/stop_token.hpp>

#include <atomic>
#include <cstdint>
#include <thread>

Expand Down Expand Up @@ -48,7 +49,7 @@ class LifeCycleManager
/**
* \brief Runnable application
*/
Application* m_app;
std::atomic<Application*> m_app;

/**
* \brief signal handling mask.
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/crash_on_startup/crash_on_startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_crash_on_startup(target, setup_test, assert_test_results, remote_test_d
binary_path=str(remote_test_dir / "launch_manager"),
file_path=remote_test_dir.parent / "test_end",
cwd=str(remote_test_dir),
timeout_s=6.0,
timeout_s=10.0,
)

assert_test_results(
Expand Down
13 changes: 11 additions & 2 deletions tests/integration/process_crash_monitoring/control_client_mock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include <fcntl.h>
#include <score/mw/lifecycle/control_client.h>
#include <score/mw/lifecycle/report_running.h>
#include <chrono>
#include <thread>


// Given a correct configuration with:
Expand All @@ -41,8 +43,15 @@ TEST(ProcessCrashMonitoring, ControlClientMock)
EXPECT_TRUE(result.has_value()) << "Activating target run_target_crashing_app_on_runtime failed: "
<< result.error().Message();
}
// When the process crashes
sleep(2);
// When the process crashes, wait for the fallback to be activated.
// Use polling instead of a fixed sleep so the test is robust under slow builds (e.g. TSan).
{
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
while (!std::filesystem::exists(fallback_file) && std::chrono::steady_clock::now() < deadline)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
// Then
TEST_STEP("Verify state changed to fallback run target")
{
Expand Down
Loading