Преглед изворни кода

Win: Use SetConsoleCtrlHandler for SIGINT/SIGTERM

Nils Maier пре 12 година
родитељ
комит
79fcafc31f
3 измењених фајлова са 114 додато и 7 уклоњено
  1. 8 0
      src/DownloadEngine.cc
  2. 30 7
      src/MultiUrlRequestInfo.cc
  3. 76 0
      src/util.cc

+ 8 - 0
src/DownloadEngine.cc

@@ -83,6 +83,7 @@ namespace global {
 // 2 ... stop signal processed by DownloadEngine
 // 3 ... 2nd stop signal(force shutdown) detected
 // 4 ... 2nd stop signal processed by DownloadEngine
+// 5 ... main loop exited
 volatile sig_atomic_t globalHaltRequested = 0;
 
 } // namespace global
@@ -141,6 +142,13 @@ void executeCommand(std::deque<std::unique_ptr<Command>>& commands,
 
 int DownloadEngine::run(bool oneshot)
 {
+  class GHR {
+    public:
+      ~GHR() {
+        global::globalHaltRequested = 5;
+      }
+  } ghr;
+
   while(!commands_.empty() || !routineCommands_.empty()) {
     if(!commands_.empty()) {
       waitData();

+ 30 - 7
src/MultiUrlRequestInfo.cc

@@ -86,21 +86,44 @@ extern volatile sig_atomic_t globalHaltRequested;
 } // namespace global
 
 namespace {
-void handler(int signal) {
+
+#ifdef _WIN32
+static const DWORD mainThread = GetCurrentThreadId();
+#endif
+
+static void handler(int signal)
+{
   if(
 #ifdef SIGHUP
      signal == SIGHUP ||
 #endif // SIGHUP
      signal == SIGTERM) {
-    if(global::globalHaltRequested == 0 || global::globalHaltRequested == 2) {
+    if(global::globalHaltRequested <= 2) {
       global::globalHaltRequested = 3;
     }
-  } else {
-    if(global::globalHaltRequested == 0) {
-      global::globalHaltRequested = 1;
-    } else if(global::globalHaltRequested == 2) {
-      global::globalHaltRequested = 3;
+#ifdef _WIN32
+    if (::GetCurrentThreadId() != mainThread) {
+      // SIGTERM may arrive on another thread (via SetConsoleCtrlHandler), and
+      // the process will be forcefully terminated as soon as that thread is
+      // done. So better make sure it isn't done prematurely. ;)
+      while (global::globalHaltRequested != 5) {
+        ::Sleep(100); // Yeah, semi-busy waiting for now.
+      }
     }
+#endif
+    return;
+  }
+
+  // SIGINT
+
+  if (global::globalHaltRequested == 0) {
+    global::globalHaltRequested = 1;
+    return;
+  }
+
+  if (global::globalHaltRequested == 2) {
+    global::globalHaltRequested = 3;
+    return;
   }
 }
 } // namespace

+ 76 - 0
src/util.cc

@@ -84,6 +84,7 @@
 #include "BufferedFile.h"
 #include "SocketCore.h"
 #include "prefs.h"
+#include "Lock.h"
 
 #ifdef ENABLE_MESSAGE_DIGEST
 # include "MessageDigest.h"
@@ -1232,8 +1233,83 @@ bool isNumericHost(const std::string& name)
   return true;
 }
 
+#if _WIN32
+namespace {
+  static Lock win_signal_lock;
+
+  static void(*win_int_handler)(int) = nullptr;
+  static void(*win_term_handler)(int) = nullptr;
+
+  static void win_ign_handler(int) {}
+
+  static BOOL WINAPI HandlerRoutine(DWORD ctrlType)
+  {
+    void(*handler)(int) = nullptr;
+    switch (ctrlType) {
+      case CTRL_C_EVENT:
+      case CTRL_BREAK_EVENT:
+        {
+          // Handler will be called on a new/different thread.
+          LockGuard lg(win_signal_lock);
+          handler = win_int_handler;
+        }
+
+        if (handler) {
+          handler(SIGINT);
+          return TRUE;
+        }
+        return FALSE;
+
+      case CTRL_LOGOFF_EVENT:
+      case CTRL_CLOSE_EVENT:
+      case CTRL_SHUTDOWN_EVENT:
+        {
+          // Handler will be called on a new/different thread.
+          LockGuard lg(win_signal_lock);
+          handler = win_term_handler;;
+        }
+        if (handler) {
+          handler(SIGTERM);
+          return TRUE;
+        }
+        return FALSE;
+    }
+    return FALSE;
+  }
+}
+#endif
+
 void setGlobalSignalHandler(int sig, sigset_t* mask, void (*handler)(int),
                             int flags) {
+#if _WIN32
+  if (sig == SIGINT || sig == SIGTERM) {
+    // Handler will be called on a new/different thread.
+    LockGuard lg(win_signal_lock);
+
+    if (handler == SIG_DFL) {
+      handler = nullptr;
+    }
+    else if (handler == SIG_IGN) {
+      handler = win_ign_handler;
+    }
+    // Not yet in use: add console handler.
+    if (handler && !win_int_handler && !win_term_handler) {
+      ::SetConsoleCtrlHandler(HandlerRoutine, TRUE);
+    }
+    if (sig == SIGINT) {
+      win_int_handler = handler;
+    }
+    else {
+      win_term_handler = handler;
+    }
+    // No handlers set: remove.
+    if (!win_int_handler && !win_term_handler) {
+      ::SetConsoleCtrlHandler(HandlerRoutine, FALSE);
+    }
+    return;
+  }
+#endif
+
 #ifdef HAVE_SIGACTION
   struct sigaction sigact;
   sigact.sa_handler = handler;