Bug 336193 - P1: Use SignalPipeWatcher to detect signals SIGTERM/SIGINT/SIGHUP and quit application gracefully. draft
authorKestrel <kestrel@vmail.me>
Thu, 29 Mar 2018 19:23:45 +0800
changeset 774719 fb5fbe6ced380e59a217f4c9f0f84f6558f23fe3
parent 774718 538ce8ca4b132cc27bb645f5f20caef4c5d6fb93
child 774720 676773180d7e3c0a015d60961cf0f8e8f84b1a6c
push id104477
push userbmo:kestrel@vmail.me
push dateThu, 29 Mar 2018 11:25:28 +0000
bugs336193
milestone61.0a1
Bug 336193 - P1: Use SignalPipeWatcher to detect signals SIGTERM/SIGINT/SIGHUP and quit application gracefully. MozReview-Commit-ID: HH1B7UFhWqY
toolkit/xre/nsNativeAppSupportUnix.cpp
toolkit/xre/nsUpdateDriver.cpp
xpcom/base/nsDumpUtils.cpp
xpcom/base/nsDumpUtils.h
--- a/toolkit/xre/nsNativeAppSupportUnix.cpp
+++ b/toolkit/xre/nsNativeAppSupportUnix.cpp
@@ -9,16 +9,17 @@
 #include "nsXPCOM.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIObserverService.h"
 #include "nsIAppStartup.h"
 #include "nsServiceManagerUtils.h"
 #include "prlink.h"
 #include "nsXREDirProvider.h"
 #include "nsReadableUtils.h"
+#include "nsDumpUtils.h"
 
 #include "nsIFile.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsICommandLineRunner.h"
 #include "nsIWindowMediator.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShell.h"
 #include "nsIBaseWindow.h"
@@ -156,16 +157,18 @@ private:
   {
     mClientState = aState;
     MOZ_LOG(sMozSMLog, LogLevel::Debug, ("New state = %s\n", gClientStateTable[aState]));
   }
 
   SmcConn mSessionConnection;
   ClientState mClientState;
 #endif
+  static void TerminationHandler(const uint8_t aRecvSig);
+  static void ForceAppQuitAsync();
 };
 
 #if MOZ_X11
 static gboolean
 process_ice_messages(IceConn connection)
 {
   IceProcessMessagesStatus status;
 
@@ -374,22 +377,17 @@ nsNativeAppSupportUnix::SaveYourselfCB(S
   } else {
     SmcSaveYourselfDone(smc_conn, True);
   }
 }
 
 void
 nsNativeAppSupportUnix::DieCB(SmcConn smc_conn, SmPointer client_data)
 {
-  nsCOMPtr<nsIAppStartup> appService =
-    do_GetService("@mozilla.org/toolkit/app-startup;1");
-
-  if (appService) {
-    appService->Quit(nsIAppStartup::eForceQuit);
-  }
+  ForceAppQuitAsync();
   // Quit causes the shutdown to begin but the shutdown process is asynchronous
   // so we can't DisconnectFromSM() yet
 }
 
 void
 nsNativeAppSupportUnix::ShutdownCancelledCB(SmcConn smc_conn,
                                             SmPointer client_data)
 {
@@ -669,19 +667,56 @@ nsNativeAppSupportUnix::Stop(bool *aResu
   NS_ENSURE_ARG(aResult);
   *aResult = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNativeAppSupportUnix::Enable()
 {
+  // Detect termination signals with a pipe so they can be handled cleanly.
+  // Note that SignalPipeWatcher does not chain to previous handler, this
+  // prevents nsProfileLock from interfering until we unregister later. SIGINT
+  // may be set to SIG_IGN by non-interactive shell which will not be registered.
+  SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton();
+  sw->RegisterCallback(SIGTERM, nsNativeAppSupportUnix::TerminationHandler);
+  sw->RegisterCallback(SIGINT, nsNativeAppSupportUnix::TerminationHandler);
+  sw->RegisterCallback(SIGHUP, nsNativeAppSupportUnix::TerminationHandler);
   return NS_OK;
 }
 
+void
+nsNativeAppSupportUnix::TerminationHandler(const uint8_t aRecvSig)
+{
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+    "nsNativeAppSupportUnix::TerminationHandler", []() -> void {
+      // Try to perform a normal clean quit, needs to be executed on
+      // the main thread.
+      ForceAppQuitAsync();
+
+      // Restore previous signal handlers so subsequent termination attempts
+      // can kill process, can't be part of immediate callback due to mutex lock.
+      SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton();
+      sw->UnregisterCallback(SIGTERM);
+      sw->UnregisterCallback(SIGINT);
+      sw->UnregisterCallback(SIGHUP);
+    }));
+}
+
+void
+nsNativeAppSupportUnix::ForceAppQuitAsync()
+{
+  nsCOMPtr<nsIAppStartup> appService =
+    do_GetService("@mozilla.org/toolkit/app-startup;1");
+
+  if (appService) {
+    appService->Quit(nsIAppStartup::eForceQuit);
+  }
+}
+
 nsresult
 NS_CreateNativeAppSupport(nsINativeAppSupport **aResult)
 {
   nsNativeAppSupportBase* native = new nsNativeAppSupportUnix();
   if (!native)
     return NS_ERROR_OUT_OF_MEMORY;
 
   *aResult = native;
--- a/toolkit/xre/nsUpdateDriver.cpp
+++ b/toolkit/xre/nsUpdateDriver.cpp
@@ -44,16 +44,17 @@
 #elif defined(XP_UNIX)
 # include <unistd.h>
 # include <sys/wait.h>
 #endif
 
 using namespace mozilla;
 
 static LazyLogModule sUpdateLog("updatedriver");
+#undef LOG
 #define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
 
 #ifdef XP_WIN
 #define UPDATER_BIN "updater.exe"
 #elif XP_MACOSX
 #define UPDATER_BIN "org.mozilla.updater"
 #else
 #define UPDATER_BIN "updater"
--- a/xpcom/base/nsDumpUtils.cpp
+++ b/xpcom/base/nsDumpUtils.cpp
@@ -133,22 +133,50 @@ SignalPipeWatcher::RegisterCallback(uint
   MutexAutoLock lock(mSignalInfoLock);
 
   for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) {
     if (mSignalInfo[i].mSignal == aSignal) {
       LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal);
       return;
     }
   }
