From ffc6c3819b3e67a218f3b9e199aa7b4829d69cfb Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 7 Dec 2015 16:42:14 +0100 Subject: [PATCH] More robust process functions. --- src/process.cpp | 13 +++++++----- src/process.hpp | 21 ++++++++++++++++--- src/process_unix.cpp | 28 +++++++++++++++++++++---- src/process_win.cpp | 49 +++++++++++++++++++++++++++++++++++--------- src/terminal.cc | 4 ++-- 5 files changed, 91 insertions(+), 24 deletions(-) diff --git a/src/process.cpp b/src/process.cpp index d754b89..a1f7ec9 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -7,14 +7,17 @@ Process::Process(const std::string &command, const std::string &path, std::function read_stdout, std::function read_stderr, bool open_stdin, size_t buffer_size): - read_stdout(read_stdout), read_stderr(read_stderr), open_stdin(open_stdin), buffer_size(buffer_size) { - id=open(command, path); - if(id>0) - async_read(); + closed(true), read_stdout(read_stdout), read_stderr(read_stderr), open_stdin(open_stdin), buffer_size(buffer_size) { + open(command, path); + async_read(); } Process::~Process() { - close_all(); + close_fds(); +} + +Process::id_type Process::get_id() { + return data.id; } bool Process::write(const std::string &data) { diff --git a/src/process.hpp b/src/process.hpp index 49a11ad..79d5ef5 100644 --- a/src/process.hpp +++ b/src/process.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef _WIN32 #include #else @@ -26,6 +27,16 @@ public: typedef pid_t id_type; typedef int fd_type; #endif +private: + class Data { + public: + Data(); + id_type id; +#ifdef _WIN32 + HANDLE handle; +#endif + }; +public: Process(const std::string &command, const std::string &path=std::string(), std::function read_stdout=nullptr, std::function read_stderr=nullptr, @@ -34,7 +45,7 @@ public: ~Process(); ///Get the process id of the started process. - id_type get_id() {return id;} + id_type get_id(); ///Wait until process is finished, and return exit status. int get_exit_status(); ///Write to stdin. @@ -44,10 +55,15 @@ public: ///Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent. void close_stdin(); + ///Kill the process. + void kill(bool force=false); ///Kill a given process id. static void kill(id_type id, bool force=false); private: + Data data; + bool closed; + std::mutex close_mutex; std::function read_stdout; std::function read_stderr; std::thread stdout_thread, stderr_thread; @@ -58,9 +74,8 @@ private: std::unique_ptr stdout_fd, stderr_fd, stdin_fd; id_type open(const std::string &command, const std::string &path); - id_type id; void async_read(); - void close_all(); + void close_fds(); }; #endif // TINY_PROCESS_LIBRARY_HPP_ diff --git a/src/process_unix.cpp b/src/process_unix.cpp index 1ff6f59..7213621 100644 --- a/src/process_unix.cpp +++ b/src/process_unix.cpp @@ -6,6 +6,8 @@ #include //TODO: remove using namespace std; //TODO: remove +Process::Data::Data(): id(-1) {} + Process::id_type Process::open(const std::string &command, const std::string &path) { if(open_stdin) stdin_fd=std::unique_ptr(new fd_type); @@ -78,10 +80,14 @@ Process::id_type Process::open(const std::string &command, const std::string &pa if(stdout_fd) *stdout_fd = stdout_p[0]; if(stderr_fd) *stderr_fd = stderr_p[0]; + closed=false; + data.id=pid; return pid; } void Process::async_read() { + if(data.id<=0) + return; if(stdout_fd) { stdout_thread=std::thread([this](){ char buffer[buffer_size]; @@ -101,17 +107,20 @@ void Process::async_read() { } int Process::get_exit_status() { - if(id<=0) + if(data.id<=0) return -1; int exit_status; - waitpid(id, &exit_status, 0); + waitpid(data.id, &exit_status, 0); + close_mutex.lock(); + closed=true; + close_mutex.unlock(); - close_all(); + close_fds(); return exit_status; } -void Process::close_all() { +void Process::close_fds() { if(stdout_thread.joinable()) stdout_thread.join(); if(stderr_thread.joinable()) @@ -154,6 +163,17 @@ void Process::close_stdin() { stdin_mutex.unlock(); } +void Process::kill(bool force) { + close_mutex.lock(); + if(data.id>0 && !closed) { + if(force) + ::kill(-data.id, SIGTERM); + else + ::kill(-data.id, SIGINT); + } + close_mutex.unlock(); +} + void Process::kill(id_type id, bool force) { if(id<=0) return; diff --git a/src/process_win.cpp b/src/process_win.cpp index bfd3699..25a15bb 100644 --- a/src/process_win.cpp +++ b/src/process_win.cpp @@ -5,6 +5,8 @@ #include //TODO: remove using namespace std; //TODO: remove +Process::Data::Data(): id(0), handle(NULL) {} + //Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx. //Note: on Windows it seems impossible to specify which pipes to use. //Thus, if read_stdout==nullptr, read_stderr==nullptr and open_stdin==false, the stdout, stderr and stdin are sent to the parent process instead. @@ -138,10 +140,15 @@ Process::id_type Process::open(const std::string &command, const std::string &pa if(stdout_fd) *stdout_fd=g_hChildStd_OUT_Rd; if(stderr_fd) *stderr_fd=g_hChildStd_ERR_Rd; + closed=false; + data.id=process_info.dwProcessId; + data.handle=process_info.hProcess; return process_info.dwProcessId; } void Process::async_read() { + if(data.id==0) + return; if(stdout_fd) { stdout_thread=std::thread([this](){ DWORD n; @@ -169,24 +176,23 @@ void Process::async_read() { } int Process::get_exit_status() { - if(id==0) + if(data.id==0) return -1; DWORD exit_status; - HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); - if(process_handle) { - WaitForSingleObject(process_handle, INFINITE); - GetExitCodeProcess(process_handle, &exit_status); - CloseHandle(process_handle); - } - else + WaitForSingleObject(data.handle, INFINITE); + if(!GetExitCodeProcess(data.handle, &exit_status)) exit_status=-1; + close_mutex.lock(); + CloseHandle(data.handle); + closed=true; + close_mutex.unlock(); - close_all(); + close_fds(); return static_cast(exit_status); } -void Process::close_all() { +void Process::close_fds() { if(stdout_thread.joinable()) stdout_thread.join(); if(stderr_thread.joinable()) @@ -231,6 +237,29 @@ void Process::close_stdin() { stdin_mutex.unlock(); } +//Based on http://stackoverflow.com/a/1173396 +void Process::kill(bool force) { + close_mutex.lock(); + if(data.id>0 && !closed) { + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(snapshot) { + PROCESSENTRY32 process; + ZeroMemory(&process, sizeof(process)); + process.dwSize = sizeof(process); + if(Process32First(snapshot, &process)) { + do { + if(process.th32ParentProcessID==data.id) { + HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID); + if(process_handle) TerminateProcess(process_handle, 2); + } + } while (Process32Next(snapshot, &process)); + } + } + TerminateProcess(data.handle, 2); + } + close_mutex.unlock(); +} + //Based on http://stackoverflow.com/a/1173396 void Process::kill(id_type id, bool force) { if(id==0) diff --git a/src/terminal.cc b/src/terminal.cc index 9cbd480..278b60e 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -166,14 +166,14 @@ void Terminal::async_process(const std::string &command, const boost::filesystem void Terminal::kill_last_async_process(bool force) { processes_mutex.lock(); if(processes.size()>0) - Process::kill(processes.back()->get_id(), force); + processes.back()->kill(force); processes_mutex.unlock(); } void Terminal::kill_async_processes(bool force) { processes_mutex.lock(); for(auto &process: processes) - Process::kill(process->get_id(), force); + process->kill(force); processes_mutex.unlock(); }