From e197ae6fae73719266fd4747f499cd6106fbff4e Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 8 Oct 2020 21:24:00 +0300 Subject: Add process::term() and implement process::kill() on Windows --- libbutl/process.cxx | 57 ++++++++++++++++++++++++++++++++++++++++------------- libbutl/process.mxx | 30 ++++++++++++++++++---------- 2 files changed, 63 insertions(+), 24 deletions(-) (limited to 'libbutl') diff --git a/libbutl/process.cxx b/libbutl/process.cxx index 0695493..3383a73 100644 --- a/libbutl/process.cxx +++ b/libbutl/process.cxx @@ -805,13 +805,15 @@ namespace butl void process:: kill () { - if (handle != 0) - { - if (::kill (handle, SIGKILL) == -1) - throw process_error (errno); + if (handle != 0 && ::kill (handle, SIGKILL) == -1) + throw process_error (errno); + } - wait (); - } + void process:: + term () + { + if (handle != 0 && ::kill (handle, SIGTERM) == -1) + throw process_error (errno); } process::id_type process:: @@ -1958,9 +1960,16 @@ namespace butl optional process:: try_wait () { + return timed_wait (chrono::milliseconds (0)); + } + + template <> + optional process:: + timed_wait (const chrono::milliseconds& t) + { if (handle != 0) { - DWORD r (WaitForSingleObject (handle, 0)); + DWORD r (WaitForSingleObject (handle, static_cast (t.count ()))); if (r == WAIT_TIMEOUT) return nullopt; @@ -1982,17 +1991,33 @@ namespace butl return exit ? static_cast (*exit) : optional (); } - template <> - optional process:: - timed_wait (const chrono::milliseconds&) + void process:: + kill () { - throw process_error (ENOTSUP); + // Note that TerminateProcess() requires an exit code the process will be + // terminated with. We could probably craft a custom exit code that will + // be treated by the normal() function as an abnormal termination. + // However, let's keep it simple and reuse the existing (semantically + // close) error code. + // + if (handle != 0 && !TerminateProcess (handle, DBG_TERMINATE_PROCESS)) + { + DWORD e (GetLastError ()); + if (e != ERROR_ACCESS_DENIED) + throw process_error (error_msg (e)); + + // Handle the case when the process has already terminated or is still + // exiting (potentially after being killed). + // + if (!try_wait ()) + throw process_error (error_msg (e), EPERM); + } } void process:: - kill () + term () { - throw process_error (ENOTSUP); + kill (); } process::id_type process:: @@ -2023,7 +2048,7 @@ namespace butl // [ 0, 16) - program exit code or exception code // [16, 29) - facility // [29, 30) - flag indicating if the status value is customer-defined - // [30, 31) - severity (00 -success, 01 - informational, 10 - warning, + // [30, 31] - severity (00 -success, 01 - informational, 10 - warning, // 11 - error) // : status (c) @@ -2094,6 +2119,10 @@ namespace butl case STATUS_STACK_BUFFER_OVERRUN: return "aborted"; case STATUS_STACK_OVERFLOW: return "stack overflow"; + // Presumably the kill() function was called for the process. + // + case DBG_TERMINATE_PROCESS: return "killed"; + default: { string desc ("unknown error 0x"); diff --git a/libbutl/process.mxx b/libbutl/process.mxx index 54abdec..9106549 100644 --- a/libbutl/process.mxx +++ b/libbutl/process.mxx @@ -360,27 +360,37 @@ LIBBUTL_MODEXPORT namespace butl // duration. Return the same result as wait() if the process has // terminated in this timeframe and nullopt otherwise. // - // Note: not yet implemented on Windows. - // template optional timed_wait (const std::chrono::duration&); - // Terminate the process. + // Note that the destructor will wait for the process but will ignore + // any errors and the exit status. + // + ~process () {if (handle != 0) wait (true);} + + // Process termination. // - // On POSIX send SIGKILL to the process and wait until it terminates. The - // process exit information is available after the call returns. Noop for - // an already terminated process. + + // Send SIGKILL to the process on POSIX and call TerminateProcess() with + // DBG_TERMINATE_PROCESS exit code on Windows. Noop for an already + // terminated process. + // + // Note that if the process is killed, it terminates as if it has called + // abort() (functions registered with atexit() are not called, etc). // - // Note: not yet implemented on Windows. + // Also note that on Windows calling this function for a terminating + // process results in the EPERM process_error exception. // void kill (); - // Note that the destructor will wait for the process but will ignore - // any errors and the exit status. + // Send SIGTERM to the process on POSIX and call kill() on Windows (where + // there is no general way to terminate a console process gracefully). + // Noop for an already terminated process. // - ~process () {if (handle != 0) wait (true);} + void + term (); // Moveable-only type. // -- cgit v1.1