30 #include <sys/prctl.h>
33 #include <boost/regex.hpp>
35 #include <IceUtil/UUID.h>
37 #include <SimoxUtility/algorithm/string/string_tools.h>
53 defineRequiredProperty<std::string>(
55 "Either the name of the application if its directory is part of the PATH environment "
56 "variable of the system or it is located in an ArmarX-package, or the relative or "
57 "absolute path to the application.")
58 .setCaseInsensitive(
false);
59 defineOptionalProperty<std::string>(
"ApplicationArguments",
61 "Comma separated list of command line parameters. "
62 "Commas in double-quoted strings are ignored.");
63 defineOptionalProperty<std::string>(
66 "Set this property if the application is located in an ArmarX-package.");
67 defineOptionalProperty<std::string>(
70 "If set, this path is used as working directory for the external app. This overrides "
71 "BinaryPathAsWorkingDirectory. ${HOME} for env vars, $C{RobotAPI:BINARY_DIR} for "
72 "CMakePackageFinder vars");
74 defineOptionalProperty<bool>(
75 "BinaryPathAsWorkingDirectory",
77 "If true the path of the binary is set as the working directory.");
78 defineOptionalProperty<bool>(
79 "RestartInCaseOfCrash",
81 "Whether the application should be restarted in case it crashed.");
83 defineOptionalProperty<bool>(
84 "DisconnectInCaseOfCrash",
86 "Whether this component should disconnect as long as the application is not running.");
87 defineOptionalProperty<int>(
88 "FakeObjectStartDelay",
90 "Delay in ms after which the fake armarx object is started on which other apps can "
91 "depend. Not used if property FakeObjectDelayedStartKeyword is used.");
92 defineOptionalProperty<std::string>(
93 "FakeObjectDelayedStartKeyword",
95 "If not empty, the start up of the fake armarx object will be delayed until this "
96 "keyword is found in the stdout of the subprocess.");
97 defineOptionalProperty<int>(
100 "Delay ins ms before the subprocess is killed after sending the stop signal");
101 defineOptionalProperty<bool>(
104 "If true, PYTHONUNBUFFERED=1 is added to the environment variables.");
105 defineOptionalProperty<bool>(
"RedirectToArmarXLog",
107 "If true, all outputs from the subprocess are printed with "
108 "ARMARX_LOG, otherwise with std::cout");
109 defineOptionalProperty<Ice::StringSeq>(
112 "Comma-seperated list of env-var assignment, e.g. MYVAR=1,ADDPATH=/tmp");
113 defineOptionalProperty<Ice::StringSeq>(
116 "Comma-seperated list of Ice Object dependencies. The external app will only be "
117 "started after all dependencies have been found.");
123 const std::string argsStr = getProperty<std::string>(
"ApplicationArguments").getValue();
125 const boost::regex re(
",(?=(?:[^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*$)");
127 boost::match_results<std::string::const_iterator> what;
128 boost::match_flag_type flags = boost::match_default;
129 std::string::const_iterator
s = argsStr.begin();
130 std::string::const_iterator e = argsStr.end();
132 while (boost::regex_search(
s, e, what, re, flags))
134 int pos = what.position();
135 auto arg = argsStr.substr(begin, pos);
140 std::string::difference_type l = what.length();
141 std::string::difference_type p = what.position();
145 std::string lastArg = argsStr.substr(begin);
146 if (!lastArg.empty())
148 args.push_back(lastArg);
155 getProperty(redirectToArmarXLog,
"RedirectToArmarXLog");
156 getProperty(restartWhenCrash,
"RestartInCaseOfCrash");
157 getProperty(disconnectWhenCrash,
"DisconnectInCaseOfCrash");
159 updateLogSenderComponentName();
164 std::filesystem::path appPath(application);
165 ARMARX_INFO <<
"Application absolute path: " << appPath;
168 args.insert(args.begin(), application);
171 if (getProperty<bool>(
"PythonUnbuffered"))
173 envVars.push_back(
"PYTHONUNBUFFERED=1");
175 if (getProperty<Ice::StringSeq>(
"AdditionalEnvVars").isSet())
178 getProperty<Ice::StringSeq>(
"AdditionalEnvVars").getValueAndReplaceAllVars();
179 envVars.insert(envVars.end(), addEnvVrs.begin(), addEnvVrs.end());
182 for (
auto& var : envVars)
185 if (
split.size() >= 2)
187 setenv(
split.at(0).c_str(),
split.at(1).c_str(), 1);
191 getProperty(startUpKeyword,
"FakeObjectDelayedStartKeyword");
192 starterUUID =
"ExternalApplicationManagerStarter" + IceUtil::generateUUID();
193 depObjUUID =
"ExternalApplicationManagerDependency" + IceUtil::generateUUID();
226 ExternalApplicationManager::updateLogSenderComponentName()
234 else if (getProperty<std::string>(
"ObjectName").isSet())
247 std::string workingDir;
250 workingDir = getProperty<bool>(
"BinaryPathAsWorkingDirectory")
251 ? std::filesystem::path(application).parent_path().string()
252 : std::filesystem::current_path().string();
254 catch (std::exception& e)
256 ARMARX_ERROR <<
"caught exception in line " << __LINE__ <<
" what:\n" << e.what();
260 if (getProperty<std::string>(
"WorkingDirectory").isSet())
262 workingDir = getProperty<std::string>(
"WorkingDirectory").getValueAndReplaceAllVars();
265 std::filesystem::current_path(workingDir);
267 catch (std::exception& e)
270 <<
" when setting the current working directory to '" << workingDir
271 <<
"'. except.what:\n"
275 ARMARX_INFO <<
"Using '" << workingDir <<
"' as working directory";
284 std::filesystem::path applicationPath =
285 getProperty<std::string>(
"ApplicationPath").getValueAndReplaceAllVars();
287 if (applicationPath.is_absolute())
289 if (!std::filesystem::exists(applicationPath))
291 throw LocalException() << applicationPath <<
" does not exist.";
293 return applicationPath;
296 if (getProperty<std::string>(
"ArmarXPackage").isSet())
301 std::string path = finder.
getBinaryDir() / applicationPath;
302 if (!std::filesystem::exists(path))
304 throw LocalException()
305 << applicationPath <<
" does not exist in ArmarX-package "
312 throw LocalException() << finder.
getName() <<
" is not an ArmarX-package";
317 std::string applicationPathStr = applicationPath.string();
321 applicationPath = applicationPathStr;
323 if (!std::filesystem::exists(applicationPath))
328 throw LocalException()
329 << applicationPath <<
" does not represent an executable.";
338 return applicationPath;
369 ExternalApplicationManager::waitForApplication()
378 if (restartWhenCrash && !appStoppedOnPurpose)
385 isAppRunning =
false;
386 if (this->disconnectWhenCrash)
402 depObj->setParent(
this);
405 ARMARX_INFO <<
"Adding dummy app with name " << depObjUUID;
412 starter->setParent(
this);
413 starter->setDependencies(getProperty<Ice::StringSeq>(
"Dependencies").getValue());
419 ExternalApplicationManager::setupStream(StreamMetaData& meta)
423 meta.read = [&,
this](
const boost::system::error_code& error, std::size_t length)
427 std::ostringstream ss;
428 ss << &meta.input_buffer;
429 std::string
s = ss.str();
431 std::filesystem::path appPath(application);
432 if (redirectToArmarXLog)
442 if (!startUpKeyword.empty() && !depObj)
449 boost::asio::async_read_until(meta.pend, meta.input_buffer,
"\n", meta.read);
451 else if (error == boost::asio::error::not_found)
453 std::cout <<
"Did not receive ending character!" << std::endl;
456 boost::asio::async_read_until(meta.pend, meta.input_buffer,
"\n", meta.read);
457 auto run = [&]() { meta.io_service.run(); };
459 std::thread{run}.detach();
463 ExternalApplicationManager::startApplication()
465 appStoppedOnPurpose =
false;
467 Ice::StringSeq managedObjects = this->
getArmarXManager()->getManagedObjectNames();
468 bool dummyAlreadyAdded =
false;
469 for (std::string
s : managedObjects)
473 dummyAlreadyAdded =
true;
477 outMetaData.reset(
new StreamMetaData());
478 errMetaData.reset(
new StreamMetaData());
481 outMetaData->sink = boost::iostreams::file_descriptor_sink(
482 outMetaData->pipe.sink, boost::iostreams::close_handle);
483 errMetaData->sink = boost::iostreams::file_descriptor_sink(
484 errMetaData->pipe.sink, boost::iostreams::close_handle);
488 !getProperty<std::string>(
"ApplicationArguments").
getValue().
empty())
491 << getProperty<std::string>(
"ApplicationArguments").getValue();
493 ARMARX_INFO <<
"Commandline: " << simox::alg::join(args,
" ");
497 boost::process::initializers::bind_stdout(outMetaData->sink),
498 boost::process::initializers::bind_stderr(errMetaData->sink),
499 boost::process::initializers::inherit_env(),
502 [](boost::process::executor&) { ::prctl(PR_SET_PDEATHSIG, SIGKILL); }))));
507 setupStream(*outMetaData);
508 setupStream(*errMetaData);
513 waitTask =
new RunningTask<ExternalApplicationManager>(
514 this, &ExternalApplicationManager::waitForApplication);
517 usleep(1000 * getProperty<int>(
"FakeObjectStartDelay").
getValue());
518 if (!dummyAlreadyAdded && startUpKeyword.empty())
527 ExternalApplicationManager::stopApplication()
532 int processId = (*childProcess).pid;
533 ARMARX_INFO <<
"Stopping application with PID " << processId <<
" with SIGINT";
534 appStoppedOnPurpose =
true;
542 ::kill(processId, SIGINT);
545 if (!waitForProcessToFinish(processId, getProperty<int>(
"KillDelay").
getValue()))
552 isAppRunning =
false;
557 ExternalApplicationManager::cleanUp()
563 errMetaData->io_service.stop();
564 outMetaData->io_service.stop();
569 ExternalApplicationManager::waitForProcessToFinish(
int pid,
int timeoutMS)
579 ::kill(pid, 0) != -1)
584 if (::kill(pid, 0) == -1)
594 ExternalApplicationManager::StreamMetaData::StreamMetaData() :
599 ExternalApplicationManager::StreamMetaData::StreamMetaData(
600 const ExternalApplicationManager::StreamMetaData&
data) :