libmoost
/home/mhx/git/github/libmoost/include/moost/process/service.hpp
Go to the documentation of this file.
00001 /* vim:set ts=3 sw=3 sts=3 et: */
00028 #ifndef FM_LAST_MOOST_PROCESS_SERVICE_H_
00029 #define FM_LAST_MOOST_PROCESS_SERVICE_H_
00030 
00154 #include <stdexcept>
00155 #include <cassert>
00156 
00157 #include <boost/filesystem/path.hpp>
00158 #include <boost/shared_ptr.hpp>
00159 #include <boost/scoped_ptr.hpp>
00160 #include <boost/noncopyable.hpp>
00161 #include <boost/function.hpp>
00162 
00163 #include "daemon.hpp"
00164 #include "sleeper.hpp"
00165 #include "pidfile.hpp"
00166 #include "quit_handler.hpp"
00167 #include "../logging/class_logger.hpp"
00168 #include "../logging/standard_console.hpp"
00169 #include "../service/remote_shell.h"
00170 #include "../service/appender.h"
00171 
00172 // XXX: This is just a workaround until hopefully some day show_help() and get_prompt() are const...
00173 // #define MPS_FM303_SHELL_CONST const
00174 #define MPS_FM303_SHELL_CONST
00175 
00176 namespace moost { namespace process {
00177 
00178 class NoConsoleLoggerPolicy
00179 {
00180 public:
00181    void create()
00182    {
00183    }
00184 
00185    bool set_log_level(const std::string&)
00186    {
00187       return false;
00188    }
00189 
00190    std::string show_help() const
00191    {
00192       return "";
00193    }
00194 
00195    bool handle_command(std::string&, const std::string&, const std::string&)
00196    {
00197       return false;
00198    }
00199 
00200    void enable()
00201    {
00202    }
00203 
00204    void disable()
00205    {
00206    }
00207 
00208    void log(const std::string&)
00209    {
00210    }
00211 };
00212 
00213 class MoostStandardConsoleLoggerPolicy
00214 {
00215 public:
00216    MoostStandardConsoleLoggerPolicy()
00217      : m_disabled(false)
00218      , m_invalid_log_level(new log4cxx::Level(-1, LOG4CXX_STR("INVALID"), 7 ))
00219    {
00220    }
00221 
00222    void create()
00223    {
00224       if (!m_console)
00225       {
00226          m_console.reset(new moost::logging::standard_console);
00227          set_log_level(m_log_level);
00228       }
00229    }
00230 
00231    bool set_log_level(const std::string& level)
00232    {
00233       if (m_console)
00234       {
00235          if (level.empty())
00236          {
00237             // default to 'info' level
00238             return m_console->setThreshold(log4cxx::Level::getInfo());
00239          }
00240 
00241          return m_console->setThreshold(level);
00242       }
00243 
00244       // this shouldn't be here; but we're safe as long as
00245       // this code and the logging framework are both in moost
00246 
00247       log4cxx::LevelPtr new_level = log4cxx::Level::toLevel(level, m_invalid_log_level);
00248 
00249       if (new_level == m_invalid_log_level)
00250       {
00251          return false;
00252       }
00253 
00254       m_log_level = level;
00255 
00256       return true;
00257    }
00258 
00259    std::string show_help() const
00260    {
00261       if (m_console && !m_disabled)
00262       {
00263          return "- level [off|fatal|error|warn|info|debug|trace|all]\n"
00264                 "      set console log level [default: info]\n";
00265       }
00266 
00267       return "";
00268    }
00269 
00270    bool handle_command(std::string& rv, const std::string& cmd, const std::string& args)
00271    {
00272       if (m_console && !m_disabled)
00273       {
00274          if (cmd == "level")
00275          {
00276             if (set_log_level(args))
00277             {
00278                std::string level;
00279                m_console->getThreshold(level);
00280                rv = "log level set to [" + level + "]\n";
00281             }
00282             else
00283             {
00284                rv = "unknown log level " + args + "\n";
00285             }
00286 
00287             return true;
00288          }
00289       }
00290 
00291       return false;
00292    }
00293 
00294    void enable()
00295    {
00296       if (m_console && m_disabled)
00297       {
00298          m_console->enable();
00299          m_disabled = false;
00300       }
00301    }
00302 
00303    void disable()
00304    {
00305       if (m_console && !m_disabled)
00306       {
00307          m_console->disable();
00308          m_disabled = true;
00309       }
00310    }
00311 
00312    void log(const std::string& msg)
00313    {
00314       MLOG_NAMED_INFO("moost::process::service", msg);
00315    }
00316 
00317 private:
00318    bool m_disabled;
00319    boost::scoped_ptr<moost::logging::standard_console> m_console;
00320    std::string m_log_level;
00321    log4cxx::LevelPtr m_invalid_log_level;
00322 };
00323 
00324 template<class ServiceT, class ConsoleLoggerPolicy = MoostStandardConsoleLoggerPolicy>
00325 class service : public boost::noncopyable
00326 {
00327 private:
00328    struct noop_child_init_func
00329    {
00330       bool operator()()
00331       {
00332          return false;
00333       }
00334    };
00335 
00336    struct default_parent_exit_func
00337    {
00338       void operator()(pid_t)
00339       {
00340          exit(0);
00341       }
00342    };
00343 
00344    class service_wrapper : public boost::noncopyable
00345    {
00346    private:
00347       boost::shared_ptr<typename ServiceT::HandlerType> checked_handler()
00348       {
00349          boost::shared_ptr<typename ServiceT::HandlerType> hdl(m_service->handler());
00350 
00351          if (!hdl)
00352          {
00353             throw std::runtime_error("got null pointer for service handler");
00354          }
00355 
00356          return hdl;
00357       }
00358 
00359       boost::shared_ptr<const typename ServiceT::HandlerType> checked_handler() const
00360       {
00361          boost::shared_ptr<const typename ServiceT::HandlerType> hdl(m_service->handler());
00362 
00363          if (!hdl)
00364          {
00365             throw std::runtime_error("got null pointer for service handler");
00366          }
00367 
00368          return hdl;
00369       }
00370 
00371    public:
00372       service_wrapper(boost::shared_ptr<ServiceT> service)
00373         : m_service(service)
00374       {
00375       }
00376 
00377       bool set_log_level(const std::string& level)
00378       {
00379          return m_logger.set_log_level(level);
00380       }
00381 
00382       std::string get_prompt() MPS_FM303_SHELL_CONST
00383       {
00384          return checked_handler()->get_prompt();
00385       }
00386 
00387       std::string show_help() MPS_FM303_SHELL_CONST
00388       {
00389          std::string help;
00390 
00391          help += m_logger.show_help();
00392          help += checked_handler()->show_help();
00393 
00394          return help;
00395       }
00396 
00397       bool handle_command(std::string& rv, const std::string& cmd, const std::string& args)
00398       {
00399          if (m_logger.handle_command(rv, cmd, args))
00400          {
00401             return true;
00402          }
00403 
00404          return checked_handler()->handle_command(rv, cmd, args);
00405       }
00406 
00407       std::string name() const
00408       {
00409          return m_service->name();
00410       }
00411 
00412       void start()
00413       {
00414          m_logger.create();
00415 
00416          m_service->start();
00417       }
00418 
00419       void disable_logger()
00420       {
00421          m_logger.disable();
00422       }
00423 
00424       void enable_logger()
00425       {
00426          m_logger.enable();
00427       }
00428 
00429       void stop()
00430       {
00431          m_service->stop();
00432       }
00433 
00434       boost::shared_ptr<ServiceT> get_service()
00435       {
00436          return m_service;
00437       }
00438 
00439       void log(const std::string& msg)
00440       {
00441          m_logger.log(msg);
00442       }
00443 
00444    private:
00445       boost::shared_ptr<ServiceT> m_service;
00446       ConsoleLoggerPolicy m_logger;
00447    };
00448 
00449    struct enable_logger_func
00450    {
00451    public:
00452       enable_logger_func(service_wrapper& svc)
00453         : m_svc(svc)
00454       {
00455       }
00456 
00457       void operator()()
00458       {
00459          m_svc.enable_logger();
00460       }
00461 
00462    private:
00463       service_wrapper& m_svc;
00464    };
00465 
00466 public:
00467    service(boost::shared_ptr<ServiceT> service)
00468      : m_svc(service)
00469      , m_started(false)
00470      , m_daemonised(false)
00471      , m_shell_port(0)
00472      , m_default_stdout_state(true)
00473      , m_default_stderr_state(true)
00474      , m_child_init_func(noop_child_init_func())
00475      , m_parent_exit_func(default_parent_exit_func())
00476    {
00477    }
00478 
00479    ~service()
00480    {
00481       if (m_pidfile)
00482       {
00483          m_pidfile->remove();
00484          m_pidfile.reset();
00485       }
00486    }
00487 
00488    bool set_log_level(const std::string& log_level)
00489    {
00490       return m_svc.set_log_level(log_level);
00491    }
00492 
00493    std::string name() const
00494    {
00495       return m_svc.name();
00496    }
00497 
00498    void set_shell_port(unsigned short shell_port)
00499    {
00500       m_shell_port = shell_port;
00501    }
00502 
00503    void set_appender_factory(moost::service::appender_factory_ptr factory)
00504    {
00505       m_app_factory = factory;
00506    }
00507 
00508    void set_default_stdout_state(bool enabled)
00509    {
00510       m_default_stdout_state = enabled;
00511    }
00512 
00513    void set_default_stderr_state(bool enabled)
00514    {
00515       m_default_stderr_state = enabled;
00516    }
00517 
00518    void set_child_init_func(boost::function0<bool> child_init_func)
00519    {
00520       m_child_init_func = child_init_func;
00521    }
00522 
00523    void set_parent_exit_func(boost::function1<void, pid_t> parent_exit_func)
00524    {
00525       m_parent_exit_func = parent_exit_func;
00526    }
00527 
00528    void run(bool daemonise = false)
00529    {
00530       if (daemonise)
00531       {
00532          moost::process::daemon d(false, m_child_init_func);
00533 
00534          m_pidfile.reset(m_pidfile_name.empty() ? new moost::process::pidfile(m_svc.name(), moost::process::pidfile::get_default_rundir().string())
00535                                                 : new moost::process::pidfile(m_pidfile_name));
00536 
00537          if (d.is_parent())
00538          {
00539             pid_t child_pid = d.get_pid();
00540 
00541             if (!m_pidfile->create(child_pid))
00542             {
00543                throw std::runtime_error("failed to create pid file");
00544             }
00545 
00546             m_parent_exit_func(child_pid);
00547 
00548             return;
00549          }
00550 
00551          m_daemonised = true;
00552       }
00553       else
00554       {
00555          m_child_init_func();
00556       }
00557 
00558       moost::process::quit_handler::set(boost::bind(&moost::process::service<ServiceT, ConsoleLoggerPolicy>::quit_handler, this));
00559 
00560       m_svc.start();
00561 
00562       m_started = true;
00563 
00564       if (daemonise && !m_shell_port)
00565       {
00566          m_svc.disable_logger();
00567          m_sleeper.sleep();
00568       }
00569       else
00570       {
00571          // Create our io_service object and preload it with a function
00572          // object that will disable the standard_console logger. This
00573          // will be executed as soon as the io_service starts its main
00574          // event loop, i.e. when the local shell has taken over control.
00575 
00576          boost::shared_ptr<boost::asio::io_service> ios(new boost::asio::io_service);
00577 
00578          ios->post(boost::bind(&service_wrapper::disable_logger, &m_svc));
00579 
00580          m_remote_shell.reset(new remote_shell_t(m_svc, ios));
00581 
00582          if (m_app_factory)
00583          {
00584             m_remote_shell->set_appender_factory(m_app_factory);
00585          }
00586 
00587          m_remote_shell->set_default_stdout_state(m_default_stdout_state);
00588          m_remote_shell->set_default_stderr_state(m_default_stderr_state);
00589 
00590          if (m_shell_port)
00591          {
00592             m_remote_shell->set_listen_port(m_shell_port);
00593          }
00594 
00595          if (!daemonise)
00596          {
00597             // Enable a local shell if we're not daemonising. The pre shutdown
00598             // function object ensures that the standard_console has taken over
00599             // logging from the local shell before the local shell quits.
00600 
00601             m_remote_shell->enable_local_shell();
00602             m_remote_shell->set_pre_shutdown_function(enable_logger_func(m_svc));
00603          }
00604 
00605          m_remote_shell->run();
00606       }
00607 
00608       m_started = false;
00609 
00610       m_svc.stop();
00611    }
00612 
00613    void quit(const std::string& msg = "")
00614    {
00615       if (m_started)
00616       {
00617          if (m_remote_shell)
00618          {
00619             m_remote_shell->stop(msg);
00620          }
00621 
00622          m_sleeper.awaken();
00623 
00624          m_started = false;
00625       }
00626    }
00627 
00628    void set_pidfile(const std::string& pidfile)
00629    {
00630       boost::filesystem::path p(pidfile);
00631 
00632       if (!p.has_root_directory())
00633       {
00634          throw std::runtime_error("require absolute path for pid file");
00635       }
00636 
00637       m_pidfile_name = pidfile;
00638    }
00639 
00640    boost::shared_ptr<ServiceT> get_service()
00641    {
00642       return m_svc.get_service();
00643    }
00644 
00645 private:
00646    void quit_handler()
00647    {
00648       m_svc.log("Received quit signal, shutting down.");
00649       quit("quit signal received, shutting down");
00650    }
00651 
00652    typedef moost::service::remote_shell<service_wrapper> remote_shell_t;
00653 
00654    service_wrapper m_svc;
00655    std::string m_pidfile_name;
00656    bool m_started;
00657    bool m_daemonised;
00658    boost::scoped_ptr<moost::process::pidfile> m_pidfile;
00659    unsigned short m_shell_port;
00660    boost::scoped_ptr<remote_shell_t> m_remote_shell;
00661    moost::service::appender_factory_ptr m_app_factory;
00662    moost::process::sleeper m_sleeper;
00663    bool m_default_stdout_state;
00664    bool m_default_stderr_state;
00665    boost::function0<bool> m_child_init_func;
00666    boost::function1<void, pid_t> m_parent_exit_func;
00667 };
00668 
00669 } }
00670 
00671 #endif