Bug 1411316 - necko api for cancelling all transactions r=dragana
test by: :kershaw
MozReview-Commit-ID: BwjsDMiEGZY
--- 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]