MILESTONE // dependency must start successfully, but once started the dependency becomes soft
};
+enum class stopped_reason_t
+{
+ NORMAL,
+
+ // Start failures:
+ DEPFAILED, // A dependency failed to start
+ FAILED, // failed to start (process terminated)
+ EXECFAILED, // failed to start (couldn't launch process)
+ TIMEDOUT, // timed out when starting
+
+ // Failure after starting:
+ TERMINATED // process terminated
+};
+
/* Service dependency record */
class service_dep
{
uid_t socket_uid = -1; // socket user id or -1
gid_t socket_gid = -1; // socket group id or -1
+ stopped_reason_t stop_reason = stopped_reason_t::NORMAL; // reason why stopped
+
// Data for use by service_set
public:
}
depends_on.clear();
}
+
+ // Why did the service stop?
+ stopped_reason_t get_stop_reason()
+ {
+ return stop_reason;
+ }
};
inline auto extract_prop_queue(service_record *sr) -> decltype(sr->prop_queue_node) &
started();
}
else {
+ stop_reason = stopped_reason_t::FAILED;
failed_to_start();
}
}
return;
}
else {
+ stop_reason = stopped_reason_t::TERMINATED;
emergency_stop();
}
services->process_queues();
{
log(loglevel_t::ERROR, get_name(), ": execution failed: ", strerror(errcode));
if (get_state() == service_state_t::STARTING) {
+ stop_reason = stopped_reason_t::EXECFAILED;
failed_to_start();
}
else {
// Process service in smooth recovery:
+ stop_reason = stopped_reason_t::TERMINATED;
emergency_stop();
}
}
if (need_stop) {
// Failed startup: no auto-restart.
+ stop_reason = stopped_reason_t::TERMINATED;
emergency_stop();
services->process_queues();
}
switch (pid_result) {
case pid_result_t::FAILED:
// Failed startup: no auto-restart.
+ stop_reason = stopped_reason_t::FAILED;
failed_to_start();
break;
case pid_result_t::TERMINATED:
}
}
else {
+ stop_reason = stopped_reason_t::FAILED;
failed_to_start();
}
}
start_explicit = false;
release(false);
}
+ stop_reason = stopped_reason_t::TERMINATED;
forced_stop();
stop_dependents();
stopped();
{
log(loglevel_t::ERROR, get_name(), ": execution failed: ", strerror(errcode));
// Only time we execute is for startup:
+ stop_reason = stopped_reason_t::EXECFAILED;
failed_to_start();
}
log(loglevel_t::ERROR, "Service ", get_name(), " command terminated due to signal ",
exit_status.get_term_sig());
}
+ stop_reason = stopped_reason_t::FAILED;
failed_to_start();
}
services->process_queues();
log(loglevel_t::ERROR, get_name(), ": execution failed: ", strerror(errcode));
auto service_state = get_state();
if (service_state == service_state_t::STARTING) {
+ stop_reason = stopped_reason_t::EXECFAILED;
failed_to_start();
}
else if (service_state == service_state_t::STOPPING) {
bsp->exec_succeeded();
}
+ static void exec_failed(base_process_service *bsp, int errcode)
+ {
+ bsp->waiting_for_execstat = false;
+ bsp->exec_failed(errcode);
+ }
+
static void handle_exit(base_process_service *bsp, int exit_status)
{
bsp->pid = -1;
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::TERMINATED);
assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::TIMEDOUT);
assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::TIMEDOUT);
assert(ts.get_state() == service_state_t::STARTED);
assert(event_loop.active_timers.size() == 0);
sset.remove_service(&p);
}
+// Test exec() failure for process service start.
+void test_proc_start_execfail()
+{
+ using namespace std;
+
+ service_set sset;
+
+ string command = "test-command";
+ list<pair<unsigned,unsigned>> command_offsets;
+ command_offsets.emplace_back(0, command.length());
+ std::list<prelim_dep> depends;
+
+ process_service p {&sset, "testproc", std::move(command), command_offsets, depends};
+ init_service_defaults(p);
+ sset.add_service(&p);
+
+ p.start(true);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTING);
+
+ base_process_service_test::exec_failed(&p, ENOENT);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::EXECFAILED);
+ assert(event_loop.active_timers.size() == 0);
+
+ sset.remove_service(&p);
+}
+
+
// Test stop timeout
void test_proc_stop_timeout()
{
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
// Note that timer is still active as we faked its expiry above
//assert(event_loop.active_timers.size() == 0);
sset.process_queues();
assert(p.get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
event_loop.active_timers.clear();
sset.remove_service(&p);
assert(p.get_state() == service_state_t::STOPPED);
assert(s2->get_state() == service_state_t::STOPPED);
assert(s3->get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::FAILED);
+ assert(s2->get_stop_reason() == stopped_reason_t::DEPFAILED);
+ assert(s3->get_stop_reason() == stopped_reason_t::DEPFAILED);
event_loop.active_timers.clear();
sset.remove_service(&p);
assert(p.get_state() == service_state_t::STOPPED);
assert(s2->get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
+ assert(s2->get_stop_reason() == stopped_reason_t::NORMAL);
assert(sset.count_active_services() == 0);
event_loop.active_timers.clear();
assert(p.get_state() == service_state_t::STOPPED);
assert(s2->get_state() == service_state_t::STOPPED);
+ assert(p.get_stop_reason() == stopped_reason_t::NORMAL);
+ assert(s2->get_stop_reason() == stopped_reason_t::NORMAL);
assert(sset.count_active_services() == 0);
event_loop.active_timers.clear();
sset.remove_service(&p);
}
+
#define RUN_TEST(name, spacing) \
std::cout << #name "..." spacing; \
name(); \
RUN_TEST(test_term_via_stop, " ");
RUN_TEST(test_proc_start_timeout, " ");
RUN_TEST(test_proc_start_timeout2, " ");
+ RUN_TEST(test_proc_start_execfail, " ");
RUN_TEST(test_proc_stop_timeout, " ");
RUN_TEST(test_proc_smooth_recovery1, "");
RUN_TEST(test_proc_smooth_recovery2, "");