Skip to content

Commit 0ee2031

Browse files
author
Joone Hur
committed
Apply feedback and refine Delayed Messages API explainer
- Updated problem description to clarify causes of `postMessage` delays and challenges in identifying root causes. - Expanded explanation of deserialization timing inconsistencies across browsers. - Refined summary of problems to emphasize the need for a dedicated API for diagnosing `postMessage` delays. - Improved description of `PerformanceExecutionContextInfo.name` to clarify optionality for workers and windows/iframes. - Removed "It adds boilerplate code and maintenance overhead" in manual instrumentation section. - Added missing reference to the Event Timing API in the references section. - Update congested example to remove IndexedDB references
1 parent 617e3a4 commit 0ee2031

File tree

1 file changed

+28
-29
lines changed

1 file changed

+28
-29
lines changed

DelayedMessages/explainer.md

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Web applications frequently use `postMessage` to send messages across different
88

99
Such delays can degrade user experience by making applications feel unresponsive. Identifying the root cause of these delays—whether it's a busy thread, a congested message queue, or the overhead of preparing message data—is currently challenging for web developers.
1010

11-
The Delayed Messages API allows web developers to identify congested browser contexts or workers and provide details on the end-to-end timing of `postMessage` events, as well as their related blocking tasks. These metrics help identify the causes of delays in `postMessage` handling and in improving the performance of web applications.
11+
The Delayed Messages API allows web developers to identify congested browser contexts or workers and provide details on the end-to-end timing of `postMessage` events, as well as their related blocking tasks. These metrics help identify the causes of `postMessage` delays and improve application performance.
1212

1313
# Goals
1414

@@ -26,15 +26,15 @@ This API does not aim to monitor or provide diagnostics for the following types
2626

2727
# Problems
2828

29-
When a developer sends a message using `postMessage` to a web worker or an iframe, they expect it to be processed on the target context in a timely manner. However, `postMessage` can experience significant delays, making it difficult to pinpoint the root cause. These delays might result from synchronous JavaScript executions blocking the main thread or worker thread, an excessive number of messages being sent too quickly, or significant time spent processing the data being transferred.
29+
While developers expect messages sent via `postMessage` to web workers or iframes to be processed promptly, these tasks typically receive default priority in the browser's task scheduler(e.g. Chromium). As a result, `postMessage` communication can experience noticeable delays due to lower prioritization compared to user-visible tasks, often compounded by synchronous JavaScript blocking the target thread, a flood of messages overwhelming the message queue, or significant time spent processing the data being transferred, making the root cause challenging to pinpoint.
3030

3131
These problems can be broadly categorized into three areas:
3232

3333
1. **Thread being occupied:** The receiving thread is busy executing long-running tasks.
3434
2. **Message queue becoming congested:** Too many messages are enqueued faster than they can be processed.
3535
3. **Serialization/deserialization processes taking significant time:** The data being sent is large or complex, leading to overhead.
3636

37-
This section will analyze each area with examples. All examples involve web workers, but similar situations can also occur between the main window and iframes, or between different windows.
37+
The following sections will analyze each area with examples. All examples involve web workers, but similar situations can also occur between the main window and iframes, or between different windows.
3838

3939
## Case 1: Thread Being Occupied
4040

@@ -146,13 +146,13 @@ Manually instrumenting code with `performance.now()` and `event.timeStamp` can h
146146

147147
Even without individual long-running tasks, a high volume of messages sent in a short period can overwhelm the receiving context. The message queue can grow, leading to congestion and delays for all subsequent messages, including potentially urgent ones.
148148

149-
The following example codes demonstrates this situation, where multiple `deleteMail` tasks congest the message queue before an urgent `readMail` task.
149+
The following example code demonstrates this situation, where multiple `deleteMail` tasks congest the message queue before an urgent `readMail` task.
150150

