Surprisingly I can not find the answer to this question anywhere on the web.
In the documentation it is stated that setTimeout and setInterval share the same pool of ids, as well as that an id will never repeat. If that is the case then they must eventually run out because there is a maximum number the computer can handle? What happens then, you can't use timeouts anymore?
Maximum delay value Browsers including Internet Explorer, Chrome, Safari, and Firefox store the delay as a 32-bit signed integer internally. This causes an integer overflow when using delays larger than 2,147,483,647 ms (about 24.8 days), resulting in the timeout being executed immediately.
Introduction to JavaScript setTimeout()The setTimeout() sets a timer and executes a callback function after the timer expires. In this syntax: cb is a callback function to be executed after the timer expires. delay is the time in milliseconds that the timer should wait before executing the callback function.
We all know that passing a string to setTimeout (or setInterval ) is evil, because it is run in the global scope, has performance issues, is potentially insecure if you're injecting any parameters, etc. So doing this is definitely deprecated: setTimeout('doSomething(someVar)', 10000);
The reason why this happens is because Javascript is single threaded.
TL;DR;
It depends on the browser's engine.
In Blink and Webkit:
From the W3C docs:
The
setTimeout()
method must run the following steps:
Let handle be a user-agent-defined integer that is greater than zero that will identify the timeout to be set by this call.
Add an entry to the list of active timeouts for handle.
[...]
Also:
Each object that implements the
WindowTimers
interface has a list of active timeouts and a list of active intervals. Each entry in these lists is identified by a number, which must be unique within its list for the lifetime of the object that implements theWindowTimers
interface.
Note: while the W3C mentions two lists, the WHATWG spec establishes that setTimeout
and setInterval
share a common list of active timers. That means that you can use clearInterval()
to remove a timer created by setTimeout()
and vice versa.
Basically, each user agent has freedom to implement the handle Id as they please, with the only requirement to be an integer unique for each object; you can get as many answers as browser implementations.
Let's see, for example, what Blink is doing.
Previous note: It's not such an easy task to find the actual source code of Blink. It belongs to the Chromium codebase which is mirrored in GitHub. I will link GitHub (its current latest tag: 72.0.3598.1
) because its better tools to navigate the code. Three years ago, they were pushing commits to chromium/blink/. Nowadays, active development is on chromium/third_party/WebKit but there is a discussion going on about a new migration.
In Blink (and in WebKit, which obviously has a very similar codebase), the responsible of maintaining the aforementioned list of active timers is the DOMTimerCoordinator
belonging to each ExecutionContext
.
// Maintains a set of DOMTimers for a given page or
// worker. DOMTimerCoordinator assigns IDs to timers; these IDs are
// the ones returned to web authors from setTimeout or setInterval. It
// also tracks recursive creation or iterative scheduling of timers,
// which is used as a signal for throttling repetitive timers.
class DOMTimerCoordinator {
The DOMTimerCoordinator
stores the timers in the blink::HeapHashMap
(alias TimeoutMap
) collection timers_
which key is (meeting the specs) int
type:
using TimeoutMap = HeapHashMap<int, Member<DOMTimer>>;
TimeoutMap timers_;
That answer your first question (in the contex of Blink): the maximum number of active timers for each context is 231-1; much lower than the JavaScript MAX_SAFE_INTEGER
(253-1) that you mentioned but still more than enough for normal use cases.
For your second question, "What happens then, you can't use timeouts anymore?", I have so far just a partial answer.
New timers are created by DOMTimerCoordinator::InstallNewTimeout()
. It calls the private member function NextID()
to retrieve an available integer key and DOMTimer::Create
for the actual creation of the timer object. Then, it inserts the new timer and the corresponding key into timers_
.
int timeout_id = NextID();
timers_.insert(timeout_id, DOMTimer::Create(context, action, timeout,
single_shot, timeout_id));
NextID()
gets the next id in a circular sequence from 1 to 231-1:
int DOMTimerCoordinator::NextID() {
while (true) {
++circular_sequential_id_;
if (circular_sequential_id_ <= 0)
circular_sequential_id_ = 1;
if (!timers_.Contains(circular_sequential_id_))
return circular_sequential_id_;
}
}
It increments in 1 the value of circular_sequential_id_
or set it to 1 if it goes beyond the upper limit (although INT_MAX
+1 invokes UB, most C implementations return INT_MIN
).
So, when the DOMTimerCoordinator
runs out of IDs, tries again from 1 up until it finds one free.
But, what happen if they are all in use? What does prevent NextID()
from entering in a endless loop? It seems that nothing. Likely, Blink developers coded NextID()
under the assumption that there will never be 231-1 timers concurrently. It makes sense; for every byte returned by DOMTimer::Create()
you will need a GB of RAM to store timers_
if it is full. It can add up to TB if you store long callbacks. Let alone the time needed to create them.
Anyway, it looks surprising that no guard against an endless loop has been implemented, so I have contacted Blink developers, but so far I have no response. I will update my answer if they reply.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With