- log_service_stopped(service_name);
- notify_listeners(service_event_t::STOPPED);
-}
-
-dasynq::rearm service_child_watcher::status_change(eventloop_t &loop, pid_t child, int status) noexcept
-{
- base_process_service *sr = service;
-
- sr->pid = -1;
- sr->exit_status = status;
-
- // Ok, for a process service, any process death which we didn't rig
- // ourselves is a bit... unexpected. Probably, the child died because
- // we asked it to (sr->service_state == STOPPING). But even if
- // we didn't, there's not much we can do.
-
- if (sr->waiting_for_execstat) {
- // We still don't have an exec() status from the forked child, wait for that
- // before doing any further processing.
- return rearm::NOOP; // hold watch reservation
- }
-
- // Must stop watch now since handle_exit_status might result in re-launch:
- // (stop_watch instead of deregister, so that we hold watch reservation).
- stop_watch(loop);
-
- if (sr->stop_timer_armed) {
- sr->restart_timer.stop_timer(loop);
- sr->stop_timer_armed = false;
- }
-
- sr->handle_exit_status(status);
- return rearm::NOOP;
-}
-
-bool service_record::do_auto_restart() noexcept
-{
- if (auto_restart) {
- return services->get_auto_restart();
- }
- return false;
-}
-
-void service_record::emergency_stop() noexcept
-{
- if (! do_auto_restart() && start_explicit) {
- start_explicit = false;
- release();
- }
- forced_stop();
- stop_dependents();
- stopped();
-}
-
-void base_process_service::do_smooth_recovery() noexcept
-{
- if (! restart_ps_process()) {
- emergency_stop();
- services->process_queues();
- }
-}
-
-void process_service::handle_exit_status(int exit_status) noexcept
-{
- bool did_exit = WIFEXITED(exit_status);
- bool was_signalled = WIFSIGNALED(exit_status);
- restarting = false;
- auto service_state = get_state();
-
- if (exit_status != 0 && service_state != service_state_t::STOPPING) {
- if (did_exit) {
- log(loglevel_t::ERROR, "Service ", get_name(), " process terminated with exit code ",
- WEXITSTATUS(exit_status));
- }
- else if (was_signalled) {
- log(loglevel_t::ERROR, "Service ", get_name(), " terminated due to signal ",
- WTERMSIG(exit_status));
- }
- }
-
- if (service_state == service_state_t::STARTING) {
- if (did_exit && WEXITSTATUS(exit_status) == 0) {
- started();
- }
- else {
- failed_to_start();
- }
- }
- else if (service_state == service_state_t::STOPPING) {
- // We won't log a non-zero exit status or termination due to signal here -
- // we assume that the process died because we signalled it.
- stopped();
- }
- else if (smooth_recovery && service_state == service_state_t::STARTED
- && get_target_state() == service_state_t::STARTED) {
- do_smooth_recovery();
- return;
- }
- else {
- emergency_stop();
- }
- services->process_queues();
-}
-
-void process_service::exec_failed(int errcode) noexcept
-{
- log(loglevel_t::ERROR, get_name(), ": execution failed: ", strerror(errcode));
- if (get_state() == service_state_t::STARTING) {
- failed_to_start();
- }
- else {
- // Process service in smooth recovery:
- emergency_stop();
- }
-}
-
-void bgproc_service::handle_exit_status(int exit_status) noexcept
-{
- begin:
- bool did_exit = WIFEXITED(exit_status);
- bool was_signalled = WIFSIGNALED(exit_status);
- auto service_state = get_state();
-
- if (exit_status != 0 && service_state != service_state_t::STOPPING) {
- if (did_exit) {
- log(loglevel_t::ERROR, "Service ", get_name(), " process terminated with exit code ",
- WEXITSTATUS(exit_status));
- }
- else if (was_signalled) {
- log(loglevel_t::ERROR, "Service ", get_name(), " terminated due to signal ",
- WTERMSIG(exit_status));
- }
- }
-
- // This may be a "smooth recovery" where we are restarting the process while leaving the
- // service in the STARTED state.
- if (restarting && service_state == service_state_t::STARTED) {
- restarting = false;
- bool need_stop = false;
- if ((did_exit && WEXITSTATUS(exit_status) != 0) || was_signalled) {
- need_stop = true;
- }
- else {
- // We need to re-read the PID, since it has now changed.
- if (pid_file.length() != 0) {
- auto pid_result = read_pid_file(&exit_status);
- switch (pid_result) {
- case pid_result_t::FAILED:
- // Failed startup: no auto-restart.
- need_stop = true;
- break;
- case pid_result_t::TERMINATED:
- goto begin;
- case pid_result_t::OK:
- break;
- }
- }
- }
-
- if (need_stop) {
- // Failed startup: no auto-restart.
- emergency_stop();
- services->process_queues();
- }
-
- return;
- }
-
- restarting = false;
- if (service_state == service_state_t::STARTING) {
- // POSIX requires that if the process exited clearly with a status code of 0,
- // the exit status value will be 0:
- if (exit_status == 0) {
- auto pid_result = read_pid_file(&exit_status);
- switch (pid_result) {
- case pid_result_t::FAILED:
- // Failed startup: no auto-restart.
- failed_to_start();
- break;
- case pid_result_t::TERMINATED:
- // started, but immediately terminated
- started();
- goto begin;
- case pid_result_t::OK:
- started();
- break;
- }
- }
- else {
- failed_to_start();
- }
- }
- else if (service_state == service_state_t::STOPPING) {
- // We won't log a non-zero exit status or termination due to signal here -
- // we assume that the process died because we signalled it.
- stopped();
- }
- else {
- // we must be STARTED
- if (smooth_recovery && get_target_state() == service_state_t::STARTED) {
- do_smooth_recovery();
- return;
- }
- if (! do_auto_restart() && start_explicit) {
- start_explicit = false;
- release();
- }
- forced_stop();
- stop_dependents();
- stopped();
- }
- services->process_queues();
-}
-
-void bgproc_service::exec_failed(int errcode) noexcept
-{
- log(loglevel_t::ERROR, get_name(), ": execution failed: ", strerror(errcode));
- // Only time we execute is for startup:
- failed_to_start();
-}
-
-void scripted_service::handle_exit_status(int exit_status) noexcept
-{
- bool did_exit = WIFEXITED(exit_status);
- bool was_signalled = WIFSIGNALED(exit_status);
- auto service_state = get_state();
-
- // For a scripted service, a termination occurs in one of three main cases:
- // - the start script completed (or failed), when service was STARTING
- // - the start script was interrupted to cancel startup; state is STOPPING
- // - the stop script complete (or failed), state is STOPPING
-
- if (service_state == service_state_t::STOPPING) {
- // We might be running the stop script, or we might be running the start script and have issued
- // a cancel order via SIGINT:
- if (did_exit && WEXITSTATUS(exit_status) == 0) {
- if (interrupting_start) {
- interrupting_start = false;
- // launch stop script:
- bring_down();
- }
- else {
- // We were running the stop script and finished successfully
- stopped();
- }
- }
- else {
- if (interrupting_start) {
- // We issued a start interrupt, so we expected this failure:
- if (did_exit) {
- log(loglevel_t::INFO, "Service ", get_name(), " start cancelled; exit code ",
- WEXITSTATUS(exit_status));
- }
- else if (was_signalled) {
- log(loglevel_t::INFO, "Service ", get_name(), " start cancelled from signal ",
- WTERMSIG(exit_status));
- }
- }
- else {
- // ??? failed to stop! Let's log it as warning:
- if (did_exit) {
- log(loglevel_t::WARN, "Service ", get_name(), " stop command failed with exit code ",
- WEXITSTATUS(exit_status));
- }
- else if (was_signalled) {
- log(loglevel_t::WARN, "Service ", get_name(), " stop command terminated due to signal ",
- WTERMSIG(exit_status));
- }
- }
- // Even if the stop script failed, assume that service is now stopped, so that any dependencies
- // can be stopped. There's not really any other useful course of action here.
- interrupting_start = false;
- stopped();
- }
- services->process_queues();
- }
- else { // STARTING
- if (exit_status == 0) {
- started();
- }
- else {
- // failed to start
- if (did_exit) {
- log(loglevel_t::ERROR, "Service ", get_name(), " command failed with exit code ",
- WEXITSTATUS(exit_status));
- }
- else if (was_signalled) {
- log(loglevel_t::ERROR, "Service ", get_name(), " command terminated due to signal ",
- WTERMSIG(exit_status));
- }
- failed_to_start();
- }
- services->process_queues();
- }
-}
-
-void scripted_service::exec_failed(int errcode) noexcept
-{
- log(loglevel_t::ERROR, get_name(), ": execution failed: ", strerror(errcode));
- auto service_state = get_state();
- if (service_state == service_state_t::STARTING) {
- failed_to_start();
- }
- else if (service_state == service_state_t::STOPPING) {
- // We've logged the failure, but it's probably better not to leave the service in
- // STOPPING state:
- stopped();
- }
-}