Skip to content

Commit 78ae851

Browse files
jooneJoone Hur
and
Joone Hur
authored
Add explainer for Extending Long Tasks API to Web Workers (#902)
Co-authored-by: Joone Hur <joonehur@microsoft.com>
1 parent 028870f commit 78ae851

File tree

1 file changed

+296
-0
lines changed

1 file changed

+296
-0
lines changed

LongTasks/explainer.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
2+
# Extending Long Tasks API to Web Workers
3+
4+
Author: [Joone Hur](https://github.com/joone)
5+
6+
# Introduction
7+
8+
The Long Tasks API allows web developers to monitor long-running tasks on the main thread that affect responsiveness and UI updates. To provide more detailed insights into these tasks, the Long Animation Frames (LoAF) API was introduced. However, neither API monitors long-running tasks in Web Workers, which can also affect user experience. To address this, we propose extending the Long Tasks API to support Web Workers, allowing Web developers to monitor long-running tasks on worker threads.
9+
10+
# Goals
11+
12+
- Enable sites to identify and diagnose worker-side tasks that block progress, including the worker types.
13+
- Improve consistency in telemetry logging across documents and workers by providing a unified monitoring mechanism for main thread and worker threads.
14+
15+
# Non-goals
16+
17+
- Provide a detailed breakdown and attribution of long-task timing similar to what the LoAF API does. We anticipate it may be in-scope for future work.
18+
19+
# Problems
20+
21+
When developers use a WebWorker, they expect to know when the task is done quickly. But when there's a delay, it can be hard to find the root cause. The delay might happen because one task is blocking others from running, a task is waiting for a network request, or other reasons.
22+
To address this, Web developers try to use the Long Tasks or LoAF API to identify long-running tasks that affect responsiveness and UI updates.
23+
24+
This example code simulates long tasks occurring on both the main thread and a worker thread.
25+
26+
## Main script (main.js)
27+
28+
```js
29+
function longTaskMain() {
30+
console.log('Main task completed: fibonacci value:' + fibonacci(40));
31+
}
32+
33+
function longTaskWorker() {
34+
const worker = new Worker('worker.js');
35+
worker.postMessage('startWorkerTask');
36+
37+
worker.onmessage = (event) => {
38+
console.log('Worker task completed:', event.data);
39+
};
40+
}
41+
42+
const observer = new PerformanceObserver((list) => {
43+
const entries = list.getEntries();
44+
console.log(entries);
45+
});
46+
47+
observer.observe({ entryTypes: ['longtask', 'long-animation-frame']});
48+
49+
longTaskMain();
50+
longTaskWorker();
51+
52+
```
53+
54+
## Web Worker script(worker.js)
55+
56+
```js
57+
self.onmessage = (event) => {
58+
if (event.data === 'startWorkerTask') {
59+
postMessage('fibonacci value:' + fibonacci(40));
60+
}
61+
};
62+
```
63+
64+
However, neither API monitors long-running tasks in Web Workers, which can also impact user experience, particularly when tasks involve fetching or processing data for UI updates. Therefore, this example only shows performance entries monitored from the main thread:
65+
66+
```json
67+
[
68+
{
69+
"name": "long-animation-frame",
70+
"entryType": "long-animation-frame",
71+
"startTime": 18.899999976158142,
72+
"duration": 1217,
73+
"renderStart": 1235.1000000238419,
74+
"styleAndLayoutStart": 1235.1000000238419,
75+
"firstUIEventTimestamp": 0,
76+
"blockingDuration": 1151,
77+
"scripts": [
78+
{
79+
"name": "script",
80+
"entryType": "script",
81+
"startTime": 35.19999998807907,
82+
"duration": 1199,
83+
"invoker": " https://localhost/explainers/long_tasks_ex/",
84+
"invokerType": "classic-script",
85+
"windowAttribution": "self",
86+
"executionStart": 35.5,
87+
"forcedStyleAndLayoutDuration": 0,
88+
"pauseDuration": 0,
89+
"sourceURL": " https://localhost/explainers/long_tasks_ex/",
90+
"sourceFunctionName": "",
91+
"sourceCharPosition": 0
92+
}
93+
]
94+
},
95+
{
96+
"name": "self",
97+
"entryType": "longtask",
98+
"startTime": 34.89999997615814,
99+
"duration": 1199,
100+
"attribution": [
101+
{
102+
"name": "unknown",
103+
"entryType": "taskattribution",
104+
"startTime": 0,
105+
"duration": 0,
106+
"containerType": "window",
107+
"containerSrc": "",
108+
"containerId": "",
109+
"containerName": ""
110+
}
111+
]
112+
}
113+
]
114+
```
115+
116+
In addition, the name "the Long Tasks API" might cause misunderstandings among web developers, as it suggests the API could work within web workers (see [this Chromium issue](https://issues.chromium.org/issues/41399667)).
117+
118+
# Proposal
119+
120+
We propose extending the Long Tasks API to support Web Workers. To adapt the API for Web Workers, the `PerformanceLongTaskTiming` interface needs to be updated to account for long tasks in worker threads. The primary change would be within the TaskAttribution interface, where certain properties should reflect the worker context instead of the main thread.
121+
122+
```json
123+
{
124+
"name": "unknown",
125+
"entryType": "longtask",
126+
"startTime": 1448.1000000238419,
127+
"duration": 297,
128+
"attribution": [
129+
{
130+
"name": "unknown",
131+
"entryType": "taskattribution",
132+
"startTime": 0,
133+
"duration": 0,
134+
"containerType": "worker",
135+
"containerSrc": " https://localhost/explainers/long_tasks_ex/worker.js",
136+
"containerId": "",
137+
"containerName": ""
138+
}
139+
]
140+
}
141+
```
142+
143+
## TaskAttribution Changes
144+
145+
The table below highlights the differences in the properties of the `TaskAttributionTiming` interface for long tasks on Web Workers compared to the main thread:
146+
147+
|**Property**|**Value(Main Thread)**|**Value(Web Worker)**|
148+
|---|---|---|
149+
|TaskAttributionTiming.duration|Always returns 0|Always returns 0|
150+
|TaskAttributionTiming.entryType|always returns taskattribution|Always returns taskattribution|
151+
|TaskAttributionTiming.name|always returns unknown|Always returns unknown|
152+
|TaskAttributionTiming.startTime|always returns 0|Always returns 0|
153+
|TaskAttributionTiming.containerType|Returns the type of frame container, one of iframe, embed, or object. Defaults to window if no container is identified.|Always return "worker"|
154+
|TaskAttributionTiming.containerSrc|Returns the container's src attribute.|Returns the worker script's URL|
155+
|TaskAttributionTiming.containerId|Returns the container's id attribute.|Always returns an empty string|
156+
|TaskAttributionTiming.containerName|Returns the container's name attribute.|Always returns an empty string|
157+
158+
For Web Workers, the containerType should return "worker" to clearly indicate the context. Additionally, containerSrc can return the URL of the worker script, helping developers differentiate between multiple workers.
159+
160+
## Monitoring multiple web workers
161+
162+
The main thread can create multiple workers. Instead of setting up a `PerformanceObserver` for each worker, the main thread can gather performance entries from all workers and its own long tasks. Therefore, it is only necessary to set up the `PerformanceObserver` for the longtask type on the main thread to collect these entries:
163+
164+
## Main script (main.js)
165+
166+
```js
167+
function longTaskMain() {
168+
fibonacci(40);
169+
}
170+
171+
function longTaskWorker(url) {
172+
const worker = new Worker(url);
173+
worker.postMessage('startWorkerTask');
174+
175+
worker.onmessage = (event) => {
176+
console.log('Worker task completed:', event.data);
177+
};
178+
}
179+
180+
longTaskMain();
181+
longTaskWorker("worker1.js");
182+
longTaskWorker("worker2.js");
183+
```
184+
185+
If you run the `PerformanceObserver` like this in the JavaScript console, you will get three performance entries as shown below:
186+
187+
```js
188+
const observer = new PerformanceObserver((list) => {
189+
console.log(list.getEntries());
190+
});
191+
192+
observer.observe({ type: 'longtask', buffered: true });
193+
194+
[
195+
{
196+
"name": "self",
197+
"entryType": "longtask",
198+
"startTime": 183.60000002384186,
199+
"duration": 1279,
200+
"attribution": [
201+
{
202+
"name": "unknown",
203+
"entryType": "taskattribution",
204+
"startTime": 0,
205+
"duration": 0,
206+
"containerType": "window",
207+
"containerSrc": "",
208+
"containerId": "",
209+
"containerName": ""
210+
}
211+
]
212+
},
213+
{
214+
"name": "unknown",
215+
"entryType": "longtask",
216+
"startTime": 1508.5,
217+
"duration": 229,
218+
"attribution": [
219+
{
220+
"name": "unknown",
221+
"entryType": "taskattribution",
222+
"startTime": 0,
223+
"duration": 0,
224+
"containerType": "worker",
225+
"containerSrc": " https://localhost/explainers/long_tasks_ex/worker1.js ",
226+
"containerId": "",
227+
"containerName": ""
228+
}
229+
]
230+
},
231+
{
232+
"name": "unknown",
233+
"entryType": "longtask",
234+
"startTime": 1534.199999988079,
235+
"duration": 303,
236+
"attribution": [
237+
{
238+
"name": "unknown",
239+
"entryType": "taskattribution",
240+
"startTime": 0,
241+
"duration": 0,
242+
"containerType": "worker",
243+
"containerSrc": " https://localhost/explainers/long_tasks_ex/worker2.js ",
244+
"containerId": "",
245+
"containerName": ""
246+
}
247+
]
248+
}
249+
]
250+
```
251+
252+
# Alternatives considered
253+
254+
## DevTools Tracing
255+
256+
DevTools tracing allows for manual inspection of long tasks; however, it is not well-suited for systematic metric collection.
257+
258+
## Override worker messages
259+
260+
Developers can implement a polyfill that intercepts every worker message, wraps callbacks with timers, and collect the necessary telemetry without native support. However, it is challenging to intercept all Web Worker messages in third-party libraries.
261+
262+
# Discussion
263+
264+
## SharedWorker Contexts
265+
266+
The `PerformanceLongTaskTiming.name` property might indicate multiple contexts for SharedWorkers. Further discussion is needed on how to handle shared execution environments and effectively track long tasks across these different contexts.
267+
268+
## Different Types of Workers
269+
270+
There are several types of worker threads, including Web Workers, Service Workers, and Worklets. To differentiate long tasks across these contexts, we can propose introducing a new property (e.g., workerType) to specify the type of worker from which the long task originates.
271+
272+
## Minimum Duration of Long Tasks in Web Workers
273+
274+
According to the specification, 50ms is the minimum duration for long tasks. We should consider whether the 50ms is appropriate for the minimum duration of long tasks in Web Workers as well, as their background operations may require a different standard. Furthermore, the minimum duration of long tasks should be configurable for both main thread and workers to accommodate various situations.
275+
276+
## Exposing the Source Function Name and Character Position
277+
278+
Since the LoAF API provides details about the invoker's source location, the Long Tasks API can extend similar support for Web Workers by exposing the function name and character position of long-running tasks.
279+
280+
## Who observes Web Workers?
281+
282+
This proposal allows the main thread to monitor long-running tasks on worker threads and collect performance entries directly from them. Alternatively, web developers could set up a `PerformanceObserver` for each worker and manually consolidate the entries.
283+
284+
# Chromium Issue
285+
286+
- [Support Long Tasks API in workers [41399667] - Chromium](https://issues.chromium.org/issues/41399667)
287+
288+
# Chrome Platform Status
289+
290+
[Support the Long Tasks API in web workers - Chrome Platform Status (chromestatus.com)](https://chromestatus.com/feature/5072227155050496)
291+
292+
# Links and further Reading
293+
294+
- [Long Tasks API (w3c.github.io)](https://w3c.github.io/longtasks/)
295+
- [PerformanceLongTaskTiming - Web APIs | MDN (mozilla.org)](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceLongTaskTiming)
296+
- [Long Animation Frames API  |  Web Platform  |  Chrome for Developers](https://developer.chrome.com/docs/web-platform/long-animation-frames)

0 commit comments

Comments
 (0)