jQuery ajax() gets premature timeout on Chrome, not Firefox or Safari
Reported by
richb.ha...@gmail.com,
Apr 28 2017
|
|||||
Issue descriptionUserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.29 Safari/537.36 Steps to reproduce the problem: I see this problem with Chrome, but not Firefox or Safari on my Mac OSX 10.12.4 machine: 1. start a jQuery ajax() query with long-ish timeout (6 second), to an address that will not respond. 2. Most of the time, the completion routine gets 'timeout' response as expected 3. 5-10% of the time, the Console shows ERR_CONNECTION_TIMED_OUT 3-5 seconds after the request was sent 4. I HAVE A DEMO: http://testmyinter.net that shows the problem 5. I have also observed this behavior with a simple XMLHttpRequest What is the expected behavior? 100% of the time I would expect the ajax() request to return 'timeout' What went wrong? I get net::ERR_CONNECTION_TIMED_OUT from time to time. Did this work before? N/A Chrome version: 59.0.3071.29 Channel: beta OS Version: OS X 10.12.4 Flash Version: There's additional detail on the jQuery forums, at https://zohodiscussions.com/jquery/topic/jquery-ajax-gets-net-err-connection-timed-out-on-chrome-not-firefox-or-safari As noted above, I observed this "premature timeout" when using XMLHttpRequest. I switched my code to use jQuery ajax() since it is better debugged than my new code. This can be observed at my page, http://TestMyInter.net. I can also provide access to a github repo of my code.
,
Apr 28 2017
Update: I had a great test event. I left the page running overnight with *no failures.* I forced a manual test (my onclick() calls the same routine as the setInterval()) and it immediately failed. Here's more test information: 1) I believe this ticket should have been called "Premature Timeout", since the failures occur when the ajax() call gets the net::ERR_CONNECTION_TIMED_OUT status *before* the specified 6000 msec timeout occurs 2) I've attached an annotated screen shot of the Console 3) I've pasted in the full text from the Console to see if that provides any further info. Please let me know if I can provide additional debugging info. Thanks! 2jquery.min.js:4 OPTIONS http://google.com/?name=http%3A%2F%2FTestMyInter.net&_=1493348591644 send @ jquery.min.js:4 ajax @ jquery.min.js:4 CheckHost @ TestMyInternet.js:57 _loop @ TestMyInternet.js:41 CheckHosts @ TestMyInternet.js:39 (index):1 XMLHttpRequest cannot load http://google.com/?name=http%3A%2F%2FTestMyInter.net&_=1493348591644. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://testmyinter.net' is therefore not allowed access. The response had HTTP status code 405. *** The "Normal 30-second test results *** utilities.js:13 4/28/2017, 6:27:38 AM google.com returned: "OK: ", error utilities.js:13 4/28/2017, 6:27:45 AM 192.168.0.1 returned: "Down: ", timeout utilities.js:13 4/28/2017, 6:27:45 AM 127.0.0.20 returned: "Down: ", timeout 2jquery.min.js:4 OPTIONS http://google.com/?name=http%3A%2F%2FTestMyInter.net&_=1493348591647 send @ jquery.min.js:4 ajax @ jquery.min.js:4 CheckHost @ TestMyInternet.js:57 _loop @ TestMyInternet.js:41 CheckHosts @ TestMyInternet.js:39 (index):1 XMLHttpRequest cannot load http://google.com/?name=http%3A%2F%2FTestMyInter.net&_=1493348591647. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://testmyinter.net' is therefore not allowed access. The response had HTTP status code 405. *** The "Normal 30-second test results *** utilities.js:13 4/28/2017, 6:28:08 AM google.com returned: "OK: ", error utilities.js:13 4/28/2017, 6:28:14 AM 192.168.0.1 returned: "Down: ", timeout utilities.js:13 4/28/2017, 6:28:14 AM 127.0.0.20 returned: "Down: ", timeout *** Forcing a manual check four seconds later (6:28:18) *** utilities.js:13 4/28/2017, 6:28:18 AM Manual check... 2jquery.min.js:4 OPTIONS http://google.com/?name=http%3A%2F%2FTestMyInter.net&_=1493348591650 405 (Method Not Allowed) send @ jquery.min.js:4 ajax @ jquery.min.js:4 CheckHost @ TestMyInternet.js:57 _loop @ TestMyInternet.js:41 CheckHosts @ TestMyInternet.js:39 headers.(anonymous function).onclick @ TestMyInternet.js:14 (index):1 XMLHttpRequest cannot load http://google.com/?name=http%3A%2F%2FTestMyInter.net&_=1493348591650. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://testmyinter.net' is therefore not allowed access. The response had HTTP status code 405. *** Google returns immediately as expected *** utilities.js:13 4/28/2017, 6:28:18 AM google.com returned: "OK: ", error jquery.min.js:4 OPTIONS http://127.0.0.20/?name=http%3A%2F%2FTestMyInter.net&_=1493348591652 net::ERR_CONNECTION_TIMED_OUT *** This is the premature timeout *** send @ jquery.min.js:4 ajax @ jquery.min.js:4 CheckHost @ TestMyInternet.js:57 _loop @ TestMyInternet.js:41 CheckHosts @ TestMyInternet.js:39 headers.(anonymous function).onclick @ TestMyInternet.js:14 *** Premature timeout for 127.0.0.20 *** utilities.js:13 4/28/2017, 6:28:23 AM 127.0.0.20 returned: "OK: ", error jquery.min.js:4 OPTIONS http://192.168.0.1/?name=http%3A%2F%2FTestMyInter.net&_=1493348591651 net::ERR_CONNECTION_TIMED_OUT send @ jquery.min.js:4 ajax @ jquery.min.js:4 CheckHost @ TestMyInternet.js:57 _loop @ TestMyInternet.js:41 CheckHosts @ TestMyInternet.js:39 headers.(anonymous function).onclick @ TestMyInternet.js:14 *** Premature timeout for 192.168.0.1 *** utilities.js:13 4/28/2017, 6:28:23 AM 192.168.0.1 returned: "OK: ", error 2jquery.min.js:4 OPTIONS http://google.com/?name=http%3A%2F%2FTestMyInter.net&_=1493348591653 405 (Method Not Allowed) send @ jquery.min.js:4 ajax @ jquery.min.js:4 CheckHost @ TestMyInternet.js:57 _loop @ TestMyInternet.js:41 CheckHosts @ TestMyInternet.js:39 (index):1 XMLHttpRequest cannot load http://google.com/?name=http%3A%2F%2FTestMyInter.net&_=1493348591653. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://testmyinter.net' is therefore not allowed access. The response had HTTP status code 405. *** Back to the "Normal 30-second test results *** utilities.js:13 4/28/2017, 6:28:38 AM google.com returned: "OK: ", error utilities.js:13 4/28/2017, 6:28:44 AM 192.168.0.1 returned: "Down: ", timeout utilities.js:13 4/28/2017, 6:28:44 AM 127.0.0.20 returned: "Down: ", timeout
,
Apr 28 2017
I haven't yet reproduced this in Chrome 60.0.3082.0 Mac or Chrome 57.0.2987.133 Mac, but I may just not be waiting long enough. To network API team for further triage. A stand-alone repro doing an XHR only to an address expecting to timeout might help the team investigate this more quickly (there's a lot of different things going on here).
,
Apr 28 2017
I created a single page HTML file that reproduces the problem with an XHR. To use it, simply open the attached page in your browser, then view the console to see the progress. The code sends xhr request every 10 seconds, with a 6000 msec timeout, and logs the elapsed time (from req.send() to the completion routine), along with the error status. The code oscillates between working (timing out in 6000 msec) for several requests and timing out prematurely for several more. I don't see a pattern. When it fails, the request can time out as early as ~1200msec: the longest time I've seen is 5599 msec.
,
Apr 29 2017
Update: I tweaked the single-page app to accumulate and display counts of the error status values. Get the file at http://testmyinter.net/simpletest.html
,
Apr 29 2017
Update: I have been running the test page (http://testmyinter.net/simpletest.html) overnight with multiple browsers on OSX and Win10. Three browsers (on both OS's) give the premature timeout error: Chrome, Opera, and Brave. I guess this is not surprising since they rely on Chromium. The other browsers (Firefox, Safari, MSIE and Edge) do not show the error.
,
May 1 2017
Confirmed with the simple repro. It looks like something quite interesting is going on here. Your test page starts a new connection every 10 seconds. It gives up waiting after 6 seconds, but the underlying connection is still happening in the network stack. On my system the connect() call gets ETIMEDOUT after 15 seconds. By this time the page's second XHR request is in progress, and waiting for a connection. It appears the failed socket is handed out to the new request, and so it reports ERR_CONNECTION_TIMED_OUT. The reason it's intermittent on many systems is that it depends on the interaction between the connect timeout delay (either in the OS or Chrome), the interval timer and the script's delay. On my system the connect() is timing out quickly and so I can reproduce it 100% of the time. I think if you reduce the interval timer to below 6 seconds it should reduce reliably on all systems.
,
May 2 2017
Oh, good. I'm glad that I provided an interesting problem :-) This leads me to ask two questions: 1) I definitely want to stay out of the way of the connect() ETIMEDOUT at 15 seconds. What can *I* do to work around this behavior? - If I change my application to send requests every 30 seconds, do you think I will avoid those premature timeouts? - As an alternative, I could set the xhr timeout=0, and add my own timer that aborts the xhr request. Would that avoid the problem? 2) What can be done so that the rest of the world avoids the problem? It seems wrong that the socket of the failing connection gets passed along to the next xhr request. Would it be better for the browser to start each new xhr request with a new socket (or a thoroughly-dead one)? Many thanks!
,
May 2 2017
I think the simplest workaround is not to start a new check until the previous one has completed. Starting each xhr request with a new socket would seriously hurt performance. Some kind of refinement to how the socket pools hand out errored sockets might be possible.
,
May 2 2017
> I think the simplest workaround is not to start a new check until the previous one has completed. This sounds practical. Am I correct that I could set the xhr timeout=0, and add my own timer that aborts the xhr request as a workaround? (Or is there some other way for my code to know whether the connection has completed?) Thanks again.
,
May 2 2017
Update: I modified the simple repro page to use a 20 second repeat rate, and user-settable timeout that calls req.abort(). See http://testmyinter.net/simpletest.html Results: When the simpletest.html window is in front, the timeouts occur as expected, taking a few milliseconds longer than the setTimeout() value. When the simpletest.html window is in back, (for example, when I'm browsing elsewhere on the web), I see two odd effects: 1) The timeout seems to be lengthened by 1000 msec most of the time when the test window is not frontmost. (My recent test is using a 7000 msec timeout; I regularly see 'elapsed: 8000') 2) I still see occasional "net::ERR_CONNECTION_TIMED_OUT" errors, the most recent was at 7845 msec. That's slightly below the 7000 msec user-specified timeout plus the 1000 msec "window in back" penalty... Is there some way for my code to force the browser to abandon the connection attempt? Other thoughts? Thanks.
,
May 2 2017
OK. I think I have convinced the browsers to abandon the request. But it's a complicated solution, and requires:
- req.timeout = 0 to let the timer control the connection
- setTimeout that calls abort()
- a completion routine that calls clearTimeout
- set on{load error abort timeout} to call the completion routine
- (maybe) clearing all the req.on*** values after the timeout
- Test conditions that seem to work:
- Interval between requests is 30 seconds;
- setTimeout up to about 6000 msec:
IE & Edge seem to time out their underlying OS connections around 7 seconds (7000 msec)
Here's code that shows what I'm using. It's taken from my test page, so you can ignore all the GUI stuff I dumped in...
function LogCompletion(text) {
var endTime = new Date();
var elapsed = endTime - startTime;
if (timer) {
clearTimeout(timer);
}
console.log("Elapsed: " + elapsed + " msec; Status was: " + text);
counts[text] += 1;
recentElapsed[text] = elapsed;
for (let prop in counts) {
document.getElementById(prop).innerHTML = counts[prop];
document.getElementById(prop + 'elapsed').innerHTML = recentElapsed[prop].toString();
}
}
function CheckAlive() {
var host = document.getElementById('addressStr').value;
var timeout = document.getElementById('timeoutStr').value;
var userTimeout = parseInt(timeout);
var req = new XMLHttpRequest(); // this is the request we'll use to test the host
req.open('GET', host, true); // async http GET from host
req.timeout = 0; // Infinite timer - setTimeout() controls length
req.onload = function (e) { LogCompletion('load') };
req.onerror = function (e) { LogCompletion('error') };
req.onabort = function (e) { LogCompletion('abort') };
req.ontimeout = function (e) { LogCompletion('timeout') };
startTime = new Date();
timer = setTimeout(function () {
console.log("xhr setTimeout aborting: "+req.readyState);
LogCompletion('abort');
req.onload = undefined; // clear out all our request handlers
req.onerror = undefined;
req.onabort = undefined;
req.ontimeout = undefined;
req.onloadstart = undefined; // clear out all other request handlers
req.onprogress = undefined;
req.onloadend = undefined;
req.abort(); // abort the call
// console.log("Post-abort: readyState " + req.readyState + ", statusText: " + req.statusText);
}, userTimeout);
req.send();
}
,
May 5 2017
Network triager here. Removing Internals > Network label because the networking stack is working as intended. I agree with ricea@ in #10 that we can't start a new socket for each xhr.
,
May 5 2017
I agree, and believe that this ticket can be closed: 1) I believe the code above aborts the xhr request, and leaves the req object in a state that doesn't interfere if the underlying socket is re-used. (At least, this seemed to operate reliably with a 30-second repeat rate and a six-second (6000 msec) userTimeout. 2) (Any network triager is welcome to chime in with an opinion about how to make the code above more airtight...) 3) This is an aberrant use case: I'm purposely allowing a long connection attempt (req.timeout=0) while using a separate setTimeout() for multiple seconds to abort to detect the condition that the other end isn't responding. 4) Consequently, as far as I'm concerned, the ticket can be closed. THANKS FOR ALL THE HIGH LEVEL ATTENTION!
,
May 23 2017
Thank you. I think there's nothing left to do here; closing the ticket.
,
May 23 2017
I am not sure it makes sense to close this. Why would the original code not work (it works in other browsers)?
,
May 23 2017
to respond to #17: I still wonder if the underlying TCP connection is timing out too early in Chrome. I seem to have worked around it in the code at the current http://testmyinter.net/simpletest.html. If you set the timeout on that page to 20000 (20 seconds) the underlying connection seems to time out in ~15 seconds. If you set the timeout to a smaller value, the separate setTimeout() always aborts as expected. (To clarify the stats: abort = Good result; error = Bad - premature timeout) I attached the current (working) code for reference. For reference I pasted code from the original simpletest.html page (around comment #4) below. (It's a short page that you can load into your browser.) In this case, timeout is good, while error is bad (premature timeout). As I said, I'm happy with the current state of affairs, but if someone thinks there's an underlying oddity, please feel to investigate further. <!-- This is the code from #4 that demonstrates the premature timeout --> <!DOCTYPE html> <html lang="en"> <head> <!-- <link rel="stylesheet" type="text/css" href="./build/css/flex.css"> --> <title>Test XHR Premature Timeout</title> <style> body { font-family: sans-serif; } </style></head> <body> <h1>Test XHR Premature Timeout</h1> <p> This code will continually send an XMLHttpRequest to a host with a long timeout. Open the Console to monitor its progress, and the events surrounding its operation. </p> <p> Demo code created in response to <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=716283#c3">https://bugs.chromium.org/p/chromium/issues/detail?id=716283#c3</a></i> <ul> <li>Enter an address that won't answer: <input type=text id="addressStr" value="http://123.45.67.89" /> (good choice: <i>http://123.45.67.89</i>) </li> <li> Enter a timeout: <input type=text id="timeoutStr" value="6000" /> <i>msec</i> </li> </ul> </p> <h3>Counts of error status</h3> <h4 id="browserid"></h4> <ul> <li>Load: <span id="load"></span></li> <li>Error: <span id="error"></span> (Bad!)</li> <li>Abort: <span id="abort"></span></li> <li>Timeout: <span id="timeout"></span> (Good!)</li> </ul> <script src="https://cdnjs.cloudflare.com/ajax/libs/UAParser.js/0.7.12/ua-parser.min.js"></script> <script> const counts = { load: 0, error: 0, abort: 0, timeout: 0 }; const checkInterval = 10 * 1000; const requestTimeout = 6 * 1000; var startTime = new Date(); var parser = new UAParser(); var browserInfo = parser.getResult(); var browserID = browserInfo.browser.name + " " + browserInfo.browser.version; browserID += " (" + browserInfo.os.name + " " + browserInfo.os.version + ")"; document.getElementById('browserid').innerHTML = browserID; CheckAlive(); setInterval (CheckAlive, checkInterval); function LogCompletion(text) { const endTime = new Date(); const elapsed = endTime - startTime; console.log("Elapsed: " + elapsed + " msec; Status was: " + text); counts[text] += 1; for (let prop in counts){ document.getElementById(prop).innerHTML = counts[prop]; } } function CheckAlive() { const host = document.getElementById('addressStr').value; const timeout = document.getElementById('timeoutStr').value; const userTimeout = parseInt(timeout); // console.log("Called CheckAlive: "+host + "timeout is "+ userTimeout +" msec"); const req = new XMLHttpRequest(); // this is the request we'll use to test host_port req.addEventListener("load" , function() { LogCompletion('load') }); // Got some kind of response back req.addEventListener("error" , function() { LogCompletion('error') }); // CORS errors show the server is there req.addEventListener("abort" , function() { LogCompletion('abort') }); // Somebody aborted test (don't know how) req.addEventListener("timeout", function() { LogCompletion('timeout') }); // timed out - presumably couldn't connect // LogToWindow("Testing " + url); req.open('GET', host, true); // async http GET from host req.timeout = userTimeout; // long-ish timer // 'ontimeout' events seem always to be paired with addEventListener 'timeout' events, so comment this out // req.ontimeout = (e) => { console.log("timeout from ontimeout - That's OK!")}; startTime = new Date(); req.send(); } </script> </body> </html>
,
Nov 29 2017
between two request, wait some seconds. can avoid the error.(i try 20sec, success) |
|||||
►
Sign in to add a comment |
|||||
Comment 1 by nyerramilli@google.com
, Apr 28 2017