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
46 changes: 34 additions & 12 deletions score/launch_manager/src/daemon/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@

#include "score/mw/launch_manager/common/log.hpp"

#include "score/mw/launch_manager/alive_monitor/details/daemon/AliveMonitorImpl.hpp"
#include "score/mw/launch_manager/alive_monitor/details/watchdog/WatchdogImpl.hpp"
#include "score/mw/launch_manager/process_group_manager/alive_monitor_thread.hpp"
#include "score/mw/launch_manager/process_group_manager/process_group_manager.hpp"
#include "score/mw/launch_manager/process_state_client/process_state_notifier.hpp"
#include "score/mw/launch_manager/recovery_client/recovery_client.hpp"
#include "score/mw/launch_manager/alive_monitor/details/daemon/AliveMonitorImpl.hpp"
#include "score/mw/launch_manager/alive_monitor/details/watchdog/WatchdogImpl.hpp"

using namespace std;
using namespace score::lcm::internal;
Expand Down Expand Up @@ -67,35 +67,57 @@ bool runLCMDaemon(ProcessGroupManager& process_group_manager)
}
}

/// @brief Reserves a file descriptor
/// @param fd file descriptor to reserve
/// @brief Reserves a file descriptor.
/// @param fd file descriptor to reserve.
/// @warning This function can abort if system calls fail.
void reserveFD(int fd)
{
if (fcntl(fd, F_GETFD) != -1 || errno != EBADF)
{
errno = 0;
const int open_fd_flags = ::fcntl(fd, F_GETFD);
const bool fd_already_opened = open_fd_flags == 0 && errno == EBADFD;

std::cerr << "file descriptor already in use\n";
if (fd_already_opened)
{
std::cerr << "Failed to reserve required file descriptor (" << fd
<< "), file descriptor already in use. " << std::strerror(errno);
std::abort();
}

int tmp_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (tmp_fd < 0)
{
std::cerr << "unable to get an unused file descriptor\n";
std::cerr << "Failed to reserve required file descriptor (" << fd
<< "), failed to open temporary file. " << std::strerror(errno);
std::abort();
}

if (fd != tmp_fd)
{
if (dup2(tmp_fd, fd) == -1)
if (::dup2(tmp_fd, fd) == -1)
{
::close(tmp_fd);

std::cerr << "Failed to reserve required file descriptor (" << fd
<< "), couldn't duplicate fd with required number. "
<< std::strerror(errno);
std::abort();
}

if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
{
close(tmp_fd);
std::cerr << std::strerror(errno);
::close(tmp_fd);
::close(fd);

std::cerr << "Failed to reserve required file descriptor (" << fd
<< ") , couldn't set flags on reserved file decriptor. "
<< std::strerror(errno);
std::abort();
}
close(tmp_fd);

::close(tmp_fd);
}
}

/// @brief Main function to start the LCM daemon.
/// This function initializes and runs the LCM daemon by creating a ProcessGroupManager,
/// initializing it, and then running it. It returns the appropriate exit code based on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ inline bool ProcessGroupManager::initializeControlClientHandler()
dup2(fd, osal::IpcCommsSync::control_client_handler_nudge_fd); // always make sure we are using fd=4
close(fd);

// dup2 clears the O_CLOEXEC flag so this needs to be set again
if (fcntl(fd2, F_SETFD, FD_CLOEXEC) != 0)
Comment thread
MaciejKaszynski marked this conversation as resolved.
{
::close(fd2);
return false;
}


if (osal::IpcCommsSync::control_client_handler_nudge_fd == fd2)
{
void* buf = mmap(NULL, sizeof(osal::Semaphore), PROT_WRITE, MAP_SHARED, fd2, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ TEST(NonReporting, Process)
{
using ipc = score::lcm::internal::osal::IpcCommsSync;

// Map the sync_fd to some shared memory of size 0. With sync_fd=3, this is likely to happen by default
// Map the sync_fd to some shared memory of size 0. With sync_fd=111, this is likely to happen by default
// As there is no comms channel, using sync_fd in this process should not cause a crash.
auto fd = shm_open("some_shared_memory", O_CREAT | O_RDWR, 0);
ASSERT_NE(fd, -1) << "Failed to open shared memory: " << strerror(errno);
shm_unlink("some_shared_memory");
struct stat res;
if (fstat(ipc::sync_fd, &res) != 0)
{ // sync_fd is closed
auto dup_res = dup2(ipc::sync_fd, fd);
auto dup_res = dup2(fd, ipc::sync_fd);
ASSERT_NE(dup_res, -1) << "dup2 failed: " << strerror(errno);
}

Expand Down
97 changes: 97 additions & 0 deletions tests/integration/process_fd_leak/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# *******************************************************************************
# 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_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_files")
load("@rules_pkg//pkg:tar.bzl", "pkg_tar")
load("//:defs.bzl", "launch_manager_config")
load("//tests/utils/bazel:integration.bzl", "integration_test")

launch_manager_config(
name = "lm_process_fd_leak_config",
config = "//tests/integration/process_fd_leak:process_fd_leak.json",
flatbuffer_out_dir = "etc",
)

cc_binary(
name = "native",
srcs = [
"get_fds.hpp",
"native.cpp",
],
deps = [
"//tests/utils/test_helper",
"@googletest//:gtest_main",
"@score_baselibs//score/mw/log",
],
)

cc_binary(
name = "reporting",
srcs = [
"get_fds.hpp",
"reporting.cpp",
],
deps = [
"//score/launch_manager:lifecycle_cc",
"//tests/utils/test_helper",
"@googletest//:gtest_main",
],
)

cc_binary(
name = "control_client",
srcs = [
"control_client.cpp",
"get_fds.hpp",
],
deps = [
"//score/launch_manager:control_cc",
"//score/launch_manager:lifecycle_cc",
"//tests/utils/test_helper",
"@googletest//:gtest_main",
"@score_baselibs//score/mw/log",
],
)

pkg_files(
name = "main_files",
srcs = [
":control_client",
":native",
":reporting",
"//score/launch_manager",
],
attributes = pkg_attributes(mode = "0777"),
prefix = "tests/process_fd_leak",
)

pkg_files(
name = "configs",
srcs = [":lm_process_fd_leak_config"],
prefix = "tests/process_fd_leak",
)

pkg_tar(
name = "process_fd_leak_binaries",
srcs = [
":configs",
":main_files",
],
)

integration_test(
name = "process_fd_leak",
srcs = ["process_fd_leak.py"],
tags = ["integration"],
test_binaries = ":process_fd_leak_binaries",
deps = ["//tests/utils/testing_utils"],
)
71 changes: 71 additions & 0 deletions tests/integration/process_fd_leak/control_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/********************************************************************************
* 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 <gtest/gtest.h>

#include <sstream>

#include "get_fds.hpp"
#include "tests/utils/test_helper/test_helper.hpp"
#include <score/mw/lifecycle/control_client.h>
#include <score/mw/lifecycle/report_running.h>

int g_argc;
char** g_argv;

TEST(ControlClientFDs, FindOpenFDs)
{

TEST_STEP("Before Running")
{
auto open_fds = get_fds();
EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/ipc_shared_mem[0-9]+")));
EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/_nudge~._.~me_")));
std::ostringstream oss;
oss << open_fds;
EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str();
}

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

TEST_STEP("After Running")
{
auto open_fds = get_fds();
EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/ipc_shared_mem[0-9]+")));
Comment thread
MaciejKaszynski marked this conversation as resolved.
EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/_nudge~._.~me_")));
std::ostringstream oss;
oss << open_fds;
EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str();
}

score::mw::lifecycle::ControlClient client{};

TEST_STEP("After Control Client")
{
auto open_fds = get_fds();
EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/ipc_shared_mem[0-9]+")));
EXPECT_TRUE(filter_fd(open_fds, std::regex("/dev/shm/_nudge~._.~me_")));
std::ostringstream oss;
oss << open_fds;
EXPECT_TRUE(open_fds.empty()) << "Found open files!\n" << oss.str();
}
}

int main(int argc, char** argv)
{
g_argc = argc;
g_argv = argv;

TestRunner runner{__FILE__, TerminationBehavior::kContinue, TerminationNotification::kTestEnd};

return runner.RunTests();
}
Loading
Loading