Make process.exit() quit gracefully
authorCheng Zhao <zcbenz@gmail.com>
Fri, 6 Nov 2015 10:27:13 +0000 (18:27 +0800)
committerCheng Zhao <zcbenz@gmail.com>
Fri, 6 Nov 2015 12:25:20 +0000 (20:25 +0800)
Instead of abrupting the whole program immediately, we should close all
windows and release all native resources gracefully on exit. This avoids
possible crashes.

Fix #3350.

atom/browser/api/atom_api_app.cc
atom/browser/api/lib/app.coffee
atom/browser/atom_browser_main_parts.cc
atom/browser/atom_browser_main_parts.h
atom/browser/browser.cc
atom/browser/browser.h
atom/browser/lib/init.coffee
docs/api/app.md

index 04ed06e0138f73800c3caca1fce1754d340854f8..1c5c2c04f145d38761c7eeec8652b58bd83cb47e 100644 (file)
@@ -339,6 +339,7 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder(
   auto browser = base::Unretained(Browser::Get());
   return mate::ObjectTemplateBuilder(isolate)
       .SetMethod("quit", base::Bind(&Browser::Quit, browser))
+      .SetMethod("exit", base::Bind(&Browser::Exit, browser))
       .SetMethod("focus", base::Bind(&Browser::Focus, browser))
       .SetMethod("getVersion", base::Bind(&Browser::GetVersion, browser))
       .SetMethod("setVersion", base::Bind(&Browser::SetVersion, browser))
index ba68076177ae7e1270070eeae38c394858f291ff..4df7aef10bdf2c2733a7dec7e038879884c94171 100644 (file)
@@ -50,7 +50,6 @@ app.getAppPath = ->
 # Be compatible with old API.
 app.once 'ready', -> @emit 'finish-launching'
 app.terminate = app.quit
-app.exit = process.exit
 app.getHomeDir = -> @getPath 'home'
 app.getDataPath = -> @getPath 'userData'
 app.setDataPath = (path) -> @setPath 'userData', path
index 041c9bfc8fbe5200d850eb795727381760360063..0a8c16ca223fc88f68f0cafad4c7c489c0c14d23 100644 (file)
@@ -30,6 +30,7 @@ AtomBrowserMainParts* AtomBrowserMainParts::self_ = NULL;
 
 AtomBrowserMainParts::AtomBrowserMainParts()
     : fake_browser_process_(new BrowserProcess),
+      exit_code_(nullptr),
       browser_(new Browser),
       node_bindings_(NodeBindings::Create(true)),
       atom_bindings_(new AtomBindings),
@@ -47,6 +48,14 @@ AtomBrowserMainParts* AtomBrowserMainParts::Get() {
   return self_;
 }
 
+bool AtomBrowserMainParts::SetExitCode(int code) {
+  if (!exit_code_)
+    return false;
+
+  *exit_code_ = code;
+  return true;
+}
+
 void AtomBrowserMainParts::RegisterDestructionCallback(
     const base::Closure& callback) {
   destruction_callbacks_.push_back(callback);
@@ -118,6 +127,11 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() {
 #endif
 }
 
+bool AtomBrowserMainParts::MainMessageLoopRun(int* result_code) {
+  exit_code_ = result_code;
+  return brightray::BrowserMainParts::MainMessageLoopRun(result_code);
+}
+
 void AtomBrowserMainParts::PostMainMessageLoopStart() {
   brightray::BrowserMainParts::PostMainMessageLoopStart();
 #if defined(OS_POSIX)
index 360b1cba1169f293d3d522dee521cafd8340030c..bb4b204669fd4bc26f2d45607dfa05f462d5d8c5 100644 (file)
@@ -31,6 +31,9 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts {
 
   static AtomBrowserMainParts* Get();
 
+  // Sets the exit code, will fail if the the message loop is not ready.
+  bool SetExitCode(int code);
+
   // Register a callback that should be destroyed before JavaScript environment
   // gets destroyed.
   void RegisterDestructionCallback(const base::Closure& callback);
@@ -42,6 +45,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts {
   void PreEarlyInitialization() override;
   void PostEarlyInitialization() override;
   void PreMainMessageLoopRun() override;
+  bool MainMessageLoopRun(int* result_code) override;
   void PostMainMessageLoopStart() override;
   void PostMainMessageLoopRun() override;
 #if defined(OS_MACOSX)
@@ -66,6 +70,9 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts {
   // with a task runner that will post all work to main loop.
   scoped_refptr<BridgeTaskRunner> bridge_task_runner_;
 
+  // Pointer to exit code.
+  int* exit_code_;
+
   scoped_ptr<Browser> browser_;
   scoped_ptr<JavascriptEnvironment> js_env_;
   scoped_ptr<NodeBindings> node_bindings_;
index e80cb4e60e20842e7e3f545691c79ffa6297927e..57741786520ddbbc120ad3da3d64489a4811d9f8 100644 (file)
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "atom/browser/atom_browser_main_parts.h"
+#include "atom/browser/native_window.h"
 #include "atom/browser/window_list.h"
 #include "base/message_loop/message_loop.h"
 #include "content/public/browser/client_certificate_delegate.h"
@@ -45,6 +46,27 @@ void Browser::Quit() {
   window_list->CloseAllWindows();
 }
 
+void Browser::Exit(int code) {
+  if (!AtomBrowserMainParts::Get()->SetExitCode(code)) {
+    // Message loop is not ready, quit directly.
+    exit(code);
+  } else {
+    // Prepare to quit when all windows have been closed..
+    is_quiting_ = true;
+
+    // Must destroy windows before quitting, otherwise bad things can happen.
+    atom::WindowList* window_list = atom::WindowList::GetInstance();
+    if (window_list->size() == 0) {
+      NotifyAndShutdown();
+    } else {
+      // Unlike Quit(), we do not ask to close window, but destroy the window
+      // without asking.
+      for (NativeWindow* window : *window_list)
+        window->CloseContents(nullptr);  // e.g. Destroy()
+    }
+  }
+}
+
 void Browser::Shutdown() {
   if (is_shutdown_)
     return;
index 447a526de6df066443426111b6f95e80b0987253..e20db080b67a92f4900499149ebe96e525e25d2f 100644 (file)
@@ -42,6 +42,9 @@ class Browser : public WindowListObserver {
   // Try to close all windows and quit the application.
   void Quit();
 
+  // Exit the application immediately and set exit code.
+  void Exit(int code);
+
   // Cleanup everything and shutdown the application gracefully.
   void Shutdown();
 
index 9f92d700c73a192b615f1ee9bd2f7cb3226cbceb..80d2da31b7058cd83a386a134fd8b60cb80db922 100644 (file)
@@ -53,6 +53,9 @@ app = require 'app'
 app.on 'quit', ->
   process.emit 'exit'
 
+# Map process.exit to app.exit, which quits gracefully.
+process.exit = app.exit
+
 # Load the RPC server.
 require './rpc-server'
 
index 00aade7c54ab143592ca026e1cdc2591b7b2966d..fdb9f9980592df9e36d591597c9a21df744d3de0 100644 (file)
@@ -208,6 +208,15 @@ This method guarantees that all `beforeunload` and `unload` event handlers are
 correctly executed. It is possible that a window cancels the quitting by
 returning `false` in the `beforeunload` event handler.
 
+### `app.exit(exitCode)`
+
+* `exitCode` Integer
+
+Exits immediately with `exitCode`.
+
+All windows will be closed immediately without asking user and the `before-quit`
+and `will-quit` events will not be emitted.
+
 ### `app.getAppPath()`
 
 Returns the current application directory.