1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
|
/* upstart
*
* quiesce.c - shutdown handling.
*
* Copyright © 2013 Canonical Ltd.
* Author: James Hunt <james.hunt@canonical.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include "quiesce.h"
#include "events.h"
#include "environ.h"
#include "conf.h"
#include "job_process.h"
#include "control.h"
#include <nih/main.h>
/**
* quiesce_requester:
*
* Where the quiesce request originated. This determines
* the shutdown behaviour.
**/
static QuiesceRequester quiesce_requester = QUIESCE_REQUESTER_INVALID;
/**
* quiesce_phase:
*
* Current phase of shutdown.
**/
static QuiescePhase quiesce_phase = QUIESCE_PHASE_NOT_QUIESCED;
/**
* quiesce_reason:
*
* Human-readable string denoting what triggered the quiesce.
**/
static char *quiesce_reason = NULL;
/**
* max_kill_timeout:
*
* Maxiumum kill_timeout value calculated from all running jobs used to
* determine how long to wait before exiting.
**/
static time_t max_kill_timeout = 0;
/**
* quiesce_phase_time:
*
* Time that a particular phase started.
**/
static time_t quiesce_phase_time = 0;
/**
* quiesce_start_time:
*
* Time quiesce commenced.
**/
static time_t quiesce_start_time = 0;
/**
* session_end_jobs:
*
* TRUE if any job specifies a 'start on' including SESSION_END_EVENT.
*
**/
static int session_end_jobs = FALSE;
static int quiesce_event_match (Event *event)
__attribute__ ((warn_unused_result));
/* External definitions */
extern int disable_respawn;
/**
* quiesce:
*
* @requester: where the quiesce request originated.
*
* Commence Session Init shutdown.
**/
void
quiesce (QuiesceRequester requester)
{
nih_local char **env = NULL;
Event *event;
job_class_init ();
/* Quiesce already in progress */
if (quiesce_phase != QUIESCE_PHASE_NOT_QUIESCED)
return;
quiesce_requester = requester;
/* System shutdown skips the wait phase to ensure all running
* jobs get signalled.
*
* Note that jobs which choose to start on SESSION_END_EVENT may
* not complete (or even start), but no guarantee is possible in
* the system shutdown scenario since Session Inits must not
* hold up the system.
*/
quiesce_phase = (requester == QUIESCE_REQUESTER_SYSTEM)
? QUIESCE_PHASE_KILL
: QUIESCE_PHASE_WAIT;
quiesce_reason = (requester == QUIESCE_REQUESTER_SESSION)
? _("logout") : _("shutdown");
nih_info (_("Quiescing due to %s request"), quiesce_reason);
quiesce_start_time = quiesce_phase_time = time (NULL);
/* Stop existing jobs from respawning */
disable_respawn = TRUE;
/* Signal that the session is ending. This may start new jobs.
*
* Note that the event doesn't actually get emitted until the
* next time the main loop gets a chance to run.
*/
env = NIH_MUST (nih_str_array_new (NULL));
NIH_MUST (environ_set (&env, NULL, NULL, TRUE,
"TYPE=%s", quiesce_reason));
event = NIH_MUST (event_new (NULL, SESSION_END_EVENT, env));
/* Check if any jobs care about the session end event. If not,
* the wait phase can be avoided entirely resulting in a much
* faster shutdown.
*
* Note that simply checking if running instances exist is not
* sufficient since if a job cares about the session end event,
* it won't yet have started but needs to be given a chance to
* run.
*/
if (quiesce_phase == QUIESCE_PHASE_WAIT) {
session_end_jobs = quiesce_event_match (event);
if (session_end_jobs) {
/* Some as-yet unscheduled jobs care about the
* session end event. They will be started the
* next time through the main loop and will be
* waited for (hence the quiesce phase is not
* changed).
*
* However, already-running jobs *can* be stopped
* at this time since by definition they do not
* care about the session end event and may just
* as well die now to avoid slowing the shutdown.
*/
job_process_stop_all ();
} else {
nih_debug ("Skipping wait phase");
quiesce_phase = QUIESCE_PHASE_KILL;
}
}
if (quiesce_phase == QUIESCE_PHASE_KILL) {
/* We'll attempt to wait for this long, but system
* policy may prevent it such that we just get killed
* and job processes reparented to PID 1.
*/
max_kill_timeout = job_class_max_kill_timeout ();
job_process_stop_all ();
}
/* Check every second to see if all jobs have finished. If so,
* we can exit early.
*/
NIH_MUST (nih_timer_add_periodic (NULL, 1,
(NihTimerCb)quiesce_wait_callback, NULL));
}
/**
* quiesce_wait_callback:
*
* @data: not used,
* @timer: timer that caused us to be called.
*
* Callback used to check if all jobs have finished and if so
* finalise Session Init shutdown.
**/
void
quiesce_wait_callback (void *data, NihTimer *timer)
{
time_t now;
nih_assert (timer);
nih_assert (quiesce_phase_time);
nih_assert (quiesce_requester != QUIESCE_REQUESTER_INVALID);
now = time (NULL);
if (quiesce_phase == QUIESCE_PHASE_KILL) {
nih_assert (max_kill_timeout);
if ((now - quiesce_phase_time) > max_kill_timeout)
goto timed_out;
} else if (quiesce_phase == QUIESCE_PHASE_WAIT) {
int timed_out = 0;
timed_out = ((now - quiesce_phase_time) >= QUIESCE_DEFAULT_JOB_RUNTIME);
if (timed_out
|| (session_end_jobs && ! job_process_jobs_running ())
|| ! job_process_jobs_running ()) {
quiesce_phase = QUIESCE_PHASE_KILL;
/* reset for new phase */
quiesce_phase_time = time (NULL);
max_kill_timeout = job_class_max_kill_timeout ();
job_process_stop_all ();
}
} else {
nih_assert_not_reached ();
}
if (! job_process_jobs_running ())
goto out;
return;
timed_out:
quiesce_show_slow_jobs ();
out:
/* Note that we might skip the kill phase for the session
* requestor if no jobs are actually running at this point.
*/
quiesce_phase = QUIESCE_PHASE_CLEANUP;
quiesce_finalise ();
/* Deregister */
nih_free (timer);
}
/**
* quiesce_show_slow_jobs:
*
* List jobs that are still running after their expected end time.
**/
void
quiesce_show_slow_jobs (void)
{
job_class_init ();
NIH_HASH_FOREACH (job_classes, iter) {
JobClass *class = (JobClass *)iter;
/* Note that instances get killed in a random order */
NIH_HASH_FOREACH (class->instances, job_iter) {
const char *name;
Job *job;
job = (Job *)job_iter;
name = job_name (job);
nih_warn ("job %s failed to stop", name);
}
}
}
/**
* quiesce_finalise:
*
* Request shutdown.
**/
void
quiesce_finalise (void)
{
static int finalising = FALSE;
time_t diff;
nih_assert (quiesce_start_time);
nih_assert (quiesce_phase == QUIESCE_PHASE_CLEANUP);
if (finalising)
return;
finalising = TRUE;
diff = time (NULL) - quiesce_start_time;
nih_info (_("Quiesce %s sequence took %s%d second%s"),
quiesce_reason,
! (int)diff ? "<" : "",
(int)diff ? (int)diff : 1,
diff <= 1 ? "" : "s");
nih_main_loop_exit (0);
}
/**
* quiesce_complete:
*
* Force quiesce phase to finish.
**/
void
quiesce_complete (void)
{
quiesce_phase = QUIESCE_PHASE_CLEANUP;
quiesce_finalise ();
}
/**
* quiesce_event_match:
* @event: event.
*
* Identify if any jobs _may_ start when the session ends.
*
* A simple heuristic is used such that there is no guarantee that the
* jobs entire start condition will be satisfied at session-end.
*
* Returns: TRUE if any class specifies @event in its start
* condition, else FALSE.
**/
static int
quiesce_event_match (Event *event)
{
nih_assert (event);
job_class_init ();
NIH_HASH_FOREACH (job_classes, iter) {
JobClass *class = (JobClass *)iter;
if (! class->start_on)
continue;
/* Note that only the jobs start on condition is
* relevant.
*/
NIH_TREE_FOREACH_POST (&class->start_on->node, iter) {
EventOperator *oper = (EventOperator *)iter;
switch (oper->type) {
case EVENT_OR:
case EVENT_AND:
break;
case EVENT_MATCH:
/* Job may attempt to start as the session ends */
if (event_operator_match (oper, event, NULL))
return TRUE;
break;
default:
nih_assert_not_reached ();
}
}
}
return FALSE;
}
/**
* quiesce_in_progress:
*
* Determine if shutdown is in progress.
*
* Returns: TRUE if quiesce is in progress, else FALSE.
**/
int
quiesce_in_progress (void)
{
return quiesce_phase != QUIESCE_PHASE_NOT_QUIESCED;
}
|