Bug 1411316 - necko api for cancelling all transactions r=dragana draft
authorPatrick McManus <mcmanus@ducksong.com>
Tue, 24 Oct 2017 09:17:02 -0700
changeset 710899 7fa1640fde63907d96b6afdadda788e6fd4298d4
parent 710859 a16f868d488b41c6871c705b0a15c1b5b3deb4ce
child 743684 b60b168f7dfa5c242cae75b000150e24e23bb376
push id92934
push userbmo:mcmanus@ducksong.com
push dateTue, 12 Dec 2017 18:25:17 +0000
reviewersdragana
bugs1411316
milestone59.0a1
Bug 1411316 - necko api for cancelling all transactions r=dragana test by: :kershaw MozReview-Commit-ID: BwjsDMiEGZY
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/test/unit/test_bug1411316_http1.js
netwerk/test/unit/xpcshell.ini
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -2155,23 +2155,25 @@ nsHttpConnectionMgr::GetSpdyActiveConn(n
     LOG(("GetSpdyActiveConn() request for ent %p %s "
          "did not find an active connection\n", ent, ci->HashKey().get()));
     return nullptr;
 }
 
 //-----------------------------------------------------------------------------
 
 void
-nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
+nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase *)
 {
+    if (!OnSocketThread()) {
+        Unused << PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections);
+        return;
+    }
+
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
-    LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
-
-    gHttpHandler->StopRequestTokenBucket();
-
+    LOG(("nsHttpConnectionMgr::AbortAndCloseAllConnections\n"));
     for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
         RefPtr<nsConnectionEntry> ent = iter.Data();
 
         // Close all active connections.
         while (ent->mActiveConns.Length()) {
             RefPtr<nsHttpConnection> conn(ent->mActiveConns[0]);
             ent->mActiveConns.RemoveElementAt(0);
             DecrementActiveConnCount(conn);
@@ -2219,32 +2221,47 @@ nsHttpConnectionMgr::OnMsgShutdown(int32
             ent->mHalfOpens[i]->Abandon();
         }
 
         MOZ_ASSERT(ent->mHalfOpenFastOpenBackups.Length() == 0 &&
                    !ent->mDoNotDestroy);
         iter.Remove();
     }
 
+    mActiveTransactions[false].Clear();
+    mActiveTransactions[true].Clear();
+}
+
+void
+nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+    LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
+
+    gHttpHandler->StopRequestTokenBucket();
+    AbortAndCloseAllConnections(0, nullptr);
+
+    // If all idle connections are removed we can stop pruning dead
+    // connections.
+    ConditionallyStopPruneDeadConnectionsTimer();
+
     if (mTimeoutTick) {
         mTimeoutTick->Cancel();
         mTimeoutTick = nullptr;
         mTimeoutTickArmed = false;
     }
     if (mTimer) {
       mTimer->Cancel();
       mTimer = nullptr;
     }
     if (mTrafficTimer) {
       mTrafficTimer->Cancel();
       mTrafficTimer = nullptr;
     }
     DestroyThrottleTicker();
-    mActiveTransactions[false].Clear();
-    mActiveTransactions[true].Clear();
 
     mCoalescingHash.Clear();
 
     // signal shutdown complete
     nsCOMPtr<nsIRunnable> runnable =
         new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
                       0, param);
     NS_DispatchToMainThread(runnable);
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -90,16 +90,19 @@ public:
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called on any thread.
     //-------------------------------------------------------------------------
 
     // Schedules next pruning of dead connection to happen after
     // given time.
     void PruneDeadConnectionsAfter(uint32_t time);
 
+    // this cancels all outstanding transactions but does not shut down the mgr
+    void AbortAndCloseAllConnections(int32_t, ARefBase *);
+
     // Stops timer scheduled for next pruning of dead connections if
     // there are no more idle connections or active spdy ones
     void ConditionallyStopPruneDeadConnectionsTimer();
 
     // Stops timer used for the read timeout tick if there are no currently
     // active connections.
     void ConditionallyStopTimeoutTick();
 
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -540,16 +540,17 @@ nsHttpHandler::Init()
         // about shutdown ordering.
         obsService->AddObserver(this, "profile-change-net-teardown", true);
         obsService->AddObserver(this, "profile-change-net-restore", true);
         obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
         obsService->AddObserver(this, "net:clear-active-logins", true);
         obsService->AddObserver(this, "net:prune-dead-connections", true);
         // Sent by the TorButton add-on in the Tor Browser
         obsService->AddObserver(this, "net:prune-all-connections", true);
