diff options
-rw-r--r-- | CMakeLists.txt | 9 | ||||
-rw-r--r-- | include/cru/common/SubProcess.h | 98 | ||||
-rw-r--r-- | src/common/SubProcess.cpp | 75 |
3 files changed, 96 insertions, 86 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fd86852..75da1485 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,12 +63,13 @@ add_subdirectory(test) add_subdirectory(demos) set(CLANGD_FILE ${CMAKE_BINARY_DIR}/clangd) + if(EMSCRIPTEN) execute_process(COMMAND ${CMAKE_C_COMPILER} --cflags OUTPUT_VARIABLE CLANGD_FLAGS_TO_ADD) separate_arguments(CLANGD_FLAGS_TO_ADD UNIX_COMMAND "${CLANGD_FLAGS_TO_ADD}") list(JOIN CLANGD_FLAGS_TO_ADD ", " CLANGD_FLAGS_TO_ADD) - set(CLANGD_TEMPLATE ${PROJECT_SOURCE_DIR}/scripts/clangd.in) - configure_file(${CLANGD_TEMPLATE} ${CLANGD_FILE}) -else() - file(WRITE ${CLANGD_FILE} "") endif() + +set(CLANGD_TEMPLATE ${PROJECT_SOURCE_DIR}/scripts/clangd.in) +configure_file(${CLANGD_TEMPLATE} ${CLANGD_FILE}) + diff --git a/include/cru/common/SubProcess.h b/include/cru/common/SubProcess.h index 6a3cc6e6..b31bc071 100644 --- a/include/cru/common/SubProcess.h +++ b/include/cru/common/SubProcess.h @@ -5,6 +5,7 @@ #include "io/Stream.h" #include <chrono> +#include <condition_variable> #include <mutex> #include <optional> #include <thread> @@ -36,6 +37,12 @@ class CRU_BASE_API SubProcessException : public Exception { ~SubProcessException() override; }; +class CRU_BASE_API SubProcessFailedToStartException : public Exception { + public: + SubProcessFailedToStartException(String message = {}); + ~SubProcessFailedToStartException() override; +}; + struct PlatformSubProcessStartInfo { String program; std::vector<String> arguments; @@ -50,8 +57,8 @@ struct PlatformSubProcessExitResult { * @brief Base class of a platform process. It is one-time, which means it * starts and exits and can't start again. * @remarks - * If an object of this class is destructed before the process ends, the process - * will be killed. + * If an object of this class is destructed before the process exits, the + * process will be killed. */ class PlatformSubProcessBase : public Object { public: @@ -61,40 +68,37 @@ class PlatformSubProcessBase : public Object { ~PlatformSubProcessBase() override; /** - * @brief Create and start a real process. If the program can't be created or - * start, an exception will be thrown. - * - * @remarks This method will hold the data lock during running. It ensures - * after return, the process already tries to start and status is definitely - * not `Prepare` but `FailedToStart` or `Running` or `Exit`. So it is safe to - * use `GetStatus` to check whether the process tries to start, aka, this - * method has been called or not. + * @brief Create and start a real process. If the process can't be created or + * start, `SubProcessFailedToStartException` will be thrown. If this function + * is already called once, `SubProcessException` will be thrown. Ensure only + * call this method once. */ void Start(); /** - * @brief Wait for the process to exit for at most `wait_time`. If the process - * already exits, it will return immediately. If the process has not started - * or failed to start, it will throw. Ensure `Start` is called and does not - * throw before call this. + * @brief Wait for the process to exit optionally for at most `wait_time`. If + * the process already exits, it will return immediately. If the process has + * not started or failed to start, `SubProcessException` will be thrown. + * Ensure `Start` is called and does not throw before calling this. * * @remarks You may wish this returns bool to indicate whether it is timeout * or the process exits. But no, even if it is timeout, the process may also - * exits due to task schedule. + * have exited due to task schedule. */ void Wait(std::optional<std::chrono::milliseconds> wait_time); /** * @brief kill the process if it is running. If the process already exits, - * nothing will happen. If the process has not started or failed to start, it - * will throw. Ensure `Start` is called and does not throw before call this. + * nothing will happen. If the process has not started or failed to start, + * `SubProcessException` will be throw. Ensure `Start` is called and does not + * throw before calling this. */ void Kill(); /** * @brief Get the status of the process. * 1. If the process has tried to start, aka `Start` is called, then this - * method must return `Running`, `FailedToStart`. + * method will return one of `Running`, `FailedToStart`, `Exited`. * 2. If it returns `Prepare`, `Start` is not called. * 3. It does NOT guarantee that this return `Running` and the process is * actually running. Because there might be a window that the process exits @@ -102,20 +106,16 @@ class PlatformSubProcessBase : public Object { */ PlatformSubProcessStatus GetStatus(); + /** + * @brief Get the exit result. If the process is not started, failed to start + * or running, `SubProcessException` will be thrown. + */ PlatformSubProcessExitResult GetExitResult(); + io::Stream* GetStdinStream(); io::Stream* GetStdoutStream(); io::Stream* GetStderrStream(); - bool IsAutoDeleteAfterProcessExit() const; - - /** - * @brief If auto delete, this instance will delete it self after the process - * exits. The delete will be called at another thread. You must ensure this - * object is created by new. - */ - void SetAutoDeleteAfterProcessExit(bool auto_delete); - protected: /** * @brief Create and start a real process. If the program can't be created or @@ -123,8 +123,8 @@ class PlatformSubProcessBase : public Object { * * Implementation should fill internal data of the new process and start it. * - * This method will be called on this thread with data lock hold. It will only - * called once in first call of `Start`. + * This method will be called only once in first call of `Start` on this + * thread with lock hold. */ virtual void PlatformCreateProcess() = 0; @@ -134,52 +134,32 @@ class PlatformSubProcessBase : public Object { * Implementation should wait for the real process forever, after that, fill * internal data and returns exit result. * - * This method will be called on another thread. It will only be called once - * after a success call of `Start`. It is safe to write internal data in this - * method because process lock will be hold and we won't write to internal. + * This method will be called only once on another thread after + * `PlatformCreateProcess` returns successfully */ virtual PlatformSubProcessExitResult PlatformWaitForProcess() = 0; /** * @brief Kill the process immediately. * - * This method will be called on this thread. It will only be called once - * after a success call of `Start`. There will be a window that the window - * already exits but the status has not been updated and this is called. So - * handle this gracefully and write to internal data carefully. + * This method will be called only once on this thread given + * `PlatformCreateProcess` returns successfullyThere will be a window that the + * window already exits but the status has not been updated and this is + * called. So handle this gracefully and write to internal data carefully. */ virtual void PlatformKillProcess() = 0; - private: - auto CreateDataLockGuard() { - return std::lock_guard<std::unique_lock<std::mutex>>(data_lock_); - } - - auto CreateProcessLockGuard() { - return std::lock_guard<std::unique_lock<std::timed_mutex>>(process_lock_); - } - protected: PlatformSubProcessStartInfo start_info_; + PlatformSubProcessExitResult exit_result_; private: - bool auto_delete_; - PlatformSubProcessStatus status_; - std::mutex data_mutex_; - /** - * Lock for protecting data of this class. - */ - std::unique_lock<std::mutex> data_lock_; std::thread process_thread_; - - std::timed_mutex process_mutex_; - /** - * Lock for protecting internal data of sub-class, and used for detect whether - * process is running. - */ - std::unique_lock<std::timed_mutex> process_lock_; + std::mutex process_mutex_; + std::unique_lock<std::mutex> process_lock_; + std::condition_variable process_condition_variable_; }; class CRU_BASE_API SubProcess : public Object { diff --git a/src/common/SubProcess.cpp b/src/common/SubProcess.cpp index d69fb973..364cd66e 100644 --- a/src/common/SubProcess.cpp +++ b/src/common/SubProcess.cpp @@ -9,66 +9,74 @@ SubProcessException::SubProcessException(String message) SubProcessException::~SubProcessException() {} +SubProcessFailedToStartException::SubProcessFailedToStartException( + String message) + : Exception(std::move(message)) {} + +SubProcessFailedToStartException::~SubProcessFailedToStartException() {} + PlatformSubProcessBase::PlatformSubProcessBase( const PlatformSubProcessStartInfo& start_info) - : auto_delete_(false), - start_info_(start_info), - data_lock_(data_mutex_, std::defer_lock), - process_lock_(process_mutex_, std::defer_lock) {} + : start_info_(start_info), process_lock_(process_mutex_, std::defer_lock) {} PlatformSubProcessBase::~PlatformSubProcessBase() {} void PlatformSubProcessBase::Start() { - auto data_lock_guard = CreateDataLockGuard(); + std::lock_guard lock_guard(process_lock_); if (status_ != PlatformSubProcessStatus::Prepare) { - throw SubProcessException(u"Sub-process has already run."); + throw SubProcessException(u"The process has already tried to start."); } - status_ = PlatformSubProcessStatus::Running; - try { PlatformCreateProcess(); + status_ = PlatformSubProcessStatus::Running; + process_thread_ = std::thread([this] { - auto process_lock_guard = CreateProcessLockGuard(); - PlatformWaitForProcess(); - auto data_lock_guard = CreateDataLockGuard(); - status_ = PlatformSubProcessStatus::Exited; + auto exit_result = PlatformWaitForProcess(); + { + std::lock_guard lock_guard(process_lock_); + exit_result_ = std::move(exit_result); + status_ = PlatformSubProcessStatus::Exited; + } + this->process_condition_variable_.notify_all(); }); process_thread_.detach(); } catch (const std::exception& e) { status_ = PlatformSubProcessStatus::FailedToStart; + throw SubProcessFailedToStartException(u"Sub-process failed to start. " + + String::FromUtf8(e.what())); } } void PlatformSubProcessBase::Wait( std::optional<std::chrono::milliseconds> wait_time) { - auto status = GetStatus(); + std::lock_guard lock_guard(process_lock_); - if (status == PlatformSubProcessStatus::Prepare) { + if (status_ == PlatformSubProcessStatus::Prepare) { throw SubProcessException( u"The process does not start. Can't wait for it."); } - if (status == PlatformSubProcessStatus::FailedToStart) { + if (status_ == PlatformSubProcessStatus::FailedToStart) { throw SubProcessException( u"The process failed to start. Can't wait for it."); } - if (status == PlatformSubProcessStatus::Exited) { + if (status_ == PlatformSubProcessStatus::Exited) { return; } + auto predicate = [this] { + return status_ == PlatformSubProcessStatus::Exited; + }; + if (wait_time) { - auto locked = process_lock_.try_lock_for(*wait_time); - if (locked) { - process_lock_.unlock(); - } + process_condition_variable_.wait_for(process_lock_, *wait_time, predicate); } else { - process_lock_.lock(); - process_lock_.unlock(); + process_condition_variable_.wait(process_lock_, predicate); } } @@ -91,7 +99,28 @@ void PlatformSubProcessBase::Kill() { } PlatformSubProcessStatus PlatformSubProcessBase::GetStatus() { - auto data_lock_guard = CreateDataLockGuard(); + std::lock_guard data_lock_guard(process_lock_); return status_; } + +PlatformSubProcessExitResult PlatformSubProcessBase::GetExitResult() { + std::lock_guard lock_guard(process_lock_); + + if (status_ == PlatformSubProcessStatus::Prepare) { + throw SubProcessException( + u"The process does not start. Can't get exit result."); + } + + if (status_ == PlatformSubProcessStatus::FailedToStart) { + throw SubProcessException( + u"The process failed to start. Can't get exit result."); + } + + if (status_ == PlatformSubProcessStatus::Running) { + throw SubProcessException( + u"The process is running. Can't get exit result."); + } + + return exit_result_; +} } // namespace cru |