Bug 61873 - [V8] Poor performance of XHR streaming
Summary: [V8] Poor performance of XHR streaming
Status: RESOLVED INVALID
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebCore JavaScript (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Nobody
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-06-01 11:10 PDT by goberman
Modified: 2014-12-16 00:48 PST (History)
4 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description goberman 2011-06-01 11:10:23 PDT
I am not sure if XHR implementation is part of WebKit or Chrome, but I will try it...

My application has a server that streams market data. One of the mechanisms uses XMLHttpRequest to stream data. As you know responseText property of XHR keeps growing till response is completed. I implemented periodic server disconnects to avoid memory leak, but encountered another problem: with Chrome cpu steadily keeps climbing as size of the responseText increases. 
If I stream couple MEG, CPU reaches close to 100%, but once server closes connection, CPU drops to 0. I stream about 300 messages per second.
Clearly this is something that is not efficient in this implementation (I do not rule out JavaScript enefficiency).

The test to duplicate this is below:
SERVER 

package test;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class WebFrameworkServletXHRReconnects extends HttpServlet {
	private static final String message = "<activ>{\"m\":4,\"r\":0,\"e\":1,\"s\":[\"MSFT.\",0],\"f\":[[55,6,317,2],[54,6,825,3],[52,4,54705518],[12,6,26865,3]]}</activ>";
	private static int iteration;
	private SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss");

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("begin " + iteration++);
		//System.out.println("Begin for session: " + request.getSession(true).getId() + " " + response.getWriter());
		
		response.setHeader("pragma", "no-cache,no-store");
		response.setHeader("cache-control", "no-cache,no-store,max-age=0,max-stale=0");
	    response.setHeader("Access-Control-Allow-Origin", "*");
		response.setContentType("text/json");
		
		PrintWriter out = response.getWriter();
		
		int sentBytes = 0;
		int i = 0;
		while (sentBytes < 1024 * 1024 * 10) {
			out.print(message);
			out.flush();
			
			sentBytes += message.length();
			
			i++;
			
			if (i % 10 == 0) {
				try {
					Thread.sleep(40);
				} catch (InterruptedException e) {}
			}
				
			if (i % 100 == 0) {
				System.out.println(formatter.format(new Date()) + " " + i);
			}
		}
	}
}

CLIENT:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
<html>
<head>
    <title>test</title>
</head>
<body>
    <input type="button" value="TEST" onclick="test(); return false;" />

    <script type="text/javascript">
        var api;

        var domElement = document.getElementsByTagName('body')[0];

        function test() {
            api = new XMLHttpRequest;
        	
            api.onreadystatechange = onreadystatechange;
            api.onerror = onerror;

            debug('connect');
            api.open("GET", "http://172.16.16.250/Test/Controller", true);

            api.send("");
        }

	    function onreadystatechange() {
	    	//debug('readyState' + api.readyState);
	        switch (api.readyState) {
	            case 4:
		            debug('done: ');
			    test();
	                break;
	            case 3:
	                processMessage();
	                break;
	        }
	    }

        function onerror() {
            debug('onerror');
	    test();
	}

	function processMessage() {
            var buffer = api.responseText;
	}
	    
        function debug(message) {
            var currentTime = new Date();
            var hours = currentTime.getHours();
  	    var minutes = currentTime.getMinutes();
  	    var seconds = currentTime.getSeconds();
            
            var oText = document.createTextNode(hours + ":" + minutes + ":" + seconds + " " + message);
            var oDiv = document.createElement('div');
            oDiv.appendChild(oText);

            domElement.insertBefore(oDiv, domElement.hasChildNodes() ? domElement.childNodes[0] : null);
        }
    </script>
</body>
</html>
Comment 1 Alexey Proskuryakov 2011-06-01 17:43:42 PDT
This sounds like a bug for webkit.org to me.

> with Chrome cpu steadily keeps climbing as size of the responseText increases

Are you saying that this only happens in Chrome, not in Safari?
Comment 2 goberman 2011-06-01 18:31:30 PDT
Tried it with Safari 5 on XP. There is a different problem - it allocates memory like crazy and cannot keep up at all. With 300 messages/s my machine runs out of memory in couple minutes - it allocated 1GEG of memory and Safari crashed.
Comment 3 goberman 2011-06-01 18:46:16 PDT
Interestingly Safari can keep up with the same message volume delivered by EventSource with 0% CPU. EventSource, however, has a memory leak I reported before (https://bugs.webkit.org/show_bug.cgi?id=61863) and also does not allow cross-domain calls.
You can see that both XHR and EventSource are far from perfect for streaming data.
Comment 4 Alexey Proskuryakov 2011-06-01 23:11:02 PDT
Could you please try a WebKit nightly build from <http://nightly.webkit.org>? It doesn't replace your Safari installation, so it's very easy to test with.
Comment 5 goberman 2011-06-02 07:20:00 PDT
I downloaded webkit and running the test with both WebKit (it apparently launches Safari for some reason) and Chrome 11.0.696.71 side by side now.

Chrome exhibits the problem with CPU I described above with "saw" pattern. Increasing as responseText grows and dropping to 0 after reconnect. But memory stays pretty much constant at around 22MEG for one instance and 44MEG for another (Chrome has 2 process instances).

WebKit (Safari) has CPU close to 0% and it does not increase much. Good!
Memory leak that was apparent in Safari 5 is gone. Also good. But memory keeps increasing: it started at 47MEG and is at 81MEG now after running for 20 minutes. I really need to run it for several hours to determine if it is a memory leak or not.

Could you explain these results? There was obviously some change made to fix something in the latest WebKit. Was it not fully propagated to Chrome?
Comment 6 Alexey Proskuryakov 2011-06-02 09:01:25 PDT
> it apparently launches Safari for some reason

We once had a blog post explaining that, <http://www.webkit.org/blog/101/back-to-basics/>.

> Could you explain these results?

Chrome uses a different JavaScript implementation than other WebKit ports, and the bug is apparently in how WebCore interacts with JavaScript.

Since this appears to be (partially or fully) fixed in JavaScriptCore bindings, I'm re-titling this to track the Chrome/v8 issue only. Please file a separate bug about Safari if the behavior you observe with WebKit nightlies is not as good as it should be yet.
Comment 7 goberman 2011-06-02 09:43:52 PDT
Alexey, thanks,
What about Chrome? Will someone work on the CPU issue based on this bug or I need to file it somewhere else to get it resolved? I am still not clear what version of WebKit memory leak was fixed with. It appears that Chrome currently uses the "memory leak free" WebKit build.
Comment 8 anton muhin 2011-06-03 04:37:19 PDT
I will have a look today/next week.

Thanks a lot for reporting!

(In reply to comment #7)
> Alexey, thanks,
> What about Chrome? Will someone work on the CPU issue based on this bug or I need to file it somewhere else to get it resolved? I am still not clear what version of WebKit memory leak was fixed with. It appears that Chrome currently uses the "memory leak free" WebKit build.
Comment 9 Brian Burg 2014-12-16 00:48:21 PST
Closing some V8-related work items.