Bug 1392411 Part 2 - Report stacks for websocket construction in net monitor, r=ochameau.
☠☠ backed out by a67530e635b1 ☠ ☠
authorBrian Hackett <bhackett1024@gmail.com>
Fri, 19 Apr 2019 07:42:58 -1000
changeset 474191 fce11a50c0589bb63a61abc716ea05ca1c53f12f
parent 474190 2e2f3fc37e2eca6e5451c04d395ccf435c3a067c
child 474192 1c23ba0f835f1a097ba5249bbef8e5c79308ae29
push id36026
push usermalexandru@mozilla.com
push dateFri, 17 May 2019 09:30:40 +0000
treeherdermozilla-central@839cdad764d7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1392411
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1392411 Part 2 - Report stacks for websocket construction in net monitor, r=ochameau. Differential Revision: https://phabricator.services.mozilla.com/D28229
devtools/server/actors/network-event.js
devtools/server/actors/network-monitor.js
devtools/server/actors/network-monitor/stack-trace-collector.js
--- a/devtools/server/actors/network-event.js
+++ b/devtools/server/actors/network-event.js
@@ -257,28 +257,36 @@ const NetworkEventActor = protocol.Actor
    *         The response packet - stack trace.
    */
   async getStackTrace() {
     let stacktrace = this._stackTrace;
     // If _stackTrace was "true", it means we are in parent process
     // and the stack is available from the content process.
     // Fetch it lazily from here via the message manager.
     if (stacktrace && typeof stacktrace == "boolean") {
+      let id;
+      if (this._cause.type == "websocket") {
+        // Convert to a websocket URL, as in onNetworkEvent.
+        id = this._request.url.replace(/^http/, "ws");
+      } else {
+        id = this._channelId;
+      }
+
       const messageManager = this.netMonitorActor.messageManager;
       stacktrace = await new Promise(resolve => {
         const onMessage = ({ data }) => {
           const { channelId, stack } = data;
-          if (channelId == this._channelId) {
+          if (channelId == id) {
             messageManager.removeMessageListener("debug:request-stack:response",
               onMessage);
             resolve(stack);
           }
         };
         messageManager.addMessageListener("debug:request-stack:response", onMessage);
-        messageManager.sendAsyncMessage("debug:request-stack:request", this._channelId);
+        messageManager.sendAsyncMessage("debug:request-stack:request", id);
       });
       this._stackTrace = stacktrace;
     }
 
     return {
       stacktrace,
     };
   },
--- a/devtools/server/actors/network-monitor.js
+++ b/devtools/server/actors/network-monitor.js
@@ -211,24 +211,35 @@ const NetworkMonitorActor = ActorClassWi
 
     // map channel to actor so we can associate future events with it
     this._netEvents.set(channelId, actor);
     return actor;
   },
 
   // This method is called by NetworkMonitor instance when a new request is fired
   onNetworkEvent(event) {
-    const { channelId } = event;
+    const { channelId, cause, url } = event;
 
     const actor = this.getNetworkEventActor(channelId);
     this._netEvents.set(channelId, actor);
 
-    event.cause.stacktrace = this.stackTraces.has(channelId);
+    // Find the ID which the stack trace collector will use to save this
+    // channel's stack trace.
+    let id;
+    if (cause.type == "websocket") {
+      // Use the URL, but convert from the http URL which this channel uses to
+      // the original websocket URL which triggered this channel's construction.
+      id = url.replace(/^http/, "ws");
+    } else {
+      id = channelId;
+    }
+
+    event.cause.stacktrace = this.stackTraces.has(id);
     if (event.cause.stacktrace) {
-      this.stackTraces.delete(channelId);
+      this.stackTraces.delete(id);
     }
     actor.init(event);
 
     this._networkEventActorsByURL.set(actor._request.url, actor);
 
     this.conn.sendActorEvent(this.parentID, "networkEvent", { eventActor: actor.form() });
     return actor;
   },
--- a/devtools/server/actors/network-monitor/stack-trace-collector.js
+++ b/devtools/server/actors/network-monitor/stack-trace-collector.js
@@ -38,34 +38,46 @@ StackTraceCollector.prototype = {
     Services.obs.removeObserver(this, "network-monitor-alternate-stack");
     ChannelEventSinkFactory.getService().unregisterCollector(this);
     for (const { messageManager } of this.netmonitors) {
       messageManager.removeMessageListener("debug:request-stack:request",
         this.onGetStack);
     }
   },
 
-  _saveStackTrace(channel, stacktrace) {
-    if (this.stacktracesById.has(channel.channelId)) {
+  _saveStackTrace(id, stacktrace) {
+    if (this.stacktracesById.has(id)) {
       // We can get up to two stack traces for the same channel: one each from
       // the two observer topics we are listening to. Use the first stack trace
       // which is specified, and ignore any later one.
       return;
     }
     for (const { messageManager } of this.netmonitors) {
       messageManager.sendAsyncMessage("debug:request-stack-available", {
-        channelId: channel.channelId,
+        channelId: id,
         stacktrace: stacktrace && stacktrace.length > 0,
       });
     }
-    this.stacktracesById.set(channel.channelId, stacktrace);
+    this.stacktracesById.set(id, stacktrace);
   },
 
   observe(subject, topic, data) {
-    const channel = subject.QueryInterface(Ci.nsIHttpChannel);
+    let channel, id;
+    try {
+      channel = subject.QueryInterface(Ci.nsIHttpChannel);
+      id = channel.channelId;
+    } catch (e) {
+      // WebSocketChannels do not have IDs, so use the URL. When a WebSocket is
+      // opened in a content process, a channel is created locally but the HTTP
+      // channel for the connection lives entirely in the parent process. When
+      // the server code running in the parent sees that HTTP channel, it will
+      // look for the creation stack using the websocket's URL.
+      channel = subject.QueryInterface(Ci.nsIWebSocketChannel);
+      id = channel.URI.spec;
+    }
 
     if (!matchRequest(channel, this.filters)) {
       return;
     }
 
     const stacktrace = [];
     switch (topic) {
       case "http-on-opening-request": {
@@ -116,33 +128,33 @@ StackTraceCollector.prototype = {
           frame = frame.parent || frame.asyncParent;
         }
         break;
       }
       default:
         throw new Error("Unexpected observe() topic");
     }
 
-    this._saveStackTrace(channel, stacktrace);
+    this._saveStackTrace(id, stacktrace);
   },
 
   // eslint-disable-next-line no-shadow
   onChannelRedirect(oldChannel, newChannel, flags) {
     // We can be called with any nsIChannel, but are interested only in HTTP channels
     try {
       oldChannel.QueryInterface(Ci.nsIHttpChannel);
       newChannel.QueryInterface(Ci.nsIHttpChannel);
     } catch (ex) {
       return;
     }
 
     const oldId = oldChannel.channelId;
     const stacktrace = this.stacktracesById.get(oldId);
     if (stacktrace) {
-      this._saveStackTrace(newChannel, stacktrace);
+      this._saveStackTrace(newChannel.channelId, stacktrace);
     }
   },
 
   getStackTrace(channelId) {
     const trace = this.stacktracesById.get(channelId);
     this.stacktracesById.delete(channelId);
     return trace;
   },