简体   繁体   中英

Stuck setTimeout()s (Chrome)

I have a situation where multiple setTimeout() calls (typical length: 100ms to 1 sec) are active and should have gone off, but they do not fire. A Chrome (Mac) debugger profile shows "idle" during this (potentially infinite) period. The profile shows that nothing is going on. No loops. No code running. No garbage collection. No activity whatsoever. The viewport is active and has focus. After I wait (potentially an infinite time), when I "do something else", like mouseover some unrelated element -- as simple as a :hover -- the logjam breaks and the queued setTimeout() s all fire.

When I set breakpoints in the setTimeout() handler functions after this "freeze" occurs, they get hit in sequence when the logjam breaks, just as you would expect.

Unfortunately the replication path is difficult. So far, creating simpler test cases just makes replication even more difficult or, ultimately, impossible.

Most of the chatter around setTimeout() "issues" is from people who don't understand the single-thread nature of jscript, etc., so it is not helpful. Let me repeat: the timers are queued and should have fired. The browser is idle, as proved by the profiler. The timers DO fire ultimately, but only after mouse activity occurs. This behavior seems very wrong to me. If the browser is idle, and there are events in the queue, they should fire.

Has anyone seen behavior like this? Have I stumbled across a way to lock up the event dispatcher? Maybe I'm missing something.

Update: Cannot replicate on Windows 7.

Update 2: Restarted Chrome on Mac, can no longer replicate. So, worst possible outcome: no answer as to why it happened, why it kept happening, why it didn't happen reliably, why it went away, and why it won't happen any more.

I had a similar issue recently and discovered that since version 47, chromium folks have decided to not honour setTimeout when they believe this is 'detrimental to the majority of users'. Basically they've deprecated the setTimeout API (asking no one, as usual).
Here is bug 570845 where people discovered about this. There are a number of other bugs and discussion threads regarding the issue.

The fallback is to emulate setTimeout using requestAnimationFrame .
Here is a proof of concept:

 'use strict' var PosfScheduler = ( function () { /* * Constructor. * Initiate the collection of timers and start the poller. */ function PosfScheduler () { this.timers = {}; this.counter = 0; this.poll = function () { var scheduler = this; var timers = scheduler.timers; var now = Date.now(); for ( var timerId in timers ) { var timer = timers[timerId]; if ( now - timer.submitDate >= timer.delay ) { if ( timer.permanent === true ) { timer.submitDate = now; } else { delete timers[timer.id]; } timer.func.apply.bind( timer.func, timer.funcobj, timer.funcargs ).apply(); } } requestAnimationFrame( scheduler.poll.bind(scheduler) ); }; this.poll(); }; /* * Adding a timer. * A timer can be * - an interval (arg[0]: true) - a recurrent timeout * - a simple timeout (arg[0]: false) */ PosfScheduler.prototype.addTimer = function () { var id = this.counter++; var permanent = arguments[0] ; var func = arguments[1] ; var delay = arguments[2] ; var funcobj = arguments[3] ; var funcargs = Array.prototype.slice.call(arguments).slice(4); var submitDate = Date.now() ; var timer = { id: id, permanent: permanent, func: func, delay: delay, funcargs: funcargs, submitDate: submitDate, } this.timers[id] = timer; return timer; }; /* * Replacement for setTimeout * Similar signature: * setTimeout ( function, delay [obj,arg1...] ) */ PosfScheduler.prototype.setTimeout = function () { var args = Array.prototype.slice.call(arguments); return this.addTimer.apply.bind( this.addTimer, this, [false].concat(args) ).apply(); }; /* * Replacement for setInterval - Untested for now. * Signature: * setInterval ( function, delay [obj,arg1...] ) */ PosfScheduler.prototype.setInterval = function () { var args = Array.prototype.slice.call(arguments); return this.addTimer.apply.bind( this.addTimer, this, [true].concat(args) ).apply(); }; PosfScheduler.prototype.cancelTimeout = function ( timer ) { delete this.timers[timer.id]; }; /* * Don't want to leave all these schedulers hogging the javascript thread. */ PosfScheduler.prototype.shutdown = function () { delete this; }; return PosfScheduler; })(); var scheduler = new PosfScheduler(); var initTime = Date.now(); var timer1 = scheduler.setTimeout ( function ( init ) { console.log ('executing function1 (should appear) after ' + String ( Date.now() - init ) + 'ms!' ); }, 200, null, initTime ); var timer2 = scheduler.setTimeout ( function ( init ) { console.log ('executing function2 afte: ' + String ( Date.now() - init ) + 'ms!' ); }, 300, null, initTime ); var timer3 = scheduler.setTimeout ( function ( init ) { console.log ('executing function3 (should not appear) after ' + String ( Date.now() - init ) + 'ms!' ); }, 1000, null, initTime ); var timer4 = scheduler.setTimeout ( function ( init, sched, timer ) { console.log ('cancelling timer3 after ' + String ( Date.now() - init ) + 'ms!' ); sched.cancelTimeout ( timer3 ); }, 500, null, initTime, scheduler, timer3 ); var timer5 = scheduler.setInterval ( function ( init, sched, timer ) { console.log ('periodic after ' + String ( Date.now() - init ) + 'ms!' ); }, 400, null, initTime, scheduler, timer3 ); var timer6 = scheduler.setTimeout ( function ( init, sched, timer ) { console.log ('cancelling periodic after ' + String ( Date.now() - init ) + 'ms!' ); sched.cancelTimeout ( timer5 ); }, 900, null, initTime, scheduler, timer5 );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM