TL;DR;
It depends on the browser's engine.
In Blink and Webkit:
- The maximum number of concurrent timers is 231-1.
- If you try to use more your browser will likely freeze due to a endless loop.
Official specification
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 the WindowTimers
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.
Blink implementation
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.
MAX_SAFE_INTEGER
is big enough for 285 thousand years worth of timers if you get a new id every millisecond. – Abiding