Bug 1168760 - Clarify the lazy-actor and parent-child-setup docs; r=ochameau
authorPatrick Brosset <pbrosset@mozilla.com>
Wed, 27 May 2015 13:55:57 +0200
changeset 268001 0c42ed8fd8c826a8581b980d8211379c908f21ce
parent 267977 67e5dc5938713a839411c7131a12c4250f9f6296
child 551155 866dbf795a006f842d272e356056e2c77240c1c3
push id2292
push userpbrosset@mozilla.com
push dateWed, 27 May 2015 11:59:49 +0000
reviewersochameau
bugs1168760
milestone41.0a1
Bug 1168760 - Clarify the lazy-actor and parent-child-setup docs; r=ochameau
toolkit/devtools/server/docs/actor-e10s-handling.md
toolkit/devtools/server/docs/actor-registration.md
toolkit/devtools/server/docs/lazy-actor-modules.md
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/docs/actor-e10s-handling.md
@@ -0,0 +1,102 @@
+# How to handle E10S in actors
+
+In multi-process environments, most devtools actors are created and initialized in the child content process, to be able to access the resources they are exposing to the toolbox. But sometimes, these actors need to access things in the parent process too. Here's why and how.
+
+## Use case and examples
+
+Some actors need to exchange messages between the parent and the child process (typically when some components aren't available in the child process).
+
+E.g. the **director-manager** needs to ask the list of installed **director scripts** from
+the **director-registry** running in the parent process.
+
+To that end, there's a parent/child setup mechanism at `DebuggerServer` level that can be used.
+
+When the actor is loaded for the first time in the `DebuggerServer` running in the child process, it may decide to run a setup procedure to load a module in the parent process with which to communicate.
+
+E.g. in the **director-registry**:
+
+```
+  const {DebuggerServer} = require("devtools/server/main");
+
+  // Setup the child<->parent communication only if the actor module
+  // is running in a child process.
+  if (DebuggerServer.isInChildProcess) {
+    setupChildProcess();
+  }
+
+  function setupChildProcess() {
+    DebuggerServer.setupInParent({
+      module: "devtools/server/actors/director-registry",
+      setupParent: "setupParentProcess"
+    });
+    // ...
+  }
+```
+
+The `setupChildProcess` helper defined and used in the previous example uses the `DebuggerServer.setupInParent` to run a given setup function in the parent process Debugger Server, e.g. in the **director-registry** module.
+
+With this, the `DebuggerServer` running in the parent process will require the requested module (**director-registry**) and call its `setupParentProcess` function (which should be exported on the module).
+
+The `setupParentProcess` function will receive a parameter that contains a reference to the **MessageManager** and a prefix that should be used to send/receive messages between the child and parent processes.
+
+See below an example implementation of a `setupParent` function in the parent process:
+
+```
+let gTrackedMessageManager = new Set();
+exports.setupParentProcess = function setupParentProcess({ mm, prefix }) {
+  // Prevent multiple subscriptions on the same messagemanager.
+  if (gTrackedMessageManager.has(mm)) { return; }
+  gTrackedMessageManager.add(mm);
+
+  // Start listening for messages from the actor in the child process.
+  mm.addMessageListener("debug:some-message-name", handleChildRequest);
+
+  function handleChildRequest(msg) {
+    switch (msg.json.method) {
+      case "get":
+        return doGetInParentProcess(msg.json.args[0]);
+        break;
+      case "list":
+        return doListInParentProcess();
+        break;
+      default:
+        console.error("Unknown method name", msg.json.method);
+        throw new Error("Unknown method name");
+    }
+  }
+
+  // Listen to the disconnection message to clean-up.
+  DebuggerServer.once("disconnected-from-child:" + prefix, handleMessageManagerDisconnected);
+
+  function handleMessageManagerDisconnected(evt, { mm: disconnected_mm }) {
+    // filter out not subscribed message managers
+    if (disconnected_mm !== mm || !gTrackedMessageManager.has(mm)) {
+      return;
+    }
+
+    gTrackedMessageManager.delete(mm);
+
+    // unregister for director-script requests handlers from the parent process (if any)
+    mm.removeMessageListener("debug:director-registry-request", handleChildRequest);
+  }
+```
+
+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.
+
+## Summary of the setup flow
+
+In the child process:
+
+* The `DebuggerServer` loads an actor module,
+* the actor module checks `DebuggerServer.isInChildProcess` to know whether it runs in a child process or not,
+* the actor module then uses the `DebuggerServer.setupInParent` helper to start setting up a parent-process counterpart,
+* the `DebuggerServer.setupInParent` helper asks the parent process to run the required module's setup function,
+* the actor module uses the `DebuggerServer.parentMessageManager.sendSyncMessage` and `DebuggerServer.parentMessageManager.addMessageListener` helpers to send or listen to message.
+
+In the parent process:
+
+* The DebuggerServer receives the `DebuggerServer.setupInParent` request,
+* tries to load the required module,
+* tries to call the `module[setupParent]` function with the frame message manager and the prefix as parameters `{ mm, prefix }`,
+* the `setupParent` function then uses the mm to subscribe the messagemanager events,
+* the `setupParent` function also uses the DebuggerServer object to subscribe *once* to the `"disconnected-from-child:PREFIX"` event to unsubscribe from messagemanager events.
\ No newline at end of file
rename from toolkit/devtools/server/docs/lazy-actor-modules.md
rename to toolkit/devtools/server/docs/actor-registration.md
--- a/toolkit/devtools/server/docs/lazy-actor-modules.md
+++ b/toolkit/devtools/server/docs/actor-registration.md
@@ -1,116 +1,33 @@
-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:
+# How to register a tab actor or a global actor
 
-```js
-    DebuggerServer.registerModule("devtools/server/actors/webapps", {
-      prefix: "webapps",
-      constructor: "WebappsActor",
-      type: { global: true }
-    });
-```
+## The DebuggerServer.registerModule function
 
-register a tab actor:
+To register a global 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();
-}
-...
+DebuggerServer.registerModule("devtools/server/actors/webapps", {
+  prefix: "webapps",
+  constructor: "WebappsActor",
+  type: { global: true }
+});
 ```
 
-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;
+To register a tab actor:
 
-  DebuggerServer.setupInParent({
-    module: "devtools/server/actors/director-registry",
-    setupParent: "setupParentProcess"
-  });
-
-  ...
+```
+DebuggerServer.registerModule("devtools/server/actors/webconsole", {
+  prefix: "console",
+  constructor: "WebConsoleActor",
+  type: { tab: true }
+});
 ```
 
-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
- */
+If you are adding a new built-in devtools actor, you should be registering it using `DebuggerServer.registerModule` in `addBrowserActors` or `addTabActors` in `/toolkit/devtools/server/main.js`.
 
-let gTrackedMessageManager = new Set();
-
-exports.setupParentProcess = function setupParentProcess({ mm, prefix }) {
-  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:" + prefix, handleMessageManagerDisconnected);
+If you are adding a new actor for an add-on, you should call `DebuggerServer.registerModule` directly from your add-on code.
 
-  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
+## A note about lazy registration
 
-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 prefix
-  in the json parameter **{ mm, prefix }**
-  - 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:PREFIX"** event (needed to unsubscribe the messagemanager events)
+The `DebuggerServer` loads and creates all of the actors lazily to keep the initial memory usage down (which is extremely important on lower end devices).
+
+It becomes especially important when debugging apps on b2g or pages with e10s when there are more than one process, because that's when we need to spawn a `DebuggerServer` per process (it may not be immediately obvious that the server in the main process is mostly only here for piping messages to the actors in the child process).