Bug 980481 - RemoteDebuggerServer e10s setupParent helper. r=ochameau
authorLuca Greco <luca.greco@alcacoop.it>
Thu, 27 Nov 2014 05:52:00 -0500
changeset 218504 351d3af2dfd3914df57f9fcc796602809e7a0dc2
parent 218503 d689a0051c5f6acb1dccf5c673641d229251f4aa
child 218505 ab3136b93cd054ca83b08435595afa89ef7ac269
push id10275
push userryanvm@gmail.com
push dateFri, 05 Dec 2014 19:39:15 +0000
treeherderfx-team@ab3136b93cd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs980481
milestone37.0a1
Bug 980481 - RemoteDebuggerServer e10s setupParent helper. r=ochameau Changes to the RemoteDebuggerServer main.js and child.js to provide hooks needed by modules which want to propagate their data lazily between the e10s parent and child processes.
toolkit/devtools/server/child.js
toolkit/devtools/server/docs/lazy-actor-modules.md
toolkit/devtools/server/main.js
--- a/toolkit/devtools/server/child.js
+++ b/toolkit/devtools/server/child.js
@@ -16,16 +16,22 @@ let chromeGlobal = this;
   const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
   const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
   if (!DebuggerServer.childID) {
     DebuggerServer.childID = 1;
   }
 
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
+
+    // message manager helpers provided for actor module parent/child message exchange
+    DebuggerServer.parentMessageManager = {
+      sendSyncMessage: sendSyncMessage,
+      addMessageListener: addMessageListener
+    };
   }
 
   // In case of apps being loaded in parent process, DebuggerServer is already
   // initialized, but child specific actors are not registered.
   // Otherwise, for apps in child process, we need to load actors the first
   // time we load child.js
   DebuggerServer.addChildActors();
 
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/docs/lazy-actor-modules.md
@@ -0,0 +1,116 @@
+Lazy Actor Modules and E10S setup
+---------------------------------
+
+The **DebuggerServer** loads and creates most of the actors lazily to keep
+the initial memory usage down (which is extremely important on lower end devices).
+
+## Register a lazy global/tab actor module
+
+register a global actor:
+
+```js
+    DebuggerServer.registerModule("devtools/server/actors/webapps", {
+      prefix: "webapps",
+      constructor: "WebappsActor",
+      type: { global: true }
+    });
+```
+
+register a tab actor:
+
+```js
+    DebuggerServer.registerModule("devtools/server/actors/webconsole", {
+      prefix: "console",
+      constructor: "WebConsoleActor",
+      type: { tab: true }
+    });
+```
+
+## E10S Setup
+
+Some of the actor modules needs to exchange messages between the parent and child processes.
+
+E.g. the **director-manager** needs to ask the list installed **director scripts** from
+the **director-registry** running in the parent process) and the parent/child setup
+is lazily directed by the **DebuggerServer**.
+
+When the actor is loaded for the first time in the the **DebuggerServer** running in the
+child process, it has the chances to run its setup procedure, e.g. in the **director-registry**:
+
+```js
+...
+const {DebuggerServer} = require("devtools/server/main");
+
+...
+
+// skip child setup if this actor module is not running in a child process
+if (DebuggerServer.isInChildProcess) {
+  setupChildProcess();
+}
+...
+```
+
+The above setupChildProcess helper will use the **DebuggerServer.setupInParent**
+to start a setup process in the parent process Debugger Server, e.g. in the the **director-registry**:
+
+```js
+function setupChildProcess() {
+  const { sendSyncMessage } = DebuggerServer.parentMessageManager;
+
+  DebuggerServer.setupInParent({
+    module: "devtools/server/actors/director-registry",
+    setupParent: "setupParentProcess"
+  });
+
+  ...
+```
+
+in the parent process, the **DebuggerServer** will require the requested module
+and call the **setupParent** exported helper with the **MessageManager**
+connected to the child process as parameter, e.g. in the **director-registry**:
+
+```js
+/**
+ * E10S parent/child setup helpers
+ */
+
+let gTrackedMessageManager = new Set();
+
+exports.setupParentProcess = function setupParentProcess({ mm, childID }) {
+  if (gTrackedMessageManager.has(mm)) { return; }
+  gTrackedMessageManager.add(mm);
+
+  // listen for director-script requests from the child process
+  mm.addMessageListener("debug:director-registry-request", handleChildRequest);
+
+  // time to unsubscribe from the disconnected message manager
+  DebuggerServer.once("disconnected-from-child:" + childID, handleMessageManagerDisconnected);
+
+  function handleMessageManagerDisconnected(evt, { mm: disconnected_mm }) {
+    ...
+  }
+```
+
+The DebuggerServer emits "disconnected-from-child:CHILDID" events to give the actor modules
+the chance to cleanup their handlers registered on the disconnected message manager.
+
+## E10S setup flow
+
+In the child process:
+- DebuggerServer loads an actor module
+  - the actor module check DebuggerServer.isInChildProcess 
+    - the actor module calls the DebuggerServer.setupInParent helper
+    - the DebuggerServer.setupInParent helper asks to the parent process
+      to run the required setup
+    - the actor module use the DebuggerServer.parentMessageManager.sendSyncMessage,
+      DebuggerServer.parentMessageManager.addMessageListener helpers to send requests
+      or to subscribe message handlers
+      
+In the parent process:
+- The DebuggerServer receives the DebuggerServer.setupInParent request
+- it tries to load the required module
+- it tries to call the **mod[setupParent]** method with the frame message manager and the childID
+  in the json parameter **{ mm, childID }**
+  - the module setupParent helper use the mm to subscribe the messagemanager events
+  - the module setupParent helper use the DebuggerServer object to subscribe *once* the
+    **"disconnected-from-child:CHILDID"** event (needed to unsubscribe the messagemanager events)
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -132,17 +132,17 @@ function ModuleAPI() {
         DebuggerServer.removeTabActor(factory);
       }
       activeTabActors = null;
       for (let factory of activeGlobalActors) {
         DebuggerServer.removeGlobalActor(factory);
       }
       activeGlobalActors = null;
     }
