bug 1204614 - test for h2 per stream flow control r=hurley
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 14 Sep 2015 14:08:10 -0400
changeset 297244 dfd4e61be0624dba05fb0562f453fa93027a1663
parent 297243 0bf012ea842fa811f1057355fb11259b7bc20487
child 297245 c150293cf55c0fe19435d7eb9ede01a39796fe2d
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley
bugs1204614
milestone43.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 1204614 - test for h2 per stream flow control r=hurley
netwerk/test/unit/test_http2.js
testing/xpcshell/moz-http2/moz-http2.js
--- a/netwerk/test/unit/test_http2.js
+++ b/netwerk/test/unit/test_http2.js
@@ -40,39 +40,44 @@ function checkIsHttp2(request) {
 
 var Http2CheckListener = function() {};
 
 Http2CheckListener.prototype = {
   onStartRequestFired: false,
   onDataAvailableFired: false,
   isHttp2Connection: false,
   shouldBeHttp2 : true,
+  accum : 0,
+  expected: -1,
 
   onStartRequest: function testOnStartRequest(request, ctx) {
     this.onStartRequestFired = true;
     if (!Components.isSuccessCode(request.status))
       do_throw("Channel should have a success code! (" + request.status + ")");
 
     do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
     do_check_eq(request.responseStatus, 200);
     do_check_eq(request.requestSucceeded, true);
   },
 
   onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
     this.onDataAvailableFired = true;
     this.isHttp2Connection = checkIsHttp2(request);
-
+    this.accum += cnt;
     read_stream(stream, cnt);
   },
 
   onStopRequest: function testOnStopRequest(request, ctx, status) {
     do_check_true(this.onStartRequestFired);
     do_check_true(Components.isSuccessCode(status));
     do_check_true(this.onDataAvailableFired);
     do_check_true(this.isHttp2Connection == this.shouldBeHttp2);
+    if (this.expected != -1) {
+      do_check_eq(this.accum, this.expected);
+    }
 
     run_next_test();
     do_test_finished();
   }
 };
 
 /*
  * Support for testing valid multiplexing of streams
@@ -276,16 +281,72 @@ function makeChan(url) {
                              Services.scriptSecurityManager.getSystemPrincipal(),
                              null,      // aTriggeringPrincipal
                              Ci.nsILoadInfo.SEC_NORMAL,
                              Ci.nsIContentPolicy.TYPE_OTHER).QueryInterface(Ci.nsIHttpChannel);
 
   return chan;
 }
 
+var ResumeStalledChannelListener = function() {};
+
+ResumeStalledChannelListener.prototype = {
+  onStartRequestFired: false,
+  onDataAvailableFired: false,
+  isHttp2Connection: false,
+  shouldBeHttp2 : true,
+  resumable : null,
+
+  onStartRequest: function testOnStartRequest(request, ctx) {
+    this.onStartRequestFired = true;
+    if (!Components.isSuccessCode(request.status))
+      do_throw("Channel should have a success code! (" + request.status + ")");
+
+    do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+    do_check_eq(request.responseStatus, 200);
+    do_check_eq(request.requestSucceeded, true);
+  },
+
+  onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+    this.onDataAvailableFired = true;
+    this.isHttp2Connection = checkIsHttp2(request);
+    read_stream(stream, cnt);
+  },
+
+  onStopRequest: function testOnStopRequest(request, ctx, status) {
+    do_check_true(this.onStartRequestFired);
+    do_check_true(Components.isSuccessCode(status));
+    do_check_true(this.onDataAvailableFired);
+    do_check_true(this.isHttp2Connection == this.shouldBeHttp2);
+    this.resumable.resume();
+  }
+};
+
+// test a large download that creates stream flow control and
+// confirm we can do another independent stream while the download
+// stream is stuck
+function test_http2_blocking_download() {
+  var chan = makeChan("https://localhost:" + serverPort + "/bigdownload");
+  var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+  internalChannel.initialRwin = 500000; // make the stream.suspend push back in h2
+  var listener = new Http2CheckListener();
+  listener.expected = 3 * 1024 * 1024;
+  chan.asyncOpen(listener, null);
+  chan.suspend();
+  // wait 5 seconds so that stream flow control kicks in and then see if we
+  // can do a basic transaction (i.e. session not blocked). afterwards resume
+  // channel
+  do_timeout(5000, function() {
+      var simpleChannel = makeChan("https://localhost:" + serverPort + "/");
+      var sl = new ResumeStalledChannelListener();
+      sl.resumable = chan;
+      simpleChannel.asyncOpen(sl, null);
+  });
+}
+
 // Make sure we make a HTTP2 connection and both us and the server mark it as such
 function test_http2_basic() {
   var chan = makeChan("https://localhost:" + serverPort + "/");
   var listener = new Http2CheckListener();
   chan.asyncOpen(listener, null);
 }
 
 function test_http2_basic_unblocked_dep() {
@@ -776,31 +837,33 @@ var tests = [ test_http2_post_big
             , test_http2_cookie_crumbling
             , test_http2_multiplex
             , test_http2_big
             , test_http2_huge_suspended
             , test_http2_post
             , test_http2_patch
             , test_http2_pushapi_1
             , test_http2_continuations
+            , test_http2_blocking_download
             // Add new tests above here - best to add new tests before h1
             // streams get too involved
             // These next two must always come in this order
             , test_http2_h11required_stream
             , test_http2_h11required_session
             , test_http2_retry_rst
             , test_http2_wrongsuite
 
             // cleanup
             , test_complete
             ];
 var current_test = 0;
 
 function run_next_test() {
   if (current_test < tests.length) {
+    dump("starting test number " + current_test + "\n");	
     tests[current_test]();
     current_test++;
     do_test_pending();
   }
 }
 
 // Support for making sure we can talk to the invalid cert the server presents
 var CertOverrideListener = function(host, port, bits) {
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -92,16 +92,35 @@ runlater.prototype = {
   resp : null,
 
   onTimeout : function onTimeout() {
     this.resp.writeHead(200);
     this.resp.end("It's all good 750ms.");
   }
 };
 
+var moreData = function() {};
+moreData.prototype = {
+  req : null,
+  resp : null,
+  iter: 3,
+
+  onTimeout : function onTimeout() {
+    // 1mb of data
+    content = generateContent(1024*1024);
+    this.resp.write(content); // 1mb chunk
+    this.iter--;
+      if (!this.iter) {
+	  this.resp.end();
+      } else {
+	  setTimeout(executeRunLater, 1, this);
+      }
+  }
+};
+
 function executeRunLater(arg) {
   arg.onTimeout();
 }
 
 var h11required_conn = null;
 var h11required_header = "yes";
 var didRst = false;
 var rstConnection = null;
@@ -312,16 +331,28 @@ function handleRequest(req, res) {
   else if (u.pathname === "/h11required_stream") {
     if (req.httpVersionMajor === 2) {
       h11required_conn = req.stream.connection;
       res.stream.reset('HTTP_1_1_REQUIRED');
       return;
     }
   }
 
+  else if (u.pathname === "/bigdownload") {
+
+    res.setHeader('Content-Type', 'text/html');
+    res.writeHead(200);
+
+    var rl = new moreData();
+    rl.req = req;
+    rl.resp = res;
+    setTimeout(executeRunLater, 1, rl);
+    return;
+  }
+
   else if (u.pathname === "/h11required_session") {
     if (req.httpVersionMajor === 2) {
       if (h11required_conn !== req.stream.connection) {
         h11required_header = "no";
       }
       res.stream.connection.close('HTTP_1_1_REQUIRED', res.stream.id - 2);
       return;
     } else {