ApplicationLifecycle.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package ArmarXCore::core
17 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
18 * GNU General Public License
19 */
20
22
23#include <thread>
24#include <utility>
25
28
29#include "../parser/iceparser.h"
30#include "Executor.h"
31#include "ProcessUtil.h"
32
33using namespace ScenarioManager;
34using namespace Exec;
35using namespace Data_Structure;
36
37std::mutex&
39{
40 static std::mutex mutex;
41 return mutex;
42}
43
44std::shared_mutex&
46{
47 static std::shared_mutex mutex;
48 return mutex;
49}
50
52 StatusManager statusManager) :
53 application(std::move(application)), statusManager(statusManager)
54{
55}
56
57void
59 const ApplicationStarterPtr& starter,
60 const StopStrategyPtr& stopStrategy,
61 const std::string& commandLineParameters,
62 bool printOnly)
63{
64 if (intent == Intent::None)
65 {
66 return;
67 }
68
69 std::lock_guard<std::mutex> guard(mutex);
70
71 if (pending.intent != Intent::None && pending.intent != intent)
72 {
73 ARMARX_INFO_S << "Superseding the pending " << ToString(pending.intent) << " of `"
74 << application->getName() << "` with " << ToString(intent) << ".";
75 }
76
77 pending = Request{intent, starter, stopStrategy, commandLineParameters, printOnly};
78 publishIntent();
79
80 if (!workerRunning)
81 {
82 workerRunning = true;
83 // The worker keeps this object (and through it the application) alive for as long as it
84 // runs, so the ScenarioManager may be closed while an operation is still in flight.
85 std::thread([self = shared_from_this()] { self->run(); }).detach();
86 }
87}
88
91{
92 std::lock_guard<std::mutex> guard(mutex);
93 return pending.intent != Intent::None ? pending.intent : current.intent;
94}
95
96bool
98{
99 return effectiveIntent() != Intent::None;
100}
101
102void
104{
105 std::unique_lock<std::mutex> guard(mutex);
106 idleCondition.wait(guard,
107 [this] { return !workerRunning && pending.intent == Intent::None; });
108}
109
110void
111ApplicationLifecycle::publishIntent()
112{
113 application->setPendingIntent(pending.intent != Intent::None ? pending.intent : current.intent);
114}
115
116void
117ApplicationLifecycle::run()
118{
119 for (;;)
120 {
121 Request request;
122 {
123 std::lock_guard<std::mutex> guard(mutex);
124 if (pending.intent == Intent::None)
125 {
126 // Nothing left to do - let the thread go. A later request() spawns a new one.
127 current = Request{};
128 workerRunning = false;
129 publishIntent();
130 idleCondition.notify_all();
131 return;
132 }
133
134 request = pending;
135 pending = Request{};
136 current = request;
137 publishIntent();
138 }
139
140 try
141 {
142 execute(request);
143 }
144 catch (...)
145 {
146 ARMARX_WARNING_S << "The " << ToString(request.intent) << " of application `"
147 << application->getName()
148 << "` failed: " << armarx::GetHandledExceptionString();
149 }
150
151 {
152 std::lock_guard<std::mutex> guard(mutex);
153 current = Request{};
154 publishIntent();
155 }
156 }
157}
158
159void
160ApplicationLifecycle::execute(const Request& request)
161{
162 // Blocks while a global operation such as `armarx killAll` is running.
163 std::shared_lock<std::shared_mutex> globalLock(GlobalOperationMutex());
164
165 switch (request.intent)
166 {
167 case Intent::Start:
168 executeStart(request);
169 break;
170 case Intent::Stop:
171 executeStop(request);
172 break;
173 case Intent::Restart:
174 executeRestart(request);
175 break;
176 case Intent::None:
177 break;
178 }
179}
180
181void
182ApplicationLifecycle::executeStart(const Request& request)
183{
184 if (isRunning())
185 {
186 ARMARX_INFO_S << "Application `" << application->getName() << "` is already running.";
187 return;
188 }
189
190 prepare();
191
192 // Preparing can take seconds. If the user has asked for a stop in the meantime, never launch
193 // the process in the first place.
194 if (supersededByStop())
195 {
196 ARMARX_INFO_S << "Not launching `" << application->getName()
197 << "`: a stop was requested while it was being prepared.";
198 return;
199 }
200
201 launch(request);
202}
203
204void
205ApplicationLifecycle::executeStop(const Request& request)
206{
207 if (!isRunning())
208 {
209 return;
210 }
211
212 stopAndWait(request);
213}
214
215void
216ApplicationLifecycle::executeRestart(const Request& request)
217{
218 if (isRunning())
219 {
220 stopAndWait(request);
221
222 if (isRunning())
223 {
224 // Launching now would leave the old process running next to the new one, and only the
225 // new pid would be remembered - the ScenarioManager would lose track of the old one.
226 ARMARX_WARNING_S << "Not restarting `" << application->getName()
227 << "`: it did not shut down. Force kill it and try again.";
228 return;
229 }
230
231 if (supersededByStop())
232 {
233 ARMARX_INFO_S << "Not restarting `" << application->getName()
234 << "`: a stop was requested while it was shutting down.";
235 return;
236 }
237 }
238
239 // Restarting an application that is not running simply starts it.
240 prepare();
241
242 if (supersededByStop())
243 {
244 ARMARX_INFO_S << "Not launching `" << application->getName()
245 << "`: a stop was requested while it was being prepared.";
246 return;
247 }
248
249 launch(request);
250}
251
252void
253ApplicationLifecycle::prepare()
254{
255 // Generates the property definitions if the cache is cold or the binary was rebuilt, then
256 // parses them. The launch needs them: the config domain comes from that xml.
258 application->load();
259}
260
261void
262ApplicationLifecycle::launch(const Request& request)
263{
265
266 std::lock_guard<std::mutex> serialize(LaunchMutex());
267 request.starter->startApplication(
268 application, statusManager, request.commandLineParameters, request.printOnly);
269}
270
271void
272ApplicationLifecycle::stopAndWait(const Request& request)
273{
274 ARMARX_CHECK_NOT_NULL(request.stopStrategy);
275 request.stopStrategy->stop(application);
276}
277
278bool
279ApplicationLifecycle::isRunning()
280{
281 const int pid = application->getPid();
282 if (pid <= 0)
283 {
284 return false;
285 }
286
287 if (ProcessUtil::IsAlive(pid))
288 {
289 // A live pid is only this application's as long as it is still running the right
290 // executable. Once our process has exited the kernel may hand its pid to something
291 // unrelated, and taking that stranger for the application refuses every start ("it is
292 // already running") and makes every stop signal the wrong process.
293 //
294 // Only a positive mismatch is acted upon: a pid whose /proc entry cannot be read at all is
295 // kept, because wrongly declaring a running application dead is the worse error.
297 application->getExecutableName(),
298 application->getConfigPath()) !=
300 {
301 return true;
302 }
303
304 ARMARX_INFO_S << "The pid " << pid << " remembered for application `"
305 << application->getName()
306 << "` has been recycled by an unrelated process. Forgetting it.";
307 }
308
309 // A pid left behind by a process that has already died would otherwise block every future
310 // start of this application.
311 if (application->compareAndResetPid(pid))
312 {
313 statusManager.savePid(application);
314 }
315 return false;
316}
317
318bool
319ApplicationLifecycle::supersededByStop() const
320{
321 std::lock_guard<std::mutex> guard(mutex);
322 return pending.intent == Intent::Stop;
323}
ApplicationLifecycle(Data_Structure::ApplicationInstancePtr application, StatusManager statusManager)
void request(Data_Structure::Intent intent, const ApplicationStarterPtr &starter, const StopStrategyPtr &stopStrategy, const std::string &commandLineParameters="", bool printOnly=false)
Requests that the application be started, stopped or restarted, superseding any intent that has not b...
void wait()
Blocks until nothing is requested or running any more.
static void loadAndSetCachedProperties(Data_Structure::ApplicationPtr application, std::string path, bool reload=false, bool set=true)
Generates an XML file of the given application and saves it in the specified path.
Definition Executor.cpp:218
static std::string getCacheDir()
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO_S
Definition Logging.h:200
#define ARMARX_WARNING_S
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:211
Intent
What the ScenarioManager is currently trying to do with an application.
const char * ToString(Intent intent)
Human readable name of an intent, for logging.
std::shared_ptr< ApplicationInstance > ApplicationInstancePtr
@ Differs
argv says this is a different program, or the same program serving a different config.
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::shared_mutex & GlobalOperationMutex()
Held shared by every application operation, and exclusively by global operations that act on all arma...
std::shared_ptr< StopStrategy > StopStrategyPtr
Definition Executor.h:48
std::mutex & LaunchMutex()
Serializes the actual spawning of applications.
std::shared_ptr< ApplicationStarter > ApplicationStarterPtr
std::string GetHandledExceptionString()