-  }
+  };
 };
 
 /***
  * Public API
  */
 var DebuggerServer = {
   _listeners: [],
   _initialized: false,
@@ -208,17 +208,17 @@ var DebuggerServer = {
 
     for (let connID of Object.getOwnPropertyNames(this._connections)) {
       this._connections[connID].close();
     }
 
     for (let id of Object.getOwnPropertyNames(gRegisteredModules)) {
       this.unregisterModule(id);
     }
-    gRegisteredModules = {};
+    gRegisteredModules = Object.create(null);
 
     this.closeAllListeners();
     this.globalActorFactories = {};
     this.tabActorFactories = {};
     this._transportInitialized = false;
     this._initialized = false;
 
     dumpn("Debugger server is shut down.");
@@ -707,16 +707,49 @@ var DebuggerServer = {
                              "message-manager-disconnect", false);
 
     events.on(aConnection, "closed", onDisconnect);
 
     return deferred.promise;
   },
 
   /**
+   * Check if the caller is running in a content child process.
+   *
+   * @return boolean
+   *         true if the caller is running in a content
+   */
+  get isInChildProcess() !!this.parentMessageManager,
+
+  /**
+   * In a content child process, ask the DebuggerServer in the parent process
+   * to execute a given module setup helper.
+   *
+   * @param module
+   *        The module to be required
+   * @param setupParent
+   *        The name of the setup helper exported by the above module
+   *        (setup helper signature: function ({mm}) { ... })
+   * @return boolean
+   *         true if the setup helper returned successfully
+   */
+  setupInParent: function({ module, setupParent }) {
+    if (!this.isInChildProcess) {
+      return false;
+    }
+
+    let { sendSyncMessage } = DebuggerServer.parentMessageManager;
+
+    return sendSyncMessage("debug:setup-in-parent", {
+      module: module,
+      setupParent: setupParent
+    });
+  },
+
+  /**
    * Connect to a child process.
    *
    * @param object aConnection
    *        The debugger server connection to use.
    * @param nsIDOMElement aFrame
    *        The browser element that holds the child process.
    * @param function [aOnDisconnect]
    *        Optional function to invoke when the child is disconnected.
@@ -731,16 +764,43 @@ var DebuggerServer = {
              .messageManager;
     mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false);
 
     let actor, childTransport;
     let prefix = aConnection.allocID("child");
     let childID = null;
     let netMonitor = null;
 
+    // provides hook to actor modules that need to exchange messages
+    // between e10s parent and child processes
+    let onSetupInParent = function (msg) {
+      let { module, setupParent } = msg.json;
+      let m, fn;
+
+      try {
+        m = require(module);
+
+        if (!setupParent in m) {
+          dumpn("ERROR: module '" + module + "' does not export '" + setupParent + "'");
+          return false;
+        }
+
+        m[setupParent]({ mm: mm, childID: childID });
+
+        return true;
+      } catch(e) {
+        let error_msg = "exception during actor module setup running in the parent process: ";
+        DevToolsUtils.reportException(error_msg + e);
+        dumpn("ERROR: " + error_msg + " \n\t module: '" + module + "' \n\t setupParent: '" + setupParent + "'\n" +
+              DevToolsUtils.safeErrorString(e));
+        return false;
+      }
+    };
+    mm.addMessageListener("debug:setup-in-parent", onSetupInParent);
+
     let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
       mm.removeMessageListener("debug:actor", onActorCreated);
 
       childID = msg.json.childID;
 
       // Pipe Debugger message from/to parent/child via the message manager
       childTransport = new ChildDebuggerTransport(mm, prefix);
       childTransport.hooks = {
@@ -760,16 +820,23 @@ var DebuggerServer = {
 
       deferred.resolve(actor);
     }).bind(this);
     mm.addMessageListener("debug:actor", onActorCreated);
 
     let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
       if (subject == mm) {
         Services.obs.removeObserver(onMessageManagerDisconnect, topic);
+
+        // provides hook to actor modules that need to exchange messages
+        // between e10s parent and child processes
+        this.emit("disconnected-from-child:" + childID, { mm: mm, childID: childID });
+
+        mm.removeMessageListener("debug:setup-in-parent", onSetupInParent);
+
         if (childTransport) {
           // If we have a child transport, the actor has already
           // been created. We need to stop using this message manager.
           childTransport.close();
           childTransport = null;
           aConnection.cancelForwarding(prefix);
 
           // ... and notify the child process to clean the tab actors.