// -*- C++ -*- //============================================================================= /** * @file Process_Manager.h * * @author Douglas C. Schmidt */ //============================================================================= #ifndef ACE_PROCESS_MANAGER_H #define ACE_PROCESS_MANAGER_H #include /**/ "ace/pre.h" #include /**/ "ace/ACE_export.h" #if !defined (ACE_LACKS_PRAGMA_ONCE) # pragma once #endif /* ACE_LACKS_PRAGMA_ONCE */ #include "ace/Process.h" #include "ace/Event_Handler.h" #include "ace/Time_Value.h" #if defined (ACE_HAS_THREADS) # include "ace/Recursive_Thread_Mutex.h" #endif /* ACE_HAS_THREADS */ ACE_BEGIN_VERSIONED_NAMESPACE_DECL class ACE_Reactor; /** * @class ACE_Process_Manager * * @brief Manages a group of processes. * * This class allows applications to control groups of processes, * similar to the way ACE_Thread_Manager controls groups of * threads. Naturally, it doesn't work at all on platforms, such * as VxWorks or pSoS, that don't support multiple processes. * There are two main ways of using ACE_Process_Manager, * depending on how involved you wish to be with the termination * of managed processes. If you want processes to simply * go away when they're finished, register the ACE_Process_Manager with * an ACE_Reactor that can handle notifications of child process exit: * @code * ACE_Process_Manager mgr; * // ... * mgr.open (100, ACE_Reactor::instance ()); * @endcode * In this usage scenario, the ACE_Process_Manager will clean up after any * processes that it spawns. (On Unix, this means executing a * wait(2) to collect the exit status and avoid zombie * processes; on Win32, it means closing the process and thread * HANDLEs that are created when CreateProcess is called.) * * @note When you register a ACE_Process_Manager with a * ACE_Reactor, the reactor's notification pipe is used to help reap the * available process exit statuses. Therefore, you must not use a * reactor whose notify pipe has been disabled. Here's the * sequence of steps used to reap the exit statuses in this case: * -# The ACE_Process_Manager registers a signal handler for * SIGCHLD. * -# The SIGCHLD handler, when invoked, uses the ACE_Reactor's * notify() method to inform the ACE_Reactor to wake up. * -# The ACE_Reactor calls the ACE_Process_Manager's * handle_input() method; this happens synchronously, not in * signal context. * -# The handle_input() method collects all available exit * statuses. * * If, on the other hand you want to wait "in line" to handle the * terminated process cleanup code, call one of the wait functions * whenever there might be managed processes that have exited. * * Note that in either case, ACE_Process_Manager allows you to * register an ACE_Event_Handler to be called when a specific * spawned process exits, or when any process without a specific * ACE_Event_Handler exits. When a process exits, the * appropriate ACE_Event_Handler's handle_input() method is called; the * ACE_HANDLE passed is either the process's HANDLE (on Windows), * or its pid cast to an ACE_HANDLE (on POSIX). * It is also possible to call the wait() functions even when the * ACE_Process_Manager is registered with a reactor. * * @note Be aware that the wait functions are "sloppy" on Unix, * because there's no good way to wait for a subset of the * children of a process. The wait functions may end up * collecting the exit status of a process that's not managed by * the ACE_Process_Manager whose wait() you invoked. It's best to * only use a single ACE_Process_Manager, and to create all * subprocesses by calling that manager's spawn() method. */ class ACE_Export ACE_Process_Manager : protected ACE_Event_Handler { public: friend class ACE_Process_Control; enum { DEFAULT_SIZE = 100 }; /** * @name Initialization and termination methods */ //@{ /** * Initialize an ACE_Process_Manager with a table containing up to * @a size processes. This table resizes itself automatically as * needed. If a @a reactor is provided, this * ACE_Process_Manager uses it to notify an application when a * process it controls exits. By default, however, we don't use an * ACE_Reactor. */ ACE_Process_Manager (size_t size = ACE_Process_Manager::DEFAULT_SIZE, ACE_Reactor *reactor = 0); /** * Initialize an ACE_Process_Manager with a table containing up to * @a size processes. This table resizes itself automatically as * needed. If a @a reactor is provided, this * ACE_Process_Manager uses it to notify an application when a * process it controls exits. By default, however, we don't use an * ACE_Reactor. */ int open (size_t size = ACE_Process_Manager::DEFAULT_SIZE, ACE_Reactor *r = 0); /// Release all resources. Do not wait for processes to exit. int close (); /// Destructor releases all resources and does not wait for processes /// to exit. virtual ~ACE_Process_Manager (); //@} /** * @name Singleton access and control */ //@{ /// Get pointer to a process-wide ACE_Process_Manager. static ACE_Process_Manager *instance (); /// Set pointer to a process-wide ACE_Process_Manager and return /// existing pointer. static ACE_Process_Manager *instance (ACE_Process_Manager *); /// Delete the dynamically allocated singleton. static void close_singleton (); /// Cleanup method, used by the ACE_Object_Manager to destroy the /// singleton. static void cleanup (void *instance, void *arg); //@} /** * @name Process creation methods */ //@{ /** * Create a new process with specified @a options. * Register @a event_handler to be called back when the process exits. * The @a proc object's ACE_Process::unmanage() method is called when * the process is removed from ACE_Process_Manager. * * On success, returns the process id of the child that was created. * On failure, returns ACE_INVALID_PID. */ pid_t spawn (ACE_Process *proc, ACE_Process_Options &options, ACE_Event_Handler *event_handler = 0); /** * Create a new process with the specified @a options. * Register @a event_handler to be called back when the process exits. * * On success, returns the process id of the child that was created. * On failure, returns ACE_INVALID_PID. */ pid_t spawn (ACE_Process_Options &options, ACE_Event_Handler *event_handler = 0); /** * Create @a n new processes with the same @a options. * If @a child_pids is non-0 it is expected to be an array of at least * @a n pid_t, which are filled in with the process IDs of the spawned * processes. * Register @a event_handler to be called back when each process exits. * Returns 0 on success and -1 on failure. */ int spawn_n (size_t n, ACE_Process_Options &options, pid_t *child_pids = 0, ACE_Event_Handler *event_Handler = 0); //@} /** * @name Process synchronization operations */ //@{ /** * Abruptly terminate a single process with id @a pid using the * ACE::terminate_process() method which works on both signal-capable * systems and on Windows. * * @note This call is potentially dangerous to use since the process * being terminated may not have a chance to cleanup before it shuts down. * The process's entry is also not removed from this class's process * table. Calling either wait() or remove() after terminate() is * advisable. * * @retval 0 on success and -1 on failure. */ int terminate (pid_t pid); /** * Sends the specified signal to the specified process. * * @note This only works on platforms that have signal capability. In * particular, it doesn't work on Windows. * * @retval 0 on success and -1 on failure. */ int terminate (pid_t pid, int sig); /** * Block until there are no more child processes running that were * spawned by this ACE_Process_Manager. Unlike the wait() method * below, this method does not require a signal handler or use of * ACE_OS::sigwait() because it simply blocks synchronously waiting * for all the children managed by this ACE_Process_Manager to * exit. Note that this does not return any status information * about the success or failure of exiting child processes, although * any registered exit handlers are called. * * @param timeout Relative time to wait for processes to terminate. * * @retval 0 on success; -1 on failure. */ int wait (const ACE_Time_Value &timeout = ACE_Time_Value::max_time); /// @sa wait template< class Rep, class Period > int wait (const std::chrono::duration& timeout) { ACE_Time_Value const tv (timeout); return this->wait (tv); } /** * Wait up to @a timeout for a single specified process to terminate. * If @a pid is 0, this method waits for any of the managed processes * (but see the note concerning "sloppy process cleanup on unix"). * If @a pid != 0, waits for that process only. * * @param pid Process ID * @param timeout Relative time to wait for process to terminate * @param status Exit status of terminated process * * @retval The pid of the process which exited, 0 * if a timeout occurred, or ACE_INVALID_PID on error. */ pid_t wait (pid_t pid, const ACE_Time_Value &timeout, ACE_exitcode *status = 0); /// @sa wait template< class Rep, class Period > pid_t wait (pid_t pid, const std::chrono::duration& timeout, ACE_exitcode *status = 0) { ACE_Time_Value const tv (timeout); return this->wait (pid, tv, status); } /** * Wait indefinitely for a single, specified process to terminate. * If @a pid is 0, waits for any of the managed processes (but see the * note concerning "sloppy process cleanup on unix"). * If @a pid != 0, this method waits for that process only. * * @retval The pid of the process which exited, or * ACE_INVALID_PID on error. */ pid_t wait (pid_t pid, ACE_exitcode *status = 0); //@} /** * @name Utility methods */ //@{ /** * Register an event handler to be called back when the specified * process exits. If @a pid == ACE_INVALID_PID this handler is called * when any process with no specific handler exits. * * @warning In multithreaded applications, there is a race condition * if a process exits between the time it is spawned and when its * handler is registered. To avoid this, register the handler at * the time the process is spawned. */ int register_handler (ACE_Event_Handler *event_handler, pid_t pid = ACE_INVALID_PID); /** * Remove process @a pid from the ACE_Process_Manager's internal records. * This is called automatically by the wait() method if the waited process * exits. This method can also be called after calling terminate() if * there's no need to wait() for the terminated process. */ int remove (pid_t pid); /// Return the number of managed processes. size_t managed () const; /** * Sets the scheduling parameters for process identified by @a pid by * passing @a params, @a pid to ACE_OS::sched_params(). * * @retval 0 on success, -1 on failure, and ACE_INVALID_PID when the * specified @a pid is not managed by this ACE_Process_Manager. */ int set_scheduler (const ACE_Sched_Params ¶ms, pid_t pid); /** * Sets the scheduling parameters for all the processes managed by * this ACE_Process_Manager by passing @a params to * ACE_OS::sched_params(). * * @retval 0 on success, -1 on failure. */ int set_scheduler_all (const ACE_Sched_Params ¶ms); /// Dump the state of an object. void dump () const; /// Declare the dynamic allocation hooks. ACE_ALLOC_HOOK_DECLARE; //@} protected: // = These methods allow a to be an Event_Handler. // As an Event_Handler, the automagically // detects child Processes exiting and calls notify_proc_handler() // and remove(). This means that you don't have to (shouldn't!) // call the wait(...) methods yourself. // On Unix, we can't detect individual process termination very // well; the best method is to catch SIGCHLD and then call the // polling wait() function to collect any available exit statuses. // However, we don't want to do this from within a signal handler // because of the restrictions associated. Therefore (following the // lead in examples/mumble) we open a bogus handle (to ACE_DEV_NULL) // and register that handle with our Reactor. Then, when our // SIGCHLD handler gets invoked, we tell the Reactor that the bogus // handle is readable. That will cause the handle_input() function // to be called once we're out of the interrupt context, and // handle_input() collects exit statuses. // On Win32, we simply register ourself with the Reactor to deal // with the Process handle becoming signaled. No muss, no fuss, no // signal handler, and no dummy handle. #if !defined(ACE_WIN32) /// Collect one (or more, on unix) process exit status. virtual int handle_input (ACE_HANDLE proc); /// If registered with a reactor for SIGCHLD and the reactor closes, this /// will get called to notify. virtual int handle_close (ACE_HANDLE handle, ACE_Reactor_Mask close_mask); #endif // !defined(ACE_WIN32) /** * On Unix, this routine is called asynchronously when a SIGCHLD is * received. We just tweak the reactor so that it'll call back our * function, which allows us to handle Process exits * synchronously. * * On Win32, this routine is called synchronously, and is passed the * HANDLE of the Process that exited, so we can do all our work here */ virtual int handle_signal (int signum, siginfo_t * = 0, ucontext_t * = 0); private: /** * @struct Process_Descriptor * * @internal This struct is for internal use only by ACE_Process_Manager. * * @brief Information describing each process that's controlled by an * ACE_Process_Manager. */ struct Process_Descriptor { /// Default ctor/dtor. Process_Descriptor (); ~Process_Descriptor (); /// Describes the process itself. ACE_Process *process_; /// Function to call when process exits ACE_Event_Handler *exit_notify_; /// Dump the state of an object. void dump () const; ACE_ALLOC_HOOK_DECLARE; }; /// Resize the pool of Process_Descriptors. int resize (size_t); /// Locate the index of the table slot occupied by @a process_id. /// Returns -1 if @a process_id is not in the @c process_table_ ssize_t find_proc (pid_t process_id); #if defined (ACE_WIN32) /// Locate the index of the table slot occupied by @a process_handle. /// Returns ~0 if @a process_handle is not in the @c process_table_ ssize_t find_proc (ACE_HANDLE process_handle); #endif /* ACE_WIN32 */ /// Insert a process in the table (checks for duplicates). Omitting /// the process handle won't work on Win32... /// Register @a event_handler to be called back when the process exits. int insert_proc (ACE_Process *process, ACE_Event_Handler *event_handler = 0); /** * Append information about a process, i.e., its in the * @c process_table_. Each entry is added at the end, growing the * table if necessary. * Register @a event_handler to be called back when the process exits. */ int append_proc (ACE_Process *process, ACE_Event_Handler *event_handler = 0); /// Actually removes the process at index @a n from the table. This method /// must be called with locks held. int remove_proc (size_t n); /// If there's a specific handler for the Process at index @a n in the /// table, or there's a default handler, call it. int notify_proc_handler (size_t n, ACE_exitcode status); /// Vector that describes process state within the Process_Manager. Process_Descriptor *process_table_; /// Maximum number of processes we can manage (should be dynamically /// allocated). size_t max_process_table_size_; /// Current number of processes we are managing. size_t current_count_; /// This event handler is used to notify when a process we control /// exits. ACE_Event_Handler *default_exit_handler_; /// Singleton pointer. static ACE_Process_Manager *instance_; /// Controls whether the is deleted when we shut /// down (we can only delete it safely if we created it!) static bool delete_instance_; #if defined (ACE_HAS_THREADS) /// This lock protects access/ops on @c process_table_. ACE_Recursive_Thread_Mutex lock_; #endif /* ACE_HAS_THREADS */ }; ACE_END_VERSIONED_NAMESPACE_DECL #if defined (__ACE_INLINE__) #include "ace/Process_Manager.inl" #endif /* __ACE_INLINE__ */ #include /**/ "ace/post.h" #endif /* ACE_PROCESS_MANAGER_H */