52#include <SimoxUtility/algorithm/string/string_tools.h>
78 constexpr std::chrono::seconds ProcessScanInterval{2};
80 std::mutex ProcessScanMutex;
81 std::vector<ProcessUtil::ProcessInfo> ProcessScanCache;
82 std::chrono::steady_clock::time_point ProcessScanTime;
83 bool ProcessScanned =
false;
89 std::vector<ProcessUtil::ProcessInfo>
90 FindProcessesNamed(
const std::string& executableName)
92 std::vector<ProcessUtil::ProcessInfo>
matches;
93 if (executableName.empty())
98 std::lock_guard<std::mutex> guard(ProcessScanMutex);
100 const auto now = std::chrono::steady_clock::now();
101 if (!ProcessScanned || now - ProcessScanTime >= ProcessScanInterval)
104 ProcessScanTime = now;
105 ProcessScanned =
true;
112 if (process.name == executableName)
123 EnsureChildrenAreReaped();
127LinuxStarter::EnsureChildrenAreReaped()
129#if defined(BOOST_POSIX_API)
152 signal(SIGCHLD, SIG_IGN);
162 if (!app->getEnabled())
167 std::filesystem::path componentFolderPath =
170 if (!std::filesystem::exists(componentFolderPath))
172 if (!std::filesystem::create_directories(componentFolderPath))
174 std::cout <<
"Could not create Cache folder for ScenarioManagerPlugin at "
175 << componentFolderPath.string() << std::endl;
180 std::filesystem::path dependenciesFilePath =
181 componentFolderPath /
182 std::filesystem::path(
"./" + app->getScenario()->getPackage()->getName() +
183 ".dependencies.cfg");
185 if (!std::filesystem::exists(dependenciesFilePath))
192 if (!std::filesystem::exists(
193 std::filesystem::path(app->getScenario()->getGlobalConfigPath())) ||
194 !std::filesystem::exists(std::filesystem::path(app->getConfigPath())) ||
195 !std::filesystem::exists(dependenciesFilePath))
197 std::cout <<
"Launching " << app->getName() <<
" in "
198 << app->getScenario()->getPackage()->getName() <<
" without an needed cfg file";
200 if (!std::filesystem::exists(app->getExecutableAbsPath()))
202 std::cout <<
"\033[1;31m"
203 <<
"Warning: Could not launch " << app->getName() <<
" in "
204 << app->getScenario()->getPackage()->getName()
205 <<
" because the executable is missing at " << app->getPathToExecutable()
206 <<
"\033[0m" << std::endl;
209 args += app->getExecutableAbsPath();
210 args += std::string(
" --Ice.Config=")
211 .append(app->getScenario()->getGlobalConfigPath())
213 .append(app->getConfigPath());
214 auto configDomain = app->getConfigDomain();
216 args += std::string(
" --" + configDomain +
".DependenciesConfig=")
217 .append(dependenciesFilePath.string());
219 std::string(
" --" + configDomain +
".LoggingGroup=").append(app->getScenario()->getName());
225 bool inString =
false;
226 std::string inStringChar;
227 std::string toPush =
"";
228 for (
auto command : additional)
230 if (!inString && command.find(
"\"") != std::string::npos)
235 else if (!inString && command.find(
"\'") != std::string::npos)
240 else if (inString && command.find(inStringChar) != std::string::npos)
244 args +=
" " + toPush;
251 toPush += command +
" ";
257 args +=
" " + command;
264 Ice::StringSeq additional =
Split(this->commandLineParameters,
" ");
265 for (
auto arg : additional)
272 using namespace boost::process::initializers;
276 for (
auto arg : args)
280 std::cout <<
" &" << std::endl << std::endl;
288 ARMARX_INFO <<
"Executing command `" << app->getExecutableAbsPath() <<
" " << args;
292 EnsureChildrenAreReaped();
294 child
c = execute(run_exe(app->getExecutableAbsPath()), set_cmd_line(args), inherit_env());
305 int pid = application->getPid();
310 const int cachedPid = statusManager.
loadPid(application);
317 return adoptRunningProcess(application, statusManager);
323 if (processName.empty())
333 if (application->compareAndResetPid(-1))
335 statusManager.
savePid(application);
337 return adoptRunningProcess(application, statusManager);
346 if (processName != application->getExecutableName())
349 ARMARX_INFO_S <<
"The Process name with Pid (" << cachedPid <<
":" << processName
350 <<
") does not correspond with app name (" << application->getName()
351 <<
"). Resetting Saved Pid.";
352 if (application->compareAndResetPid(-1))
354 statusManager.
savePid(application);
356 return adoptRunningProcess(application, statusManager);
359 if (!application->adoptPidIfUnset(cachedPid))
363 pid = application->getPid();
372 return adoptRunningProcess(application, statusManager);
385 pid, application->getExecutableName(), application->getConfigPath());
389 ARMARX_INFO_S <<
"The process with pid " << pid <<
" is no longer application "
390 << application->getName()
391 <<
" - the pid has been recycled by an unrelated process. Dropping it and "
392 "looking for the application again.";
393 if (application->compareAndResetPid(pid))
395 statusManager.
savePid(application);
397 return adoptRunningProcess(application, statusManager);
400 switch (probeProcessState(pid))
402 case ProcessState::Alive:
405 case ProcessState::Indeterminate:
416 if (
const std::optional<std::string>
status =
417 rediscover(application, statusManager, pid))
424 case ProcessState::Dead:
431 if (application->compareAndResetPid(pid))
433 statusManager.
savePid(application);
443 return adoptRunningProcess(application, statusManager);
450 bool ambiguous =
false;
451 const int pid = findRunningProcess(application, ambiguous);
462 if (!application->adoptPidIfUnset(pid))
468 ARMARX_INFO_S <<
"Found a running process (pid " << pid <<
") for application "
469 << application->getName()
470 <<
" that this ScenarioManager did not start (or whose pid it lost). Adopting "
471 "it, so that it is shown as running and can be stopped from here.";
472 statusManager.
savePid(application);
476 return probeProcessState(pid) == ProcessState::Alive
481std::optional<std::string>
483 StatusManager& statusManager,
486 bool ambiguous =
false;
487 const int found = findRunningProcess(application, ambiguous);
489 if (found < 0 || found == stalePid)
499 if (!application->compareAndResetPid(stalePid) || !application->adoptPidIfUnset(found))
504 ARMARX_INFO_S <<
"Application " << application->getName() <<
" is running as pid " << found
505 <<
", not as pid " << stalePid
506 <<
" which could no longer be identified. Following it there.";
507 statusManager.
savePid(application);
509 return probeProcessState(found) == ProcessState::Alive
519 const std::vector<ProcessUtil::ProcessInfo> candidates =
520 FindProcessesNamed(application->getExecutableName());
521 if (candidates.empty())
526 const std::string ownConfigPath = application->getConfigPath();
532 std::vector<int> certain;
533 std::vector<int> possible;
535 for (
const ProcessUtil::ProcessInfo& candidate : candidates)
545 const std::optional<std::string> configArgument =
548 if (!configArgument.has_value())
550 possible.push_back(candidate.pid);
552 else if (!ownConfigPath.empty() &&
Contains(*configArgument, ownConfigPath))
554 certain.push_back(candidate.pid);
559 if (certain.size() == 1)
561 return certain.front();
563 if (!certain.empty())
569 if (possible.size() == 1)
571 return possible.front();
574 ambiguous = !possible.empty();
578LinuxStarter::ProcessState
579LinuxStarter::probeProcessState(
int pid)
583 return ProcessState::Dead;
590 const auto stateIfUnreadable = [pid]
593 const std::string processFilePath =
"/proc/" + std::to_string(pid) +
"/status";
594 std::ifstream t(processFilePath);
598 return stateIfUnreadable();
601 std::string stateLine;
603 while (std::getline(t, line))
611 if (stateLine.empty())
613 return stateIfUnreadable();
616 std::string result = stateLine.substr(stateLine.find(
':') + 1);
618 simox::alg::trim(result);
622 return stateIfUnreadable();
625 switch (result.front())
629 return ProcessState::Alive;
637 return ProcessState::Dead;
642 return ProcessState::Indeterminate;
645 ARMARX_DEBUG_S <<
"Unhandled process state '" << result.front() <<
"' for pid " << pid;
646 return ProcessState::Indeterminate;
std::shared_ptr< ScenarioManager::Data_Structure::ApplicationInstance > ApplicationInstancePtr
std::string commandLineParameters
virtual std::string getStatus(Data_Structure::ApplicationInstancePtr application, StatusManager statusManager) override
Returns the status of an application.
virtual void startApplication(Data_Structure::ApplicationInstancePtr application, StatusManager statusManager, const std::string &commandLineParameters="", bool printOnly=false) override
Starts an application.
void generateDependenciesCfg(ScenarioManager::Data_Structure::PackagePtr package)
void savePid(Data_Structure::ApplicationInstancePtr app)
int loadPid(Data_Structure::ApplicationInstancePtr app)
static std::string GetCachePath()
The base Cache directory of ArmarX.
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_DEBUG_S
The logging level for output that is only interesting while debugging.
#define ARMARX_INFO
The normal logging level.
std::shared_ptr< Package > PackagePtr
Identity
Whether the process behind a pid still is the one we think it is.
@ Matches
argv says this is the expected executable, and it does not claim a foreign config.
@ Differs
argv says this is a different program, or the same program serving a different config.
std::string GetProcessName(int pid)
Reads the name of the executable behind pid (the basename of argv[0]).
Identity IdentifyProcess(int pid, const std::string &executableName, const std::string &configPath)
Checks whether the process with the given pid is still the application it is supposed to be.
std::optional< std::string > FindIceConfigArgument(const std::vector< std::string > &arguments)
std::vector< ProcessInfo > ScanProcesses()
Walks /proc and returns every process whose argument vector could be read.
bool matches(std::string skillName, std::vector< std::string > &searches)
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::vector< std::string > Split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)
Convenience header which includes all public Boost.Process header files.
static const std::string Stopped
static const std::string Unknown
static const std::string Running
A live process as seen by ScanProcesses().