-  SignalInfo signalInfo = { aSignal, aCallback };
+
+  // Save current signal action so it can be restored later. Respect handlers
+  // that have been set to SIG_IGN by non-job-control/non-interactive shell by
+  // not changing their disposition.
+  struct sigaction oldAction = { 0 };
+  if (sigaction(aSignal, nullptr, &oldAction) ||
+      oldAction.sa_handler == SIG_IGN) {
+    LOG("Register Signal(%d) callback failed! (OLDACTION)", aSignal);
+    return;
+  }
+
+  SignalInfo signalInfo = { aSignal, aCallback, oldAction };
   mSignalInfo.AppendElement(signalInfo);
   RegisterSignalHandler(signalInfo.mSignal);
 }
 
 void
+SignalPipeWatcher::UnregisterCallback(uint8_t aSignal)
+{
+  // Restore previous signal action
+  MutexAutoLock lock(mSignalInfoLock);
+  for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
+    if (aSignal == mSignalInfo[i].mSignal) {
+      if (sigaction(aSignal, &mSignalInfo[i].mOldAction, nullptr) == 0) {
+        mSignalInfo.RemoveElementAt(i);
+      } else {
+        LOG("SignalPipeWatcher failed to unregister sig %d.", aSignal);
+      }
+      return;
+    }
+  }
+}
+
+void
 SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal)
 {
   struct sigaction action;
   memset(&action, 0, sizeof(action));
   sigemptyset(&action.sa_mask);
   action.sa_handler = DumpSignalHandler;
 
   if (aSignal) {
--- a/xpcom/base/nsDumpUtils.h
+++ b/xpcom/base/nsDumpUtils.h
@@ -139,26 +139,29 @@ private:
   FifoInfoArray mFifoInfo;
 };
 
 typedef void (*PipeCallback)(const uint8_t aRecvSig);
 struct SignalInfo
 {
   uint8_t mSignal;
   PipeCallback mCallback;
+  struct sigaction mOldAction;
 };
 typedef nsTArray<SignalInfo> SignalInfoArray;
 
 class SignalPipeWatcher : public FdWatcher
 {
 public:
   static SignalPipeWatcher* GetSingleton();
 
   void RegisterCallback(uint8_t aSignal, PipeCallback aCallback);
 
+  void UnregisterCallback(uint8_t aSignal);
+
   void RegisterSignalHandler(uint8_t aSignal = 0);
 
   virtual ~SignalPipeWatcher();
 
   virtual int OpenFd() override;
 
   virtual void StopWatching() override;