LinuxStarter.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * Copyright (C) 2011-2016, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5 *
6 * ArmarX is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * ArmarX is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * @package ArmarXCore::core
19 * @author Nicola Miskowiec
20 * @date 2016
21 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
22 * GNU General Public License
23 */
24
25
26#include "LinuxStarter.h"
27
28#include <errno.h>
29#include <signal.h>
30#include <stdlib.h>
31
32#include <filesystem>
33#include <future>
34#include <iostream>
35#include <chrono>
36#include <mutex>
37#include <optional>
38#include <sstream>
39#include <string>
40#include <thread>
41#include <vector>
42
43#include <fcntl.h>
44#include <sys/stat.h>
45#include <sys/time.h>
46#include <sys/types.h>
47#include <syslog.h>
48#include <unistd.h>
49
50#include <boost/process.hpp>
51
52#include <SimoxUtility/algorithm/string/string_tools.h>
53
59
61#include "../parser/iceparser.h"
62#include "ProcessUtil.h"
63
64using namespace ScenarioManager;
65using namespace Exec;
66using namespace Parser;
67//using namespace Data_Structure;
68using namespace armarx;
69
70namespace
71{
72 /**
73 * How long a /proc snapshot may be reused. Walking /proc costs one open() per process on the
74 * machine, so it must happen once for all applications rather than once per application per
75 * poll. Applications do not appear and disappear behind the ScenarioManager's back often
76 * enough for this to need to be tight.
77 */
78 constexpr std::chrono::seconds ProcessScanInterval{2};
79
80 std::mutex ProcessScanMutex;
81 std::vector<ProcessUtil::ProcessInfo> ProcessScanCache;
82 std::chrono::steady_clock::time_point ProcessScanTime;
83 bool ProcessScanned = false;
84
85 /**
86 * @return the processes whose executable name is `executableName`, from a /proc snapshot that
87 * is at most ProcessScanInterval old.
88 */
89 std::vector<ProcessUtil::ProcessInfo>
90 FindProcessesNamed(const std::string& executableName)
91 {
92 std::vector<ProcessUtil::ProcessInfo> matches;
93 if (executableName.empty())
94 {
95 return matches;
96 }
97
98 std::lock_guard<std::mutex> guard(ProcessScanMutex);
99
100 const auto now = std::chrono::steady_clock::now();
101 if (!ProcessScanned || now - ProcessScanTime >= ProcessScanInterval)
102 {
103 ProcessScanCache = ProcessUtil::ScanProcesses();
104 ProcessScanTime = now;
105 ProcessScanned = true;
106 }
107
108 // Copy out only what the caller asked for - usually nothing or a single entry - so that
109 // the whole snapshot is not duplicated for every application.
110 for (const ProcessUtil::ProcessInfo& process : ProcessScanCache)
111 {
112 if (process.name == executableName)
113 {
114 matches.push_back(process);
115 }
116 }
117 return matches;
118 }
119} // namespace
120
122{
123 EnsureChildrenAreReaped();
124}
125
126void
127LinuxStarter::EnsureChildrenAreReaped()
128{
129#if defined(BOOST_POSIX_API)
130 // The applications we launch are never waited for, so ask the kernel to reap them itself
131 // instead of leaving zombies behind.
132 //
133 // This changes the signal disposition of the *whole* process, and while SIGCHLD is ignored no
134 // child can be waited for any more - system(), popen() and QProcess::waitForFinished() all
135 // stop working. That conflict is unavoidable in a GUI that hosts other plugins, and it cuts
136 // both ways: whoever installs a SIGCHLD handler later - Qt's QProcess does, on first use -
137 // silently switches the kernel's auto-reaping back off, because auto-reaping is a property of
138 // the disposition and not of any handler's behaviour.
139 //
140 // This used to be done exactly once per process, which meant that a disposition lost that way
141 // was never restored: from then on every application we stopped stayed behind as a zombie, its
142 // /proc entry never went away, and the ScenarioManager waited out the full termination timeout
143 // and then latched on Unknown. Re-asserting it before each launch bounds the damage to the
144 // applications started while the disposition was lost.
145 //
146 // It does not clean up retroactively: Linux discards children that terminate *after* SIGCHLD
147 // is ignored, so zombies that have already accumulated stay until their parent exits. Reaping
148 // them here with waitpid(-1) is not an option either, as that would swallow the exit status of
149 // children belonging to other parts of the GUI. Those leftovers are instead handled where they
150 // are observed - a zombie reads as dead, not as an unclear state - so they are harmless rather
151 // than absent.
152 signal(SIGCHLD, SIG_IGN);
153#endif
154}
155
156void
158 StatusManager statusManager,
159 const std::string& commandLineParameters,
160 bool printOnly)
161{
162 if (!app->getEnabled())
163 {
164 return;
165 }
166
167 std::filesystem::path componentFolderPath =
168 std::filesystem::path(ArmarXDataPath::GetCachePath()) / "ComponentFiles";
169
170 if (!std::filesystem::exists(componentFolderPath))
171 {
172 if (!std::filesystem::create_directories(componentFolderPath))
173 {
174 std::cout << "Could not create Cache folder for ScenarioManagerPlugin at "
175 << componentFolderPath.string() << std::endl;
176 }
177 }
178
179
180 std::filesystem::path dependenciesFilePath =
181 componentFolderPath /
182 std::filesystem::path("./" + app->getScenario()->getPackage()->getName() +
183 ".dependencies.cfg");
184
185 if (!std::filesystem::exists(dependenciesFilePath))
186 {
189 Data_Structure::PackagePtr(app->getScenario()->getPackage()));
190 }
191
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))
196 {
197 std::cout << "Launching " << app->getName() << " in "
198 << app->getScenario()->getPackage()->getName() << " without an needed cfg file";
199 }
200 if (!std::filesystem::exists(app->getExecutableAbsPath()))
201 {
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;
207 }
208 std::string args;
209 args += app->getExecutableAbsPath();
210 args += std::string(" --Ice.Config=")
211 .append(app->getScenario()->getGlobalConfigPath())
212 .append(",")
213 .append(app->getConfigPath());
214 auto configDomain = app->getConfigDomain();
215 ARMARX_CHECK_EXPRESSION(!configDomain.empty());
216 args += std::string(" --" + configDomain + ".DependenciesConfig=")
217 .append(dependenciesFilePath.string());
218 args +=
219 std::string(" --" + configDomain + ".LoggingGroup=").append(app->getScenario()->getName());
220
221 if (!commandLineParameters.empty())
222 {
223 Ice::StringSeq additional = Split(commandLineParameters, " ");
224
225 bool inString = false;
226 std::string inStringChar;
227 std::string toPush = "";
228 for (auto command : additional)
229 {
230 if (!inString && command.find("\"") != std::string::npos)
231 {
232 inString = true;
233 inStringChar = "\"";
234 }
235 else if (!inString && command.find("\'") != std::string::npos)
236 {
237 inString = true;
238 inStringChar = "\'";
239 }
240 else if (inString && command.find(inStringChar) != std::string::npos)
241 {
242 inString = false;
243 toPush += command;
244 args += " " + toPush;
245 toPush = "";
246 continue;
247 }
248
249 if (inString)
250 {
251 toPush += command + " ";
252 }
253 else
254 {
255 if (command != "")
256 {
257 args += " " + command;
258 }
259 }
260 }
261 }
262 else
263 {
264 Ice::StringSeq additional = Split(this->commandLineParameters, " ");
265 for (auto arg : additional)
266 {
267 args += arg + " ";
268 }
269 }
270
271 using namespace boost::process;
272 using namespace boost::process::initializers;
273
274 if (printOnly)
275 {
276 for (auto arg : args)
277 {
278 std::cout << arg;
279 }
280 std::cout << " &" << std::endl << std::endl;
281 return;
282 }
283
284 //this apperantly prodcuces escaping errors
285 //child c = execute(set_args(args), inherit_env());
286
287 //this is the solution
288 ARMARX_INFO << "Executing command `" << app->getExecutableAbsPath() << " " << args;
289
290 // Right before forking, so that this child is reaped even if something else in the process has
291 // taken the SIGCHLD disposition over since the last launch. We never wait for what we start.
292 EnsureChildrenAreReaped();
293
294 child c = execute(run_exe(app->getExecutableAbsPath()), set_cmd_line(args), inherit_env());
295
296 int pid = c.pid;
297
298 app->setPid(pid);
299 statusManager.savePid(app);
300}
301
302std::string
304{
305 int pid = application->getPid();
306
307 //if the app has no pid ask the pid manager if there is an cached Pid for this app
308 if (pid < 0)
309 {
310 const int cachedPid = statusManager.loadPid(application);
311
312 if (cachedPid < 0)
313 {
314 // Nothing cached either. The application may still be running - we started it in an
315 // earlier session, lost its pid, or it was started outside the ScenarioManager
316 // altogether - so look for it before declaring it stopped.
317 return adoptRunningProcess(application, statusManager);
318 }
319
320 // A cached pid may have been recycled by a completely unrelated process in the meantime,
321 // so only adopt it if it still belongs to this application.
322 const std::string processName = ProcessUtil::GetProcessName(cachedPid);
323 if (processName.empty())
324 {
325 if (!ProcessUtil::IsAlive(cachedPid))
326 {
327 // The process is really gone - drop the stale cache entry, then check whether the
328 // application is running under some other pid.
329 //
330 // Reset the pid only if it is still unset: a start running concurrently may have
331 // assigned a real pid while we were probing the cached one, and clobbering that
332 // would leave the application it just launched marked as stopped forever.
333 if (application->compareAndResetPid(-1))
334 {
335 statusManager.savePid(application);
336 }
337 return adoptRunningProcess(application, statusManager);
338 }
339
340 // The process exists but cannot be identified right now (it may have been forked but
341 // not exec'd yet). Leave the cached pid alone and try again on the next poll: wrongly
342 // discarding it here would mark a running application as stopped for good.
344 }
345
346 if (processName != application->getExecutableName())
347 {
348 //else delete the saved pid and inform the user
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))
353 {
354 statusManager.savePid(application);
355 }
356 return adoptRunningProcess(application, statusManager);
357 }
358
359 if (!application->adoptPidIfUnset(cachedPid))
360 {
361 // A start running concurrently has assigned a pid while we were probing - that one is
362 // authoritative.
363 pid = application->getPid();
364 }
365 else
366 {
367 pid = cachedPid;
368 }
369
370 if (pid < 0)
371 {
372 return adoptRunningProcess(application, statusManager);
373 }
374 }
375
376 // Everything below reasons about /proc/<pid>, so first make sure that pid is still ours.
377 //
378 // Pids are recycled. Once our process has exited the kernel may hand its pid to something
379 // completely unrelated, and we would then report that stranger's state as the application's:
380 // alive when the application is not, refusing a start because "it is already running", and
381 // signalling - possibly killing - an innocent process on stop. This check used to exist only
382 // on the cached-pid path below, which is why restarting the ScenarioManager "fixed" such a
383 // row: it dropped the in-memory pid and forced it through that path.
385 pid, application->getExecutableName(), application->getConfigPath());
386
387 if (identity == ProcessUtil::Identity::Differs)
388 {
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))
394 {
395 statusManager.savePid(application);
396 }
397 return adoptRunningProcess(application, statusManager);
398 }
399
400 switch (probeProcessState(pid))
401 {
402 case ProcessState::Alive:
404
405 case ProcessState::Indeterminate:
406 // The process is there but we cannot say what it is doing. Keep the pid - the next
407 // poll will most likely be able to read it again.
408 //
409 // Unless we could not confirm it is ours either. Then holding on to it is what makes
410 // this state absorbing: a pid that is neither identifiable nor probeable is never
411 // reconsidered, so the application stays Unknown - unstartable and unstoppable - until
412 // the ScenarioManager itself is restarted. Look for the real process instead, and only
413 // keep the pid if there is nothing better.
414 if (identity != ProcessUtil::Identity::Matches)
415 {
416 if (const std::optional<std::string> status =
417 rediscover(application, statusManager, pid))
418 {
419 return *status;
420 }
421 }
423
424 case ProcessState::Dead:
425 break;
426 }
427
428 // Only clear the pid we actually probed. A start running concurrently may have stored a
429 // new pid in the meantime, and wiping that would leave a running application marked as
430 // stopped until the ScenarioManager is restarted.
431 if (application->compareAndResetPid(pid))
432 {
433 statusManager.savePid(application);
434 }
435 else
436 {
437 // Somebody else owns the pid now; report on that one instead of on the corpse we probed.
439 }
440
441 // The pid died, but the application may have been restarted behind our back, so do not commit
442 // to "Stopped" without looking.
443 return adoptRunningProcess(application, statusManager);
444}
445
446std::string
447LinuxStarter::adoptRunningProcess(const ApplicationInstancePtr& application,
448 StatusManager& statusManager)
449{
450 bool ambiguous = false;
451 const int pid = findRunningProcess(application, ambiguous);
452
453 if (pid < 0)
454 {
455 // Matching processes exist but none of them could be attributed to this application (or
456 // several could). Guessing would let a stop request kill an unrelated process, so say we
457 // do not know rather than picking one.
460 }
461
462 if (!application->adoptPidIfUnset(pid))
463 {
464 // A start beat us to it - its pid is the authoritative one and will be probed next poll.
466 }
467
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);
473
474 // Report on the pid we just adopted rather than assuming it is alive: it may have exited
475 // between the scan and now.
476 return probeProcessState(pid) == ProcessState::Alive
479}
480
481std::optional<std::string>
482LinuxStarter::rediscover(const ApplicationInstancePtr& application,
483 StatusManager& statusManager,
484 int stalePid)
485{
486 bool ambiguous = false;
487 const int found = findRunningProcess(application, ambiguous);
488
489 if (found < 0 || found == stalePid)
490 {
491 // Nothing to hand over to. Note that this deliberately does not report Stopped: the pid we
492 // hold may still be the application, we just could not confirm it.
493 return std::nullopt;
494 }
495
496 // Two steps rather than one exchange, so that a start running concurrently wins either way: it
497 // either still holds `stalePid` and the reset succeeds, or it has already stored its own pid
498 // and one of the two calls fails, leaving its pid untouched.
499 if (!application->compareAndResetPid(stalePid) || !application->adoptPidIfUnset(found))
500 {
501 return std::nullopt;
502 }
503
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);
508
509 return probeProcessState(found) == ProcessState::Alive
512}
513
514int
515LinuxStarter::findRunningProcess(const ApplicationInstancePtr& application, bool& ambiguous)
516{
517 ambiguous = false;
518
519 const std::vector<ProcessUtil::ProcessInfo> candidates =
520 FindProcessesNamed(application->getExecutableName());
521 if (candidates.empty())
522 {
523 return -1;
524 }
525
526 const std::string ownConfigPath = application->getConfigPath();
527
528 // Processes carrying an --Ice.Config that names our config file are certainly ours; ones
529 // carrying an --Ice.Config that names somebody else's are certainly not. Anything else - a
530 // component launched by hand from a shell, say - is a possible but unproven match, and is only
531 // used when it is the sole candidate.
532 std::vector<int> certain;
533 std::vector<int> possible;
534
535 for (const ProcessUtil::ProcessInfo& candidate : candidates)
536 {
537 // The snapshot is up to a couple of seconds old, so a candidate may already be gone -
538 // notably the process we just stopped ourselves. Adopting a corpse would report it as
539 // running for another poll.
540 if (!ProcessUtil::IsAlive(candidate.pid))
541 {
542 continue;
543 }
544
545 const std::optional<std::string> configArgument =
546 ProcessUtil::FindIceConfigArgument(candidate.arguments);
547
548 if (!configArgument.has_value())
549 {
550 possible.push_back(candidate.pid);
551 }
552 else if (!ownConfigPath.empty() && Contains(*configArgument, ownConfigPath))
553 {
554 certain.push_back(candidate.pid);
555 }
556 // else: it belongs to a different scenario or instance of the same binary - not ours.
557 }
558
559 if (certain.size() == 1)
560 {
561 return certain.front();
562 }
563 if (!certain.empty())
564 {
565 // Several processes claim the same config. Nothing sensible to pick.
566 ambiguous = true;
567 return -1;
568 }
569 if (possible.size() == 1)
570 {
571 return possible.front();
572 }
573
574 ambiguous = !possible.empty();
575 return -1;
576}
577
578LinuxStarter::ProcessState
579LinuxStarter::probeProcessState(int pid)
580{
581 if (pid <= 0)
582 {
583 return ProcessState::Dead;
584 }
585
586 // Anything that goes wrong from here on is only a death if the process is really gone. A
587 // /proc entry we cannot read - because file descriptors ran out, because /proc is mounted
588 // with hidepid, because the read was interrupted - used to be reported as "Stopped", which
589 // then made the caller throw the pid away and lose a running application for good.
590 const auto stateIfUnreadable = [pid]
591 { return ProcessUtil::IsAlive(pid) ? ProcessState::Indeterminate : ProcessState::Dead; };
592
593 const std::string processFilePath = "/proc/" + std::to_string(pid) + "/status";
594 std::ifstream t(processFilePath);
595
596 if (!t.is_open())
597 {
598 return stateIfUnreadable();
599 }
600
601 std::string stateLine;
602 std::string line;
603 while (std::getline(t, line))
604 {
605 if (Contains(line, "State:"))
606 {
607 stateLine = line;
608 break;
609 }
610 }
611 if (stateLine.empty())
612 {
613 return stateIfUnreadable();
614 }
615
616 std::string result = stateLine.substr(stateLine.find(':') + 1);
617
618 simox::alg::trim(result);
619
620 if (result.empty())
621 {
622 return stateIfUnreadable();
623 }
624
625 switch (result.front())
626 {
627 case 'R': // running
628 case 'S': // interruptible sleep
629 return ProcessState::Alive;
630
631 case 'X': // dead
632 case 'Z': // terminated, still occupying its pid until somebody reaps it
633 // A zombie is a *confirmed* death, not an unclear reading: the process has run its
634 // course and will never run again. Reporting it as indeterminate kept the pid, and
635 // since a zombie's /proc entry never goes away by itself the application stayed
636 // Unknown - neither startable nor stoppable - until the ScenarioManager was restarted.
637 return ProcessState::Dead;
638
639 case 'D': // uninterruptible sleep
640 case 'T': // stopped by a signal - alive, and it must not lose its pid over this
641 case 't': // stopped by a debugger
642 return ProcessState::Indeterminate;
643
644 default:
645 ARMARX_DEBUG_S << "Unhandled process state '" << result.front() << "' for pid " << pid;
646 return ProcessState::Indeterminate;
647 }
648}
std::shared_ptr< ScenarioManager::Data_Structure::ApplicationInstance > ApplicationInstancePtr
Definition StopStrategy.h:9
constexpr T c
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.
Definition Logging.h:203
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_INFO_S
Definition Logging.h:200
std::shared_ptr< Package > PackagePtr
Definition Package.h:122
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.
A live process as seen by ScanProcesses().
Definition ProcessUtil.h:73