+        obsService->AddObserver(this, "net:cancel-all-connections", true);
         obsService->AddObserver(this, "last-pb-context-exited", true);
         obsService->AddObserver(this, "browser:purge-session-history", true);
         obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
         obsService->AddObserver(this, "application-background", true);
         obsService->AddObserver(this, "psm:user-certificate-added", true);
         obsService->AddObserver(this, "psm:user-certificate-deleted", true);
 
         if (!IsNeckoChild()) {
@@ -2272,16 +2273,20 @@ nsHttpHandler::Observe(nsISupports *subj
         }
     } else if (!strcmp(topic, "profile-change-net-restore")) {
         // initialize connection manager
         rv = InitConnectionMgr();
         MOZ_ASSERT(NS_SUCCEEDED(rv));
     } else if (!strcmp(topic, "net:clear-active-logins")) {
         Unused << mAuthCache.ClearAll();
         Unused << mPrivateAuthCache.ClearAll();
+    } else if (!strcmp(topic, "net:cancel-all-connections")) {
+        if (mConnMgr) {
+            mConnMgr->AbortAndCloseAllConnections(0, nullptr);
+        }
     } else if (!strcmp(topic, "net:prune-dead-connections")) {
         if (mConnMgr) {
             rv = mConnMgr->PruneDeadConnections();
             if (NS_FAILED(rv)) {
                 LOG(("    PruneDeadConnections failed (%08x)\n",
                      static_cast<uint32_t>(rv)));
             }
         }
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_bug1411316_http1.js
@@ -0,0 +1,121 @@
+// Test bug 1411316.
+//
+// Summary:
+// The purpose of this test is to test whether the HttpConnectionMgr really
+// cancel and close all connecitons when get "net:cancel-all-connections".
+//
+// Test step:
+// 1. Create 6 http requests. Server would not process responses and just put
+//    all requests in its queue.
+// 2. Once server receive all 6 requests, call notifyObservers with the
+//    topic "net:cancel-all-connections".
+// 3. We expect that all 6 active connections should be closed with the status
+//    NS_ERROR_ABORT.
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var server = new HttpServer();
+server.start(-1);
+var baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+var maxConnections = 0;
+var debug = false;
+var requestId = 0;
+
+function log(msg) {
+  if (!debug) {
+    return;
+  }
+
+  if (msg) {
+    dump("TEST INFO | " + msg + "\n");
+  }
+}
+
+function make_channel(url) {
+  var request = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+  request.QueryInterface(Ci.nsIHttpChannel);
+  return request;
+}
+
+function serverStopListener() {
+  server.stop();
+}
+
+function createHttpRequest(status) {
+  let uri = baseURL;
+  var chan = make_channel(uri);
+  var listner = new HttpResponseListener(++requestId, status);
+  chan.setRequestHeader("X-ID", requestId, false);
+  chan.setRequestHeader("Cache-control", "no-store", false);
+  chan.asyncOpen2(listner);
+  log("Create http request id=" + requestId);
+}
+
+function setupHttpRequests(status) {
+  log("setupHttpRequests");
+  for (var i = 0; i < maxConnections ; i++) {
+    createHttpRequest(status);
+    do_test_pending();
+  }
+}
+
+function HttpResponseListener(id, onStopRequestStatus)
+{
+  this.id = id
+  this.onStopRequestStatus = onStopRequestStatus;
+};
+
+HttpResponseListener.prototype =
+{
+  onStartRequest: function (request, ctx) {
+  },
+
+  onDataAvailable: function (request, ctx, stream, off, cnt) {
+  },
+
+  onStopRequest: function (request, ctx, status) {
+    log("STOP id=" + this.id + " status=" + status);
+    do_check_true(this.onStopRequestStatus == status);
+    do_test_finished();
+  }
+};
+
+var responseQueue = new Array();
+function setup_http_server()
+{
+  log("setup_http_server");
+  var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+  maxConnections = prefs.getIntPref("network.http.max-persistent-connections-per-server");
+
+  var allDummyHttpRequestReceived = false;
+  // Start server; will be stopped at test cleanup time.
+  server.registerPathHandler('/', function(metadata, response)
+  {
+    var id = metadata.getHeader("X-ID");
+    log("Server recived the response id=" + id);
+
+    response.processAsync();
+    response.setHeader("X-ID", id);
+    responseQueue.push(response);
+
+    if (responseQueue.length == maxConnections) {
+      log("received all http requets");
+      Services.obs.notifyObservers(null, "net:cancel-all-connections");
+    }
+
+  });
+
+  do_register_cleanup(function() {
+    server.stop(serverStopListener);
+  });
+
+}
+
+function run_test() {
+  setup_http_server();
+  setupHttpRequests(Components.results.NS_ERROR_ABORT);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -404,8 +404,9 @@ skip-if = os == "android"
 [test_bug1312774_http1.js]
 [test_1351443-missing-NewChannel2.js]
 [test_bug1312782_http1.js]
 [test_bug1355539_http1.js]
 [test_bug1378385_http1.js]
 [test_tls_flags_separate_connections.js]
 [test_tls_flags.js]
 [test_uri_mutator.js]
+[test_bug1411316_http1.js]