--- /dev/null
+/*****************************************************************************\
+* *
+* Name : launch_manager
+* Author : Chris Koeritz
+* *
+*******************************************************************************
+* Copyright (c) 2000-$now By Author. This program is free software; you can *
+* redistribute it and/or modify it under the terms of the GNU General Public *
+* License as published by the Free Software Foundation; either version 2 of *
+* the License or (at your option) any later version. This is online at: *
+* http://www.fsf.org/copyleft/gpl.html *
+* Please send any updates to: fred@gruntose.com *
+\*****************************************************************************/
+
+#include "hoople_service.h"
+#include "launch_manager.h"
+
+#include <application/windoze_helper.h>
+#include <basis/astring.h>
+#include <basis/mutex.h>
+#include <configuration/configurator.h>
+#include <configuration/section_manager.h>
+#include <filesystem/filename.h>
+#include <loggers/critical_events.h>
+#include <loggers/program_wide_logger.h>
+#include <processes/configured_applications.h>
+#include <processes/ethread.h>
+#include <processes/launch_process.h>
+#include <structures/set.h>
+#include <structures/string_table.h>
+#include <structures/unique_id.h>
+#include <textual/parser_bits.h>
+#include <timely/time_control.h>
+#include <timely/time_stamp.h>
+
+using namespace basis;
+using namespace configuration;
+using namespace filesystem;
+using namespace loggers;
+using namespace processes;
+using namespace structures;
+using namespace textual;
+using namespace timely;
+
+namespace application {
+
+#define DEBUG_PROCESS_MANAGER
+ // uncomment for verbose diagnostics.
+
+#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
+
+const int CHECK_INTERVAL = 4 * SECOND_ms;
+ // this is how frequently the checking thread executes to ensure that
+ // processes are gone when they should be.
+
+const int GRACEFUL_SLACK = 90 * SECOND_ms;
+ // the length of time before a graceful shutdown devolves into a forced
+ // shutdown.
+
+const int MAXIMUM_INITIAL_APP_WAIT = 4 * SECOND_ms;
+ // this is the longest we bother to wait for a process we just started.
+ // if it hasn't begun by then, we decide it will never do so.
+
+const int STARTUP_APPS_DELAY_PERIOD = 2 * SECOND_ms;
+ // we delay for this long before the initial apps are started.
+
+const int MAXIMUM_REQUEST_PAUSE = 42 * SECOND_ms;
+ // the longest we will ever wait for a response to be generated based on
+ // our last request.
+
+// these are concurrency control macros for the lists managed here.
+#define LOCK_CONFIG auto_synchronizer l(*_config_lock)
+#define LOCK_ZOMBIES auto_synchronizer l(*_zombie_lock)
+#define LOCK_KIDS auto_synchronizer l(*_scamp_lock)
+
+// error messages.
+#ifdef DEBUG_PROCESS_MANAGER
+ #define COMPLAIN_APPLICATION \
+ LOG(astring("the application called ") + app_name + " could not be found.")
+ #define COMPLAIN_PRODUCT \
+ LOG(astring("the section for ") + product + " could not be found.")
+#else
+ #define COMPLAIN_APPLICATION {}
+ #define COMPLAIN_PRODUCT {}
+#endif
+
+//////////////
+
+class launch_manager_thread : public ethread
+{
+public:
+ launch_manager_thread(launch_manager &parent)
+ : ethread(CHECK_INTERVAL, ethread::SLACK_INTERVAL),
+ _parent(parent) {}
+ virtual ~launch_manager_thread() {}
+
+ virtual void perform_activity(void *)
+ { _parent.push_timed_activities(_processes); }
+
+private:
+ launch_manager &_parent; // the owner of the object.
+ process_entry_array _processes; // will be filled as needed.
+};
+
+//////////////
+
+class graceful_record
+{
+public:
+ astring _product; // the product name the app is listed under.
+ astring _app_name; // the application's name.
+ time_stamp _started; // when the graceful shutdown started.
+ int _pid; // the particular process id for this app.
+ int _level; // the shutdown ordering specifier.
+
+ graceful_record(int pid = 0, const astring &product = "",
+ const astring &app_name = "", int level = 0)
+ : _product(product), _app_name(app_name), _pid(pid), _level(level) {}
+};
+
+class graceful_array : public array<graceful_record> {};
+
+//////////////
+
+launch_manager::launch_manager(configured_applications &config)
+: _configs(config),
+ _started_initial_apps(false),
+ _checker(new launch_manager_thread(*this)),
+ _config_lock(new mutex),
+ _going_down(new graceful_array),
+ _zombie_lock(new mutex),
+ _our_kids(new graceful_array),
+ _scamp_lock(new mutex),
+ _stop_launching(false),
+ _startup_time(new time_stamp(STARTUP_APPS_DELAY_PERIOD)),
+ _procs(new process_control),
+ _gag_exclusions(new string_set),
+ _tracking_exclusions(new string_set)
+{
+// FUNCDEF("constructor");
+
+ // start the application checking thread.
+ _checker->start(NIL);
+
+ _checker->reschedule(200); // make it start pretty quickly.
+}
+
+launch_manager::~launch_manager()
+{
+ FUNCDEF("destructor");
+ stop_everything();
+
+ WHACK(_checker);
+ WHACK(_going_down);
+ WHACK(_our_kids);
+ WHACK(_scamp_lock);
+ WHACK(_zombie_lock);
+ WHACK(_config_lock);
+ WHACK(_startup_time);
+ WHACK(_procs);
+ WHACK(_gag_exclusions);
+ WHACK(_tracking_exclusions);
+ LOG("launch_manager is now stopped.");
+}
+
+void launch_manager::add_gag_exclusion(const astring &exclusion)
+{ *_gag_exclusions += exclusion; }
+
+void launch_manager::add_tracking_exclusion(const astring &exclusion)
+{ *_tracking_exclusions += exclusion; }
+
+const char *launch_manager::outcome_name(const outcome &to_name)
+{
+ switch (to_name.value()) {
+ case FILE_NOT_FOUND: return "FILE_NOT_FOUND";
+ case NO_ANCHOR: return "NO_ANCHOR";
+ case NO_PRODUCT: return "NO_PRODUCT";
+ case NO_APPLICATION: return "NO_APPLICATION";
+ case BAD_PROGRAM: return "BAD_PROGRAM";
+ case LAUNCH_FAILED: return "LAUNCH_FAILED";
+ case NOT_RUNNING: return "NOT_RUNNING";
+ case FROZEN: return "FROZEN";
+ default: return common::outcome_name(to_name);
+ }
+}
+
+void launch_manager::stop_everything()
+{
+ _stop_launching = true; // at least deny any connected clients.
+ stop_all_kids(); // shut down all programs that we started.
+ _checker->stop(); // stop our thread.
+}
+
+void launch_manager::stop_all_kids()
+{
+ FUNCDEF("stop_all_kids");
+ _stop_launching = true; // set this for good measure to keep clients out.
+ LOG("zapping any active sub-processes prior to exit.");
+
+ // now we wait for the process closures to take effect. we are relying on
+ // the graceful shutdown devolving to a process zap and the timing is
+ // rooted around that assumption.
+
+ for (int lev = 100; lev >= 0; lev--) {
+ // loop from our highest level to our lowest for the shutdown.
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(a_sprintf("level %d", lev));
+#endif
+ bool zapped_any = false;
+ {
+ // this shuts down all the child processes we've started at this level.
+ LOCK_KIDS; // lock within this scope.
+ for (int i = _our_kids->length() - 1; i >= 0; i--) {
+ // now check each record and see if it's at the appropriate level.
+ graceful_record &grace = (*_our_kids)[i];
+ if (lev == grace._level) {
+ // start a graceful shutdown.
+ zap_process(grace._product, grace._app_name, true);
+ // remove it from our list.
+ _our_kids->zap(i, i);
+ zapped_any = true; // set our flag.
+ }
+ }
+ }
+ int num_dying = 1; // go into the loop once at least.
+
+#ifdef DEBUG_PROCESS_MANAGER
+ time_stamp next_print(4 * SECOND_ms);
+#endif
+
+ while (num_dying) {
+#ifdef DEBUG_PROCESS_MANAGER
+ if (time_stamp() >= next_print) {
+ LOG("waiting...");
+ next_print.reset(4 * SECOND_ms);
+ }
+#endif
+
+ // while there are any pending process zaps, we will wait here. this
+ // will hose us but good if the processes aren't eventually cleared up,
+ // but that shouldn't happen.
+
+ {
+ LOCK_ZOMBIES;
+ num_dying = _going_down->length();
+ }
+
+ if (!num_dying) break; // jump out of loop.
+
+ _checker->reschedule(0); // make thread check as soon as possible.
+
+ time_control::sleep_ms(40);
+ }
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG("done waiting...");
+#endif
+ }
+}
+
+void launch_manager::launch_startup_apps()
+{
+ FUNCDEF("launch_startup_apps");
+
+ // read the startup section.
+ string_table startup_info;
+ {
+ LOCK_CONFIG;
+ if (!_configs.find_section(_configs.STARTUP_SECTION(), startup_info)) {
+ // if there's no startup section, we do nothing right now.
+ LOG("the startup section was not found!");
+ return;
+ }
+ }
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("table has: ") + startup_info.text_form());
+#endif
+
+ for (int i = 0; i < startup_info.symbols(); i++) {
+ astring app = startup_info.name(i);
+ if (app.equal_to(configured_applications::STARTUP_APP_NAME())) continue;
+ // skip bogus name that keeps the section present.
+ astring info = startup_info[i];
+ LOG(astring("launching application ") + app + "...");
+ // parse the items that are in the entry for this program.
+ astring product, parms;
+ bool one_shot;
+ if (!configured_applications::parse_startup_entry(info, product, parms,
+ one_shot)) {
+ LOG("the startup entry was not malformed; we could not parse it!");
+ continue;
+ }
+
+ LOG(app + a_sprintf(" is %ssingle shot.", one_shot? "" : "not "));
+
+ // now try to send the program off on its way.
+ launch_now(product, app, parms);
+
+ if (one_shot) {
+ // it's only supposed to be started once, so now toss out the entry.
+ remove_from_startup(product, app);
+ }
+ }
+}
+
+outcome launch_manager::launch_now(const astring &product,
+ const astring &app_name, const astring ¶meters)
+{
+ FUNCDEF("launch_now");
+ LOG(astring("product \"") + product + "\", application \"" + app_name
+ + (parameters.length() ? astring("\"")
+ : astring("\", parms=") + parameters));
+
+ if (_stop_launching) {
+ // if the application is one of the exceptions to the gag rule, then
+ // we will still launch it. otherwise, we'll ignore this request.
+ if (_gag_exclusions->member(app_name)) {
+ // this is one of the apps that can still be launched when gagged.
+ } else {
+ LOG(astring("application \"") + app_name + "\" cannot be launched;"
+ + parser_bits::platform_eol_to_chars() + "launching services have been "
+ "shut down.");
+ return LAUNCH_FAILED;
+ }
+ }
+
+ astring entry_found;
+ int level;
+ {
+ LOCK_CONFIG;
+
+ // get the specific entry for the program they want.
+ entry_found = _configs.find_program(product, app_name, level);
+ if (!entry_found) {
+ if (!_configs.product_exists(product)) {
+ // return more specific error for missing product.
+ COMPLAIN_PRODUCT;
+ return NO_PRODUCT;
+ }
+ COMPLAIN_APPLICATION;
+ return NO_APPLICATION;
+ }
+ }
+
+ filename existence_check(entry_found);
+ if (!existence_check.exists()) {
+ LOG(astring("file or path wasn't found for ") + entry_found + ".");
+ return FILE_NOT_FOUND;
+ }
+
+ basis::un_int kid = 0;
+ int ret = launch_process::run(entry_found, parameters,
+ launch_process::RETURN_IMMEDIATELY | launch_process::HIDE_APP_WINDOW, kid);
+ if (!ret) {
+ // hey, it worked! so now make sure we track its lifetime...
+
+ if (_tracking_exclusions->member(app_name)) {
+ // this is one of the apps that we don't track. if it's still
+ // running when we're shutting down, it's not our problem.
+ return OKAY;
+ }
+
+ if (kid) {
+ // we were told the specific id for the process we started.
+ LOCK_KIDS;
+ LOG(a_sprintf("adding given process id %d for app %s at level %d.",
+ kid, app_name.s(), level));
+ graceful_record to_add(kid, product, app_name, level);
+ *_our_kids += to_add;
+ return OKAY;
+ }
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG("was not told child process id!!!");
+#endif
+ // we weren't told the process id; let's see if we can search for it.
+ int_set pids;
+ time_stamp give_it_up(MAXIMUM_INITIAL_APP_WAIT);
+ while (give_it_up > time_stamp()) {
+ // find the process id for the program we just started.
+ if (find_process(app_name, pids)) break;
+ time_control::sleep_ms(10); // pause to see if we can find it yet.
+ }
+
+ if (time_stamp() >= give_it_up) {
+ // we could not launch it for some reason, or our querier has failed.
+ // however, as far as we know, it launched successfully. if the id is
+ // missing, then that's not really our fault. we will just not be able
+ // to track the program, possibly because it's already gone.
+ LOG(astring("no process found for product \"") + product
+ + "\", application \"" + app_name + "\"; not adding "
+ "tracking record.");
+ return OKAY;
+ }
+
+ LOCK_KIDS;
+
+ // add all the processes we found under that name.
+ for (int i = 0; i < pids.elements(); i++) {
+ LOG(a_sprintf("adding process id %d for app %s at level %d.",
+ pids[i], app_name.s(), level));
+ graceful_record to_add(pids[i], product, app_name, level);
+ *_our_kids += to_add;
+ }
+ return OKAY;
+ }
+
+ // if we reached here, things are not good. we must not have been able to
+ // start the process.
+
+#ifdef __WIN32__
+ if (ret == NO_ERROR) return OKAY; // how would that happen?
+ else if ( (ret == ERROR_FILE_NOT_FOUND) || (ret == ERROR_PATH_NOT_FOUND) ) {
+ LOG(astring("file or path wasn't found for ") + app_name + ".");
+ return FILE_NOT_FOUND;
+ } else if (ret == ERROR_BAD_FORMAT) {
+ LOG(astring(app_name) + " was not in EXE format.");
+ return BAD_PROGRAM;
+ } else {
+ LOG(astring("there was an unknown error while trying to run ")
+ + app_name + "; the error is:" + parser_bits::platform_eol_to_chars()
+ + critical_events::system_error_text(ret));
+ return LAUNCH_FAILED;
+ }
+#else
+ LOG(astring("error ") + critical_events::system_error_text(ret)
+ + " occurred attempting to run: " + app_name + " " + parameters);
+ return FILE_NOT_FOUND;
+#endif
+}
+
+outcome launch_manager::launch_at_startup(const astring &product,
+ const astring &app_name, const astring ¶meters, int one_shot)
+{
+ FUNCDEF("launch_at_startup");
+ LOCK_CONFIG; // this whole function modifies the config file.
+
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("product \"") + product + "\", application \"" + app_name
+ + (one_shot? astring("\", OneShot") : astring("\", MultiUse")));
+#endif
+
+ // get the specific entry for the program they want.
+ int level;
+ astring entry_found = _configs.find_program(product, app_name, level);
+ if (!entry_found) {
+ if (!_configs.product_exists(product)) {
+ // return more specific error for missing product.
+ COMPLAIN_PRODUCT;
+ return NO_PRODUCT;
+ }
+ COMPLAIN_APPLICATION;
+ return NO_APPLICATION;
+ }
+
+ if (!_configs.add_startup_entry(product, app_name, parameters, one_shot)) {
+ // most likely problem is that it was already there.
+ return EXISTING;
+ }
+
+ return OKAY;
+}
+
+outcome launch_manager::remove_from_startup(const astring &product,
+ const astring &app_name)
+{
+ FUNCDEF("remove_from_startup");
+ LOCK_CONFIG; // this whole function modifies the config file.
+
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("product \"") + product + "\", application \"" + app_name + "\"");
+#endif
+
+ // get the specific entry for the program they want.
+ int level;
+ astring entry_found = _configs.find_program(product, app_name, level);
+ if (!entry_found) {
+ if (!_configs.product_exists(product)) {
+ // return more specific error for missing product.
+ COMPLAIN_PRODUCT;
+ return NO_PRODUCT;
+ }
+ COMPLAIN_APPLICATION;
+ return NO_APPLICATION;
+ }
+//hmmm: is product required for this for real?
+
+ if (!_configs.remove_startup_entry(product, app_name)) {
+ // the entry couldn't be removed, probably doesn't exist.
+ return NO_APPLICATION;
+ }
+
+ return OKAY;
+}
+
+outcome launch_manager::query_application(const astring &product,
+ const astring &app_name)
+{
+ FUNCDEF("query_application");
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("product \"") + product + "\", application \"" + app_name + "\"");
+#endif
+
+ {
+ LOCK_CONFIG;
+ // get the specific entry for the program they want.
+ int level;
+ astring entry_found = _configs.find_program(product, app_name, level);
+ if (!entry_found) {
+ if (!_configs.product_exists(product)) {
+ // return more specific error for missing product.
+ COMPLAIN_PRODUCT;
+ return NO_PRODUCT;
+ }
+ COMPLAIN_APPLICATION;
+ return NO_APPLICATION;
+ }
+ }
+
+ // now seek that program name in the current list of processes.
+ astring program_name(app_name);
+ program_name.to_lower();
+
+ int_set pids;
+ if (!find_process(app_name, pids))
+ return NOT_RUNNING;
+ return OKAY;
+}
+
+outcome launch_manager::start_graceful_close(const astring &product,
+ const astring &app_name)
+{
+ FUNCDEF("start_graceful_close");
+//hmmm: record this app as one we need to watch.
+
+ {
+ // find that program name to make sure it's not already shutting down.
+ LOCK_ZOMBIES;
+
+ for (int i = _going_down->length() - 1; i >= 0; i--) {
+ graceful_record &grace = (*_going_down)[i];
+ if (grace._app_name.iequals(app_name)) {
+ return OKAY;
+ }
+ }
+ }
+
+ if (!hoople_service::close_application(app_name))
+ return NO_ANCHOR;
+
+ int_set pids;
+ if (!find_process(app_name, pids)) {
+ LOG(astring("Failed to find process id for [") + app_name
+ + astring("], assuming it has already exited."));
+ return OKAY;
+ }
+
+ {
+ // add all the process ids, just in case there were multiple instances
+ // of the application somehow.
+ LOCK_ZOMBIES;
+ for (int i = 0; i < pids.elements(); i++) {
+ graceful_record to_add(pids[i], product, app_name);
+ *_going_down += to_add;
+ }
+ }
+
+ return OKAY;
+}
+
+bool launch_manager::get_processes(process_entry_array &processes)
+{
+ FUNCDEF("get_processes");
+ if (!_procs->query_processes(processes)) {
+ LOG("failed to query processes!");
+ return false;
+ }
+ return true;
+}
+
+bool launch_manager::find_process(const astring &app_name_in, int_set &pids)
+{
+// FUNCDEF("find_process");
+ pids.clear();
+ process_entry_array processes;
+ if (!get_processes(processes)) return false;
+ return process_control::find_process_in_list(processes, app_name_in, pids);
+}
+
+outcome launch_manager::zap_process(const astring &product,
+ const astring &app_name_key, bool graceful)
+{
+ FUNCDEF("zap_process");
+
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("product \"") + product + "\", application \"" + app_name_key
+ + (graceful? "\", Graceful Close" : "\", Brutal Close"));
+#endif
+
+ if (_tracking_exclusions->member(app_name_key)) {
+ // the non-tracked applications are never reported as running since they're
+ // not allowed to be zapped anyhow.
+ return NOT_RUNNING;
+ }
+
+ // use the real program name from here onward.
+ astring app_name;
+ {
+ LOCK_CONFIG;
+
+ // get the specific entry for the program they want.
+ int level;
+ app_name = _configs.find_program(product, app_name_key, level);
+ if (!app_name) {
+ if (!_configs.product_exists(product)) {
+ // return more specific error for missing product.
+ COMPLAIN_PRODUCT;
+ return NO_PRODUCT;
+ }
+ COMPLAIN_APPLICATION;
+ return NO_APPLICATION;
+ }
+ // truncate the directory and just use the base.
+ app_name = filename(app_name).basename();
+ }
+
+ // if they want a graceful close, start by trying that. if it appears to
+ // have succeeded, then we exit and let the thread take care of ensuring
+ // the app goes down.
+ outcome to_return = NOT_RUNNING;
+ if (graceful)
+ to_return = start_graceful_close(product, app_name);
+ if (to_return == OKAY)
+ return OKAY; // maybe finished the close.
+
+ // they want a harsh close of this application or the graceful close failed.
+ astring program_name(app_name);
+ int_set pids;
+ if (!find_process(program_name, pids)) {
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(program_name + " process was not running.")
+#endif
+ return NOT_RUNNING;
+ }
+
+ // search for the application in the process list.
+ bool failed = false;
+ for (int i = 0; i < pids.elements(); i++) {
+ bool ret = _procs->zap_process(pids[i]);
+ if (ret) {
+ LOG(astring(astring::SPRINTF, "Killed process %d [",
+ pids[i]) + program_name + astring("]"));
+ } else {
+ LOG(astring(astring::SPRINTF, "Failed to zap process %d [",
+ pids[i]) + program_name + astring("]"));
+ }
+ if (!ret) failed = true;
+ }
+// kind of a bizarre return, but whatever. it really should be some
+// failure result since the zap failed.
+ return failed? ACCESS_DENIED : OKAY;
+}
+
+outcome launch_manager::shut_down_launching_services(const astring &secret_word)
+{
+ FUNCDEF("shut_down_launching_services");
+ LOG("checking secret word...");
+//hmmm: changing the secret word here.
+ if (secret_word.equal_to("MarblesAreRoundishYo")) {
+ LOG("it's correct; ending launch capabilities.");
+ } else {
+ LOG("the secret word is wrong. continuing normal operation.");
+ return BAD_PROGRAM;
+ }
+ _stop_launching = true;
+ return OKAY;
+}
+
+outcome launch_manager::reenable_launching_services(const astring &secret_word)
+{
+ FUNCDEF("reenable_launching_services");
+ LOG("checking secret word...");
+ if (secret_word.equal_to("MarblesAreRoundishYo")) {
+ LOG("it's correct; resuming launch capabilities.");
+ } else {
+ LOG("the secret word is wrong. continuing with prior mode.");
+ return BAD_PROGRAM;
+ }
+ _stop_launching = false;
+ return OKAY;
+}
+
+#define GET_PROCESSES \
+ if (!retrieved_processes) { \
+ retrieved_processes = true; \
+ if (!get_processes(processes)) { \
+ LOG("failed to retrieve process list from OS!"); \
+ return; /* badness. */ \
+ } \
+ }
+
+void launch_manager::push_timed_activities(process_entry_array &processes)
+{
+ FUNCDEF("push_timed_activities");
+
+ // make sure we started the applications that were slated for execution at
+ // system startup time. we wait on this until the first thread activation
+ // to give other processes some breathing room right at startup time.
+ // also, it then doesn't block the main service thread.
+ if (!_started_initial_apps && (*_startup_time <= time_stamp()) ) {
+ // launch all the apps that are listed for system startup.
+ LOG("starting up the tasks registered for system initiation.");
+ launch_startup_apps();
+ _started_initial_apps = true;
+ }
+
+ if (!_started_initial_apps) {
+ _checker->reschedule(200);
+ // keep hitting this function until we know we can relax since the
+ // startup apps have been sent off.
+ }
+
+ bool retrieved_processes = false;
+
+ {
+ // loop over the death records we've got and check on the soon to be gone.
+ LOCK_ZOMBIES;
+
+ for (int i = _going_down->length() - 1; i >= 0; i--) {
+ graceful_record &grace = (*_going_down)[i];
+
+ GET_PROCESSES; // load them if they hadn't been.
+
+ int_set pids;
+ if (!process_control::find_process_in_list(processes, grace._app_name,
+ pids)) {
+ // the app can't be found as running, so whack the record for it.
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("cannot find app ") + grace._app_name
+ + " as running still; removing its dying record");
+#endif
+ _going_down->zap(i, i);
+ continue;
+ }
+ if (!pids.member(grace._pid)) {
+ // that particular instance exited on its own, so whack the record.
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("app ") + grace._app_name
+ + " exited on its own; removing its dying record");
+#endif
+ _going_down->zap(i, i);
+ continue;
+ }
+
+ if (grace._started <= time_stamp(-GRACEFUL_SLACK)) {
+ // time to force it.
+ LOG(astring("Application ") + grace._app_name + " is unresponsive to "
+ "the graceful shutdown request. Now zapping it instead.");
+ if (!_procs->zap_process(grace._pid))
+ LOG("Devolved graceful shutdown failed as zap_process also.");
+ _going_down->zap(i, i);
+ continue;
+ }
+
+ // it's not time to dump this one yet, so keep looking at others.
+ }
+ }
+
+ {
+ // now loop over the list of our active kids and make sure that they are
+ // all still running. if they aren't, then toss the record out.
+ LOCK_KIDS;
+
+ for (int i = _our_kids->length() - 1; i >= 0; i--) {
+ graceful_record &grace = (*_our_kids)[i];
+
+ GET_PROCESSES; // load them if they hadn't been.
+
+ int_set pids;
+ if (!process_control::find_process_in_list(processes, grace._app_name,
+ pids)) {
+ // the app can't be found as running, so whack the record for it.
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("cannot find kid ") + grace._app_name
+ + " as still running; removing its normal record");
+#endif
+ _our_kids->zap(i, i);
+ continue;
+ }
+ if (!pids.member(grace._pid)) {
+ // that particular instance exited on its own, so whack the record.
+#ifdef DEBUG_PROCESS_MANAGER
+ LOG(astring("kid ") + grace._app_name
+ + " exited on its own; removing its normal record");
+#endif
+ _our_kids->zap(i, i);
+ continue;
+ }
+
+ // this kid is still going, so keep its record.
+ }
+ }
+}
+
+} //namespace.
+