/* SUBSYSTEMRUNNER.hpp - helper for controlling execution of several dependant subsystems Copyright (C) Lumiera.org 2008, Hermann Vosseler 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 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef LUMIERA_SUBSYSTEMRUNNER_H #define LUMIERA_SUBSYSTEMRUNNER_H #include "lib/error.hpp" #include "lib/util.hpp" #include "common/subsys.hpp" #include "lib/sync.hpp" #include #include namespace lumiera { using std::tr1::bind; using std::tr1::function; using std::tr1::placeholders::_1; using std::vector; using util::cStr; using util::isnil; using util::and_all; using util::for_each; using util::removeall; namespace { function isRunning() { return bind (&Subsys::isRunning, _1); } } /***************************************************************************** * Implementation helper for managing execution of a collection of subsystems, * which may depend on one another and execute in parallel. Properties of the * subsystems are available through Subsys object refs, which act as handle. * In this context, "Subsystem" is an \em abstraction and doesn't necessarily * correspond to a single component, interface or plugin. It may well be a * complete layer of the application (e.g. the GUI). * * \par Protocol of operation * The SubsystemRunner is to be configured with a lumiera::Option object first. * Then, primary subsystems are \link #maybeRun provided \endlink for eventual * startup, which may depend on conditions defined by the subsystem. When * a component is actually to be pulled up, all of its prerequisite subsystems * shall be started in advance. Problems while starting may result in throwing * an exception, which is \em not handled here and aborts the whole operation. * On startup, a signal slot is reserved for each subsystem to notify the * SubsystemRunner on termination. It is the liability of the subsystems to * ensure this signal is activated regardless of what actually causes the * termination; failure to do so may deadlock the SubsystemRunner. * * Usually, the startup process is conducted from one (main) thread, which enters * a blocking wait() after starting the subsystems. Awakened by some termination * signal from one of the subsystems, termination of any remaining subsystems * will be triggered. The wait() function returns after shutdown of all subsystems, * signalling an emergency exit (caused by an exception) with its return value. * * @todo implement an object monitor primitive based on a mutex and a condition. Inherit from this privately and create local instances of the embedded Lock class to activate the locking and wait/notify * @todo maybe use my refArray (see builder) to use Subsys& instead of Subsys* ?? * * @see lumiera::AppState * @see lumiera::Subsys * @see main.cpp */ class SubsystemRunner // : Sync { Option& opts_; volatile bool emergency_; vector running_; function start_, killIt_; public: SubsystemRunner (Option& opts) : opts_(opts) , emergency_(false) , start_(bind (&SubsystemRunner::triggerStartup, this,_1)) , killIt_(bind (&Subsys::triggerShutdown, _1)) { } void maybeRun (Subsys& susy) { //Lock guard (this); if (!susy.isRunning() && susy.shouldStart (opts_)) triggerStartup (&susy); if (susy.isRunning()) running_.push_back (&susy); } void shutdownAll () { //Lock guard (this); for_each (running_, killIt_); } bool wait () { //Lock(this).wait (&SubsystemRunner::allDead); return isEmergencyExit(); } bool isEmergencyExit () { return emergency_; } void triggerEmergency (bool cond) { emergency_ |= cond; } private: void triggerStartup (Subsys* susy) { ASSERT (susy); INFO (operate, "Starting subsystem \"%s\"", cStr(*susy)); for_each (susy->getPrerequisites(), start_); bool started = susy->start (opts_, bind (&SubsystemRunner::sigTerm, this, susy, _1)); if (started && !susy->isRunning()) { throw error::Logic("Subsystem "+string(*susy)+" failed to start"); } if (!and_all (susy->getPrerequisites(), isRunning() )) { susy->triggerShutdown(); throw error::Logic("Unable to start all prerequisites of Subsystem "+string(*susy)); } } void sigTerm (Subsys* susy, Error* problem) ///< called from subsystem on termination { ASSERT (susy); //Lock guard (this); triggerEmergency(problem); ERROR_IF (susy->isRunning(), lumiera, "Subsystem '%s' signals termination, " "without resetting running state", cStr(*susy)); removeall (running_, susy); shutdownAll(); //guard.notify(); } bool allDead () { if (isEmergencyExit()) ; //Lock(this).setTimeout(EMERGENCYTIMEOUT); return isnil (running_); // end wait if no running subsystem left } }; } // namespace lumiera #endif