记录前端setTimeout和setInterval延迟执行排查过程

问题是这样的,前端需要和后端建立websocket连接,并且需要发心跳包,而且允许同一个用户同时给多人登录,且可以打开多个tab页面,每一个tab页面都会和后端建立连接,并且每隔3秒发一次心跳包。服务端为了测试,将心跳判断限制去掉了。

<!DOCTYPE html>
<html lang="en">
<head>
<title>Chat Example</title>
<script type="text/javascript">
window.onload = function () {
    var conn;
    var msg = document.getElementById("msg");
    var log = document.getElementById("log");

    function appendLog(item) {
        var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
        log.appendChild(item);
        if (doScroll) {
            log.scrollTop = log.scrollHeight - log.clientHeight;
        }
    }

    document.getElementById("form").onsubmit = function () {
        if (!conn) {
            return false;
        }
        if (!msg.value) {
            return false;
        }
        conn.send(msg.value);
        msg.value = "";
        return false;
    };

    if (window["WebSocket"]) {
        conn = new WebSocket("ws://127.0.0.1:8081/ws");
        conn.onclose = function (evt) {
            var item = document.createElement("div");
            item.innerHTML = "<b>Connection closed.</b>";
            appendLog(item);
        };
        var heartbeat = function(){
            conn.send('{"heartbeat":1}')
            setTimeout(heartbeat, 3000);
        }
        conn.onmessage = function (evt) {
            var messages = evt.data.split('\n');
            for (var i = 0; i < messages.length; i++) {
                var item = document.createElement("div");
                item.innerText = messages[i];
                appendLog(item);
            }
        };
        conn.onopen = function () {
            
            setTimeout(heartbeat, 3000);
            conn.send('{"token":"userToken"}')
        }
        conn.onerror = function (error) {
        console.log('WebSocket Error ' + error);
        };


        
        
    } else {
        var item = document.createElement("div");
        item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
        appendLog(item);
    }
};
</script>
<style type="text/css">
html {
    overflow: hidden;
}

body {
    overflow: hidden;
    padding: 0;
    margin: 0;
    width: 100%;
    height: 100%;
    background: gray;
}

#log {
    background: white;
    margin: 0;
    padding: 0.5em 0.5em 0.5em 0.5em;
    position: absolute;
    top: 0.5em;
    left: 0.5em;
    right: 0.5em;
    bottom: 3em;
    overflow: auto;
}

#form {
    padding: 0 0.5em 0 0.5em;
    margin: 0;
    position: absolute;
    bottom: 1em;
    left: 0px;
    width: 100%;
    overflow: hidden;
}

</style>
</head>
<body>
<div id="log"></div>
<form id="form">
    <input type="submit" value="Send" />
    <input type="text" id="msg" size="64" autofocus />
</form>
</body>
</html>

之前本来是用setInterval的,后面看到这篇文章为什么有时候用 setTimeout 替代 setInterval?后,改用setTimeout,依旧出现这样的情况

正常应该是每3秒执行一次

后面怀疑是不是setTimeout 有bug,所以查了下chrome定时器源码分析,

其中说到webkit/glue/webkit_constants.h这里的代码是定时器的配置


// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef WEBKIT_GLUE_WEBKIT_CONSTANTS_H_
#define WEBKIT_GLUE_WEBKIT_CONSTANTS_H_

namespace webkit_glue {

// Chromium 将最小间隔超时设置为 4ms,覆盖
// 默认为 10 毫秒。 我们想走得更低,但是情况很差
// 那些确实会创建 CPU 旋转循环的编码网站。 使用
// 4ms 防止 CPU 过于忙碌并提供平衡
// 在 CPU 旋转和可能的最小间隔计时器之间。
const double kForegroundTabTimerInterval = 0.004;

// 控制后台选项卡的最小计时器间隔。
const double kBackgroundTabTimerInterval = 1.0;

} // namespace webkit_glue


#endif  // WEBKIT_GLUE_WEBKIT_CONSTANTS_H_

content/renderer/render_view_impl.cc

RenderViewImpl::RenderViewImpl这个函数


 webview()->settings()->setMinimumTimerInterval(
      is_hidden() ? webkit_glue::kBackgroundTabTimerInterval :
          webkit_glue::kForegroundTabTimerInterval);

先判断tab页面是否隐藏。如果隐藏就设置最小计时器间隔为1s,如果是活动页面,设置最小计时器间隔为4ms

故将


var heartbeat = function(){
    conn.send('{"heartbeat":1}')
    setTimeout(heartbeat, 500);//这里之前是3000,表示3s发一次心跳
}

然后切换到浏览器,打开页面后切换到其他tab页面,过了10秒再回来看发送记录,发现的确是1秒执行一次。

每秒执行一次

但是我们之前偶现是1分钟执行一次,后面google了一下, 后面发现chrome://flags/#intensive-wake-up-throttling 谷歌浏览器里有个参数,控制浏览器唤醒,看谷歌的解释是 启用后,从 DOM 计时器唤醒在已隐藏 5 分钟的页面中限制为每分钟 1 次。 有关其他详细信息,请参阅 https://www.chromestatus.com/feature/4718288976216064。 – Mac、Windows、Linux、Chrome 操作系统、Android

后面打开https://www.chromestatus.com/feature/4718288976216064,发现chrome为了延长了电池使用时间,加了限制:如果tab页面不是活动页,对已隐藏 5 分钟的页面限制为每分钟执行 1 次setTimeout或者setInterval

添加新评论