Move 'run_child_proc' function into a separate source file.
[oweals/dinit.git] / src / tests / test-baseproc.cc
1 #include "dinit.h"
2 #include "proc-service.h"
3
4 // This is a mock implementation of the base_process_service class, for testing purposes.
5
6 base_process_service::base_process_service(service_set *sset, string name,
7         service_type_t service_type_p, string &&command,
8         std::list<std::pair<unsigned,unsigned>> &command_offsets,
9         const std::list<prelim_dep> &deplist_p)
10      : service_record(sset, name, service_type_p, deplist_p), child_listener(this),
11        child_status_listener(this), restart_timer(this)
12 {
13     program_name = std::move(command);
14     exec_arg_parts = separate_args(program_name, command_offsets);
15
16     restart_interval_count = 0;
17     restart_interval_time = {0, 0};
18     restart_timer.service = this;
19     //restart_timer.add_timer(event_loop);
20
21     // By default, allow a maximum of 3 restarts within 10.0 seconds:
22     restart_interval.seconds() = 10;
23     restart_interval.nseconds() = 0;
24     max_restart_interval_count = 3;
25
26     waiting_restart_timer = false;
27     reserved_child_watch = false;
28     tracking_child = false;
29     stop_timer_armed = false;
30     start_is_interruptible = false;
31 }
32
33 bool base_process_service::bring_up() noexcept
34 {
35     if (restarting) {
36         if (pid == -1) {
37             return restart_ps_process();
38         }
39         return true;
40     }
41     else {
42         //event_loop.get_time(restart_interval_time, clock_type::MONOTONIC);
43         restart_interval_count = 0;
44         if (start_ps_process(exec_arg_parts, onstart_flags.starts_on_console)) {
45             if (start_timeout != time_val(0,0)) {
46                 //restart_timer.arm_timer_rel(event_loop, start_timeout);
47                 stop_timer_armed = true;
48             }
49             else if (stop_timer_armed) {
50                 //restart_timer.stop_timer(event_loop);
51                 stop_timer_armed = false;
52             }
53             return true;
54         }
55         return false;
56     }
57 }
58
59 void base_process_service::bring_down() noexcept
60 {
61     waiting_for_deps = false;
62     if (pid != -1) {
63         // The process is still kicking on - must actually kill it. We signal the process
64         // group (-pid) rather than just the process as there's less risk then of creating
65         // an orphaned process group:
66         if (! onstart_flags.no_sigterm) {
67             //kill_pg(SIGTERM);
68         }
69         if (term_signal != -1) {
70             //kill_pg(term_signal);
71         }
72
73         // In most cases, the rest is done in handle_exit_status.
74         // If we are a BGPROCESS and the process is not our immediate child, however, that
75         // won't work - check for this now:
76         if (get_type() == service_type_t::BGPROCESS && ! tracking_child) {
77             stopped();
78         }
79         else if (stop_timeout != time_val(0,0)) {
80             //restart_timer.arm_timer_rel(event_loop, stop_timeout);
81             stop_timer_armed = true;
82         }
83     }
84     else {
85         // The process is already dead.
86         stopped();
87     }
88 }
89
90 void base_process_service::do_smooth_recovery() noexcept
91 {
92     if (! restart_ps_process()) {
93         emergency_stop();
94         services->process_queues();
95     }
96 }
97
98 bool base_process_service::start_ps_process(const std::vector<const char *> &cmd, bool on_console) noexcept
99 {
100     return false;
101 }
102
103 void base_process_service::kill_with_fire() noexcept
104 {
105     if (pid != -1) {
106         //log(loglevel_t::WARN, "Service ", get_name(), " with pid ", pid, " exceeded allowed stop time; killing.");
107         //kill_pg(SIGKILL);
108     }
109 }
110
111 void base_process_service::kill_pg(int signo) noexcept
112 {
113     //pid_t pgid = getpgid(pid);
114     //if (pgid == -1) {
115         // only should happen if pid is invalid, which should never happen...
116         //log(loglevel_t::ERROR, get_name(), ": can't signal process: ", strerror(errno));
117         //return;
118     //}
119     //kill(-pgid, signo);
120 }
121
122 bool base_process_service::restart_ps_process() noexcept
123 {
124     using time_val = dasynq::time_val;
125
126     time_val current_time;
127     event_loop.get_time(current_time, clock_type::MONOTONIC);
128
129     if (max_restart_interval_count != 0) {
130         // Check whether we're still in the most recent restart check interval:
131         time_val int_diff = current_time - restart_interval_time;
132         if (int_diff < restart_interval) {
133             if (restart_interval_count >= max_restart_interval_count) {
134                 //log(loglevel_t::ERROR, "Service ", get_name(), " restarting too quickly; stopping.");
135                 return false;
136             }
137         }
138         else {
139             restart_interval_time = current_time;
140             restart_interval_count = 0;
141         }
142     }
143
144     // Check if enough time has lapsed since the prevous restart. If not, start a timer:
145     time_val tdiff = current_time - last_start_time;
146     if (restart_delay <= tdiff) {
147         // > restart delay (normally 200ms)
148         do_restart();
149     }
150     else {
151         time_val timeout = restart_delay - tdiff;
152         //restart_timer.arm_timer_rel(event_loop, timeout);
153         waiting_restart_timer = true;
154     }
155     return true;
156 }
157
158 void base_process_service::do_restart() noexcept
159 {
160     waiting_restart_timer = false;
161     restart_interval_count++;
162     auto service_state = get_state();
163
164     // We may be STARTING (regular restart) or STARTED ("smooth recovery"). This affects whether
165     // the process should be granted access to the console:
166     bool on_console = service_state == service_state_t::STARTING
167             ? onstart_flags.starts_on_console : onstart_flags.runs_on_console;
168
169     if (service_state == service_state_t::STARTING) {
170         // for a smooth recovery, we want to check dependencies are available before actually
171         // starting:
172         if (! check_deps_started()) {
173             waiting_for_deps = true;
174             return;
175         }
176     }
177
178     if (! start_ps_process(exec_arg_parts, on_console)) {
179         restarting = false;
180         if (service_state == service_state_t::STARTING) {
181             failed_to_start();
182         }
183         else {
184             // desired_state = service_state_t::STOPPED;
185             forced_stop();
186         }
187         services->process_queues();
188     }
189 }
190
191 bool base_process_service::interrupt_start() noexcept
192 {
193     if (waiting_restart_timer) {
194         //restart_timer.stop_timer(event_loop);
195         waiting_restart_timer = false;
196         return service_record::interrupt_start();
197     }
198     else {
199         //log(loglevel_t::WARN, "Interrupting start of service ", get_name(), " with pid ", pid, " (with SIGINT).");
200         kill_pg(SIGINT);
201         if (stop_timeout != time_val(0,0)) {
202             restart_timer.arm_timer_rel(event_loop, stop_timeout);
203             stop_timer_armed = true;
204         }
205         else if (stop_timer_armed) {
206             restart_timer.stop_timer(event_loop);
207             stop_timer_armed = false;
208         }
209         set_state(service_state_t::STOPPING);
210         notify_listeners(service_event_t::STARTCANCELLED);
211         return false;
212     }
213 }
214
215 void base_process_service::timer_expired() noexcept
216 {
217     stop_timer_armed = false;
218
219     // Timer expires if:
220     // We are stopping, including after having startup cancelled (stop timeout, state is STOPPING); We are
221     // starting (start timeout, state is STARTING); We are waiting for restart timer before restarting,
222     // including smooth recovery (restart timeout, state is STARTING or STARTED).
223     if (get_state() == service_state_t::STOPPING) {
224         kill_with_fire();
225     }
226     else if (pid != -1) {
227         // Starting, start timed out.
228         stop_dependents();
229         interrupt_start();
230     }
231     else {
232         // STARTING / STARTED, and we have a pid: must be restarting (smooth recovery if STARTED)
233         do_restart();
234     }
235 }