151151
[Link to live example](https://joone.github.io/web/explainers/delayed_messages/congested/)
152152

153153
### index.html
154154

155-
```js
155+
```html
156156
<!doctype html>
157157
<html lang="en">
158158
<head>
@@ -161,12 +161,13 @@ The following example codes demonstrates this situation, where multiple `deleteM
161161
<title>An example of a message queue experiencing congestion</title>
162162
</head>
163163
<body>
164-
<h1>IndexedDB Simulation</h1>
164+
<h1>Message Queue Congestion Example</h1>
165165
<button onclick="sendTasksToWorker()">Start</button>
166166
<script src="main.js"></script>
167167
</body>
168168
</html>
169169
```
170+
170171
### main.js
171172

172173
In main.js, the email application sends 10 deleteEmail tasks every 30 ms to clear junk emails, keeping the worker occupied with intensive processing. Shortly after, the user requests to check their emails, requiring an immediate response.
@@ -175,12 +176,12 @@ In main.js, the email application sends 10 deleteEmail tasks every 30 ms to clea
175176
// Create a Web Worker
176177
const worker = new Worker("worker.js");
177178

178-
// Open IndexedDB
179+
// Open the mail storage
179180
worker.postMessage({
180-
taskName: "openIndexedDB",
181+
taskName: "openMailStorage",
181182
startTime: performance.now() + performance.timeOrigin,
182183
});
183-
console.log(`[main] dispatching the openIndexedDB task`);
184+
console.log(`[main] dispatching the openMailStorage task`);
184185

185186
// Send a message every 30ms
186187
let emailID = 0;
@@ -192,11 +193,11 @@ function sendTasksToWorker() {
192193
taskName: `deleteMail`,
193194
startTime: performance.now() + performance.timeOrigin,
194195
});
195-
console.log(`[main] dispatching the deleteEmail task(id=#${emailID})`);
196+
console.log(`[main] dispatching the deleteMail task(id=#${emailID})`);
196197
emailID++;
197198
if (emailID >= 10) {
198199
clearInterval(interval);
199-
// Read emails from IndexedDB
200+
// Read emails
200201
worker.postMessage({
201202
taskName: "readMails",
202203
startTime: performance.now() + performance.timeOrigin,
@@ -209,7 +210,7 @@ function sendTasksToWorker() {
209210

210211
### worker.js
211212

212-
The Web Worker's `onmessage` handler processes `openIndexDB`, `deleteMail`, and `readMails` tasks received from the main thread. Each task requires 50ms to complete."
213+
The Web Worker's `onmessage` handler processes `openMailStorage`, `deleteMail`, and `readMails` tasks received from the main thread. Each task requires 50ms to complete."
213214

214215
```js
215216
// Listen for messages from the main thread
@@ -219,35 +220,35 @@ onmessage = async (event) => {
219220
const blockedDuration = processingStart - startTimeFromMain;
220221
const message = event.data;
221222

222-
if (message.taskName === "openIndexedDB") {
223-
await openIndexedDB(message, blockedDuration);
223+
if (message.taskName === "openMailStorage") {
224+
await openMailStorage(message, blockedDuration);
224225
} else if (message.taskName === "readMails") {
225226
await readMails(message, blockedDuration);
226227
} else if (message.taskName === "deleteMail") {
227228
await deleteMail(message, blockedDuration);
228229
}
229230
};
230231

231-
// Open IndexedDB and initialize a store if necessary
232-
async function openIndexedDB(message, blockedDuration) {
232+
// Open the mail storage.
233+
async function openMailStorage(message, blockedDuration) {
233234
return new Promise((resolve) => {
234-
const startOpenIndexedDB = performance.now();
235-
// Simulate the openIndexedDB task.
235+
const startOpenMailStorage = performance.now();
236+
// Simulate the openMailStorage task.
236237
const start = Date.now();
237238
while (Date.now() - start < 50) {
238239
/* Do nothing */
239240
}
240-
const endOpenIndexedDB = performance.now();
241+
const endOpenMailStorage = performance.now();
241242
console.log(
242243
`[worker] ${message.taskName},`,
243244
`message queue wait time + etc: ${blockedDuration.toFixed(2)} ms,`,
244-
`task duration: ${(endOpenIndexedDB - startOpenIndexedDB).toFixed(2)} ms`,
245+
`task duration: ${(endOpenMailStorage - startOpenMailStorage).toFixed(2)} ms`,
245246
);
246247
resolve();
247248
});
248249
}
249250

250-
// Read emails from IndexedDB
251+
// Read emails from the mail storage
251252
async function readMails(message, blockedDuration) {
252253
return new Promise((resolve) => {
253254
const startRead = performance.now();
@@ -297,7 +298,7 @@ While delays in background tasks like `deleteMail` might be acceptable, delays i
297298

298299
When data is sent using `postMessage`, it undergoes serialization by the sender and deserialization by the receiver. For large or complex JavaScript objects (e.g., a large JSON payload or a deeply nested object), these processes can consume considerable time, blocking the respective threads.
299300

300-
The following example code demonstrate the delay introduced by serializing/deserializing a large JSON object during postMessage.
301+
The following example code demonstrate the delay introduced by serializing/deserializing a large JSON object during `postMessage`.
301302

302303
[Link to live demo](https://joone.github.io/web/explainers/delayed_messages/serialization/)
303304

@@ -400,13 +401,11 @@ onmessage = (event) => {
400401
```
401402
As shown, serialization on the main thread (approx. 130.30 ms) occurs synchronously during the `postMessage` call, blocking other main thread work. Similarly, deserialization on the worker thread (approx. 114.30 ms) is a significant operation that blocks the worker's event loop during message processing, delaying the execution of the `onmessage` handler and any subsequent tasks.
402403

403-
In this example, the worker log `message queue wait time + etc: 130.40 ms` indicates the time elapsed from when the main thread initiated the `postMessage` (including its ~130.30 ms serialization block) to when the worker’s `onmessage` handler began execution. This suggests that the message queue wait time is nearly zero, and the delay is primarily caused by serialization on the sender side. However, when the event loop is also busy with other long tasks, it becomes difficult to distinguish these individual sources of delay (serialization, actual queueing, deserialization, and task execution) from other task delays without manual instrumentation.
404-
405-
This API proposes to expose these timings (`serialization`, `deserialization`, `blockedDuration`) explicitly, simplifying the diagnosis of such delays.
404+
In this example, the worker log `message queue wait time + etc: 130.40 ms` indicates the time elapsed from when the main thread initiated the `postMessage` (including its ~130.30 ms serialization block) to when the worker’s `onmessage` handler began execution. This suggests that the message queue wait time is nearly zero, and the delay is primarily caused by serialization on the sender side. However, the timing of deserialization, which the [specification](https://html.spec.whatwg.org/multipage/web-messaging.html#dom-window-postmessage-options-dev) suggests should occur before the message event, can vary across browsers. For example, browsers like Chromium, may delay this process until the message data is actually accessed for the first time. This inconsistency, combined with a busy event loop that makes it difficult to distinguish serialization, actual queueing, deserialization, and task execution delays even with manual instrumentation, further underscores the need for an API to expose this particular timing information.
406405

407406
### Summary of Problems
408407

409-
Existing performance tools can help detect that messages are delayed, but pinpointing the *exact cause* is difficult. The delay could be due to serialization/deserialization, event handling logic, general browser overhead, or time spent in microtasks. Measuring message queue wait time accurately is also challenging. A dedicated API is needed to accurately measure, attribute, and identify sources of `postMessage` delays, simplifying diagnosis and optimization.
408+
Message delays frequently occur and can degrade user experience. While existing tools can detect delays, pinpointing the exact cause is difficult. Delays often stem from the receiver's thread being busy with long tasks, message queue congestion, or serialization/deserialization overhead. Accurately measuring internal message queue wait time is especially challenging with manual instrumentation. A dedicated API is needed to precisely measure, attribute, and identify these specific sources of delay.
410409

411410
# Proposal: Introducing the Delayed Messages API
412411

@@ -559,7 +558,7 @@ Returns a unique identifier for the execution context (e.g., a string or an inte
559558

560559
#### `PerformanceExecutionContextInfo.name`
561560

562-
Returns the name of the execution context. For workers, this is the name provided during instantiation (e.g., `new Worker("worker.js", { name: "MyWorker" })`). For windows or iframes, it might be empty or derived from `window.name`.
561+
Returns the name of the execution context. For workers, this is the name provided during instantiation (e.g., `new Worker("worker.js", { name: "MyWorker" })`). It might be empty, as the name is optional. For windows or iframes, it might be empty or derived from `window.name`.
563562

564563
#### `PerformanceExecutionContextInfo.type`
565564

@@ -598,7 +597,7 @@ observer.observe({ type: "delayed-message", durationThreshold: 50, buffered: tru
598597
```
599598
## Examples of a `delayed-message` Performance Entry
600599

601-
The following JSON shows example performance entries for the delayed messages identified in **Case 1: Thread Being Occupied**. This API automatically detects and reports these delays without requiring manual `performance.now()` tracking.
600+
The following JSON shows sample performance entries for the delayed messages identified in **Case 1: Thread Being Occupied**. This API automatically detects and reports these delays without requiring manual `performance.now()` tracking.
602601

603602
By examining `blockedDuration` and the handler execution time (`processingEnd - processingStart`), developers can diagnose the cause:
604603

@@ -724,7 +723,6 @@ Developers can attempt to wrap `postMessage` calls and `onmessage` handlers with
724723

725724
* It's challenging to intercept all messages, especially those from third-party libraries.
726725
* Accurately measuring internal browser operations like serialization, deserialization, and precise queue waiting time is not feasible from JavaScript.
727-
* It adds boilerplate code and maintenance overhead.
728726

729727
A native API can provide more accurate and comprehensive data with lower overhead.
730728

@@ -750,6 +748,7 @@ To address this, the API makes `durationThreshold` configurable by the developer
750748
Further discussion is needed to determine the minimum allowed value for this threshold (e.g., 50ms as suggested) to ensure it remains useful without introducing significant performance costs.
751749

752750
# References
751+
- [Event Timing API](https://w3c.github.io/event-timing/)
753752
- [Extending Long Tasks API to Web Workers](https://github.com/joone/MSEdgeExplainers/blob/add_id_src_type/LongTasks/explainer.md)
754753
- https://developer.mozilla.org/en-US/docs/Web/API/PerformanceLongTaskTiming
755754
- https://developer.mozilla.org/en-US/docs/Web/API/PerformanceScriptTiming

0 commit comments

Comments
 (0)