aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt9
-rw-r--r--include/cru/common/SubProcess.h98
-rw-r--r--src/common/SubProcess.cpp75
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