* in the STARTING state when waiting for dependencies to start or for the exec() call in
* the forked child to complete and return a status.
*
- * Aquisition/release:
+ * Acquisition/release:
* ------------------
* Each service has a dependent-count ("required_by"). This starts at 0, adds 1 if the
* service has explicitly been started (i.e. "start_explicit" is true), and adds 1 for
* - desired state (which is manipulated by require/release operations)
*
* So a forced stop cannot occur until the service is not pinned started, for instance.
+ *
+ * Two-phase transition
+ * --------------------
+ * Transition between states occurs in two phases: propagation and execution. In the
+ * propagation phase, acquisition/release messages are processed, and desired state may be
+ * altered accordingly. Desired state of dependencies/dependents should not be examined in
+ * this phase, since it may change during the phase (i.e. its current value at any point
+ * may not reflect the true final value).
+ *
+ * In the execution phase, actions are taken to achieve the desired state. Actual state may
+ * transition according to the current and desired states.
*/
struct OnstartFlags {
const char *excDescription;
protected:
- ServiceLoadExc(std::string serviceName) noexcept
- : serviceName(serviceName)
+ ServiceLoadExc(std::string serviceName, const char *desc) noexcept
+ : serviceName(serviceName), excDescription(desc)
{
}
};
{
public:
ServiceNotFound(std::string serviceName) noexcept
- : ServiceLoadExc(serviceName)
+ : ServiceLoadExc(serviceName, "Service description not found.")
{
- excDescription = "Service description not found.";
}
};
{
public:
ServiceCyclicDependency(std::string serviceName) noexcept
- : ServiceLoadExc(serviceName)
+ : ServiceLoadExc(serviceName, "Has cyclic dependency.")
{
- excDescription = "Has cyclic dependency.";
}
};
class ServiceDescriptionExc : public ServiceLoadExc
{
public:
- std::string extraInfo;
-
ServiceDescriptionExc(std::string serviceName, std::string extraInfo) noexcept
- : ServiceLoadExc(serviceName), extraInfo(extraInfo)
+ : ServiceLoadExc(serviceName, extraInfo.c_str())
{
- excDescription = extraInfo.c_str();
}
};
-class ServiceRecord; // forward declaration
-class ServiceSet; // forward declaration
+class ServiceRecord;
+class ServiceSet;
+class base_process_service;
/* Service dependency record */
class ServiceDep
class ServiceChildWatcher : public EventLoop_t::child_proc_watcher_impl<ServiceChildWatcher>
{
public:
- ServiceRecord * service;
- rearm child_status(EventLoop_t &eloop, pid_t child, int status) noexcept;
+ base_process_service * service;
+ rearm status_change(EventLoop_t &eloop, pid_t child, int status) noexcept;
- ServiceChildWatcher(ServiceRecord * sr) noexcept : service(sr) { }
+ ServiceChildWatcher(base_process_service * sr) noexcept : service(sr) { }
};
class ServiceIoWatcher : public EventLoop_t::fd_watcher_impl<ServiceIoWatcher>
{
public:
- ServiceRecord * service;
+ base_process_service * service;
rearm fd_event(EventLoop_t &eloop, int fd, int flags) noexcept;
- ServiceIoWatcher(ServiceRecord * sr) noexcept : service(sr) { }
+ ServiceIoWatcher(base_process_service * sr) noexcept : service(sr) { }
};
class ServiceRecord
{
- friend class ServiceChildWatcher;
- friend class ServiceIoWatcher;
-
+ protected:
typedef std::string string;
string service_name;
bool pinned_started : 1;
bool waiting_for_deps : 1; // if STARTING, whether we are waiting for dependencies (inc console) to start
bool waiting_for_execstat : 1; // if we are waiting for exec status after fork()
- bool doing_recovery : 1; // if we are currently recovering a BGPROCESS (restarting process, while
- // holding STARTED service state)
bool start_explicit : 1; // whether we are are explictly required to be started
bool prop_require : 1; // require must be propagated
string socket_path; // path to the socket for socket-activation service
int socket_perms; // socket permissions ("mode")
uid_t socket_uid = -1; // socket user id or -1
- gid_t socket_gid = -1; // sockget group id or -1
+ gid_t socket_gid = -1; // socket group id or -1
// Implementation details
int socket_fd = -1; // For socket-activation services, this is the file
// descriptor for the socket.
- ServiceChildWatcher child_listener;
- ServiceIoWatcher child_status_listener;
// Data for use by ServiceSet
public:
ServiceRecord *next_in_stop_queue = nullptr;
- private:
+ protected:
// All dependents have stopped.
- void allDepsStopped();
+ virtual void all_deps_stopped() noexcept;
// Service has actually stopped (includes having all dependents
// reaching STOPPED state).
// dep_failed: whether failure is recorded due to a dependency failing
void failed_to_start(bool dep_failed = false) noexcept;
- // For process services, start the process, return true on success
- bool start_ps_process() noexcept;
- bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
-
void run_child_proc(const char * const *args, const char *logfile, bool on_console, int wpipefd,
int csfd) noexcept;
static void process_child_callback(EventLoop_t *loop, ServiceChildWatcher *w,
int revents) noexcept;
- void handle_exit_status() noexcept;
+ //virtual void handle_exit_status(int exit_status) noexcept;
// A dependency has reached STARTED state
void dependencyStarted() noexcept;
void allDepsStarted(bool haveConsole = false) noexcept;
-
- // Read the pid-file, return false on failure
- bool read_pid_file() noexcept;
-
+
+ // Do any post-dependency startup; return false on failure
+ virtual bool start_ps_process() noexcept;
+
// Open the activation socket, return false on failure
bool open_socket() noexcept;
-
+
// Check whether dependencies have started, and optionally ask them to start
bool startCheckDependencies(bool do_start) noexcept;
-
+
// Whether a STARTING service can immediately transition to STOPPED (as opposed to
// having to wait for it reach STARTED and then go through STOPPING).
bool can_interrupt_start() noexcept
return waiting_for_deps && ! force_stop;
}
- // Notify dependencies that we no longer need them,
- // (if this is actually the case).
- void notify_dependencies_stopped() noexcept;
-
// A dependent has reached STOPPED state
void dependentStopped() noexcept;
void releaseConsole() noexcept;
bool do_auto_restart() noexcept;
-
+
+ // Started state reached
+ bool process_started() noexcept;
+
public:
ServiceRecord(ServiceSet *set, string name)
: service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false),
pinned_stopped(false), pinned_started(false), waiting_for_deps(false),
- waiting_for_execstat(false), doing_recovery(false),
- start_explicit(false), prop_require(false), prop_release(false), prop_failure(false),
- force_stop(false), child_listener(this), child_status_listener(this)
+ waiting_for_execstat(false), start_explicit(false),
+ prop_require(false), prop_release(false), prop_failure(false),
+ force_stop(false)
{
service_set = set;
service_name = name;
this->service_type = service_type;
this->depends_on = std::move(*pdepends_on);
- program_name = command;
+ program_name = std::move(command);
exec_arg_parts = separate_args(program_name, command_offsets);
for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
}
}
- // TODO write a destructor
+ virtual ~ServiceRecord() noexcept
+ {
+ }
// begin transition from stopped to started state or vice versa depending on current and desired state
void execute_transition() noexcept;
void set_pid_file(string &&pid_file) noexcept
{
- this->pid_file = pid_file;
+ this->pid_file = std::move(pid_file);
}
void set_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid) noexcept
{
- this->socket_path = socket_path;
+ this->socket_path = std::move(socket_path);
this->socket_perms = socket_perms;
this->socket_uid = socket_uid;
this->socket_gid = socket_gid;
}
};
+class base_process_service : public ServiceRecord
+{
+ friend class ServiceChildWatcher;
+ friend class ServiceIoWatcher;
+
+ protected:
+ ServiceChildWatcher child_listener;
+ ServiceIoWatcher child_status_listener;
+
+ // start the process, return true on success
+ virtual bool start_ps_process() noexcept;
+ bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
+
+ virtual void all_deps_stopped() noexcept;
+ virtual void handle_exit_status(int exit_status) noexcept = 0;
+
+ public:
+ base_process_service(ServiceSet *sset, string name, ServiceType service_type, string &&command,
+ std::list<std::pair<unsigned,unsigned>> &command_offsets,
+ sr_list * pdepends_on, sr_list * pdepends_soft)
+ : ServiceRecord(sset, name, service_type, std::move(command), command_offsets,
+ pdepends_on, pdepends_soft), child_listener(this), child_status_listener(this)
+ {
+ }
+
+ ~base_process_service() noexcept
+ {
+ }
+};
+
+class process_service : public base_process_service
+{
+ // called when the process exits. The exit_status is the status value yielded by
+ // the "wait" system call.
+ virtual void handle_exit_status(int exit_status) noexcept override;
+
+ public:
+ process_service(ServiceSet *sset, string name, string &&command,
+ std::list<std::pair<unsigned,unsigned>> &command_offsets,
+ sr_list * pdepends_on, sr_list * pdepends_soft)
+ : base_process_service(sset, name, ServiceType::PROCESS, std::move(command), command_offsets,
+ pdepends_on, pdepends_soft)
+ {
+ }
+
+ ~process_service() noexcept
+ {
+ }
+};
+
+class bgproc_service : public base_process_service
+{
+ virtual void handle_exit_status(int exit_status) noexcept override;
+
+ bool doing_recovery : 1; // if we are currently recovering a BGPROCESS (restarting process, while
+ // holding STARTED service state)
+
+ // Read the pid-file, return false on failure
+ bool read_pid_file() noexcept;
+
+ public:
+ bgproc_service(ServiceSet *sset, string name, string &&command,
+ std::list<std::pair<unsigned,unsigned>> &command_offsets,
+ sr_list * pdepends_on, sr_list * pdepends_soft)
+ : base_process_service(sset, name, ServiceType::BGPROCESS, std::move(command), command_offsets,
+ pdepends_on, pdepends_soft)
+ {
+ doing_recovery = false;
+ }
+
+ ~bgproc_service() noexcept
+ {
+ }
+};
+
+class scripted_service : public base_process_service
+{
+ virtual void all_deps_stopped() noexcept override;
+ virtual void handle_exit_status(int exit_status) noexcept override;
+
+ public:
+ scripted_service(ServiceSet *sset, string name, string &&command,
+ std::list<std::pair<unsigned,unsigned>> &command_offsets,
+ sr_list * pdepends_on, sr_list * pdepends_soft)
+ : base_process_service(sset, name, ServiceType::SCRIPTED, std::move(command), command_offsets,
+ pdepends_on, pdepends_soft)
+ {
+ }
+
+ ~scripted_service() noexcept
+ {
+ }
+};
+
+
/*
* A ServiceSet, as the name suggests, manages a set of services.
*
void startService(const char *name);
// Locate an existing service record.
- ServiceRecord *findService(const std::string &name) noexcept;
+ ServiceRecord *find_service(const std::string &name) noexcept;
// Find a loaded service record, or load it if it is not loaded.
// Throws:
// std::bad_alloc on out-of-memory condition
ServiceRecord *loadService(const std::string &name)
{
- ServiceRecord *record = findService(name);
+ ServiceRecord *record = find_service(name);
if (record == nullptr) {
record = loadServiceRecord(name.c_str());
}
}
// Set the console queue tail (returns previous tail)
- ServiceRecord * consoleQueueTail(ServiceRecord * newTail) noexcept
+ ServiceRecord * append_console_queue(ServiceRecord * newTail) noexcept
{
auto prev_tail = console_queue_tail;
console_queue_tail = newTail;