## Problem
GPUI's web dispatcher already uses `spin_send` and `spin_try_pop` on the
browser main thread. Those operations spin on `try_lock()` instead of
blocking while acquiring the queue mutex. However, that does **not**
make releasing a `parking_lot` mutex safe on that thread.
When a worker is waiting for the queue mutex, dropping the main thread's
guard follows this path:
```text
RawMutex::unlock_slow
→ parking_lot_core::unpark_one
→ lock_bucket
→ WordLock::lock_slow
→ ThreadParker::park
→ memory.atomic.wait32
```
`unpark_one` first locks the internal table used to locate sleeping
threads. If another thread holds that bucket lock, the caller can itself
park before reaching the actual wake instruction. See
[`unpark_one`](https://github.com/Amanieu/parking_lot/blob/d7828fff7b5d6327ae608e82db45f888b344449a/core/src/parking_lot.rs#L732-L737),
[`lock_bucket`](https://github.com/Amanieu/parking_lot/blob/d7828fff7b5d6327ae608e82db45f888b344449a/core/src/parking_lot.rs#L358-L366),
and [`WordLock`'s park
call](https://github.com/Amanieu/parking_lot/blob/d7828fff7b5d6327ae608e82db45f888b344449a/core/src/word_lock.rs#L163-L168).
Browsers forbid synchronous atomic waits on the main thread. Chromium
reports `Atomics.wait cannot be called in this context`; Safari can
misleadingly report an out-of-bounds memory access. Ordinary UI activity
can encounter this contention while scheduling background work; no
explicit blocking operation by the application is required.
## Why `std` fixes it
For **atomics-enabled Wasm**, Rust's standard-library mutex uses a futex
directly. Unlocking changes the mutex state with an atomic swap and, if
needed, calls `memory.atomic.notify`. Its condition-variable
notification similarly increments an atomic notification counter and
calls `memory.atomic.notify`. Neither operation acquires a secondary
waiter-table lock or performs an atomic wait.
A contended `std::Mutex::lock()` would still be unsafe on the browser
main thread, so this change **retains the existing `try_lock()` spin
loops**. Workers retain ordinary blocking acquisition and
condition-variable waiting. This relies on the Wasm backend's
implementation, not a portable guarantee that standard mutex operations
never block on every platform.
The production change only replaces the queue synchronization primitives
and adapts their APIs. The high/medium/low queues, weighted selection,
queue methods, and disconnect behavior are unchanged. Poisoned guards
are recovered to preserve the previous non-poisoning behavior. The
main-thread-only mailbox receiver also uses a standard mutex with
`try_lock()` so an unexpected acquisition cannot park it.
## Regression test and verification
One browser/Wasm stress test exchanges work and replies between the
actual browser main thread and configurable `N` Web Workers. It checks
exact-once delivery, cycles between idle and busy queues, and waits for
all workers to stop. An explicit Wasm wait is a negative control
confirming that the main thread rejects waits; a deadline outside the
page detects hangs.
- **Fix stashed:** rebuilt and ran the test against the original queue.
It failed with `Atomics.wait cannot be called in this context`, with
`WordLock::lock_slow → RawMutex::unlock_slow → spin_send` in the stack.
- **Fix restored:** rebuilt and ran the unchanged test with **8 and 16
workers**. Both passed, each exchanging **524,288 items over 128
rounds**.
- Both existing native queue tests passed. Shared-memory Wasm
compilation, Rust formatting, JavaScript syntax, and diff checks passed.
- Browser execution was verified in Chromium, not directly on an iOS
Safari device. This does not update any downstream application's pinned
GPUI dependency.
The test is available through the existing `hello_web` gallery:
```sh
node crates/gpui_web/examples/hello_web/test_queue.mjs http://127.0.0.1:8080 8
```
For the recorded runs, the fixture was built directly with the gallery's
shared-memory flags, `nightly-2026-07-01`, and matching wasm-bindgen
0.2.120, then served with COOP/COEP headers.
## Suggested .rules additions
For code running on the browser main thread, audit mutex unlock and
notification paths as well as acquisition: using `try_lock()` does not
guarantee that releasing the guard cannot park.
Release Notes:
- [GPUI] N/A
114 lines
5.2 KiB
HTML
114 lines
5.2 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0" />
|
|
<title>GPUI Web Examples</title>
|
|
<link data-trunk rel="rust" data-bin="hello_web" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<!-- Trunk's worker pipelines build auxiliary modules without starting them.
|
|
The gallery imports these on the main thread, not in Web Workers. -->
|
|
<link data-trunk rel="rust" data-type="worker" data-bin="hello_world" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<link data-trunk rel="rust" data-type="worker" data-bin="text" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<link data-trunk rel="rust" data-type="worker" data-bin="text_layout" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<link data-trunk rel="rust" data-type="worker" data-bin="text_wrapper" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<link data-trunk rel="rust" data-type="worker" data-bin="input" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<script data-trunk type="module" src="gallery.js"></script>
|
|
<link data-trunk rel="rust" data-type="worker" data-bin="dynamic_fonts" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<link data-trunk rel="rust" data-type="worker" data-bin="font_fallback_test" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<link data-trunk rel="rust" data-type="worker" data-bin="queue_test" data-bindgen-target="web" data-keep-debug data-wasm-opt="0" />
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
html,
|
|
body {
|
|
margin: 0;
|
|
height: 100%;
|
|
}
|
|
body {
|
|
font-family: system-ui, sans-serif;
|
|
color: #202030;
|
|
background: #f5f5f8;
|
|
}
|
|
main {
|
|
max-width: 48rem;
|
|
margin: 0 auto;
|
|
padding: 3rem 1.5rem;
|
|
}
|
|
h1, main > p {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
ul {
|
|
list-style: none;
|
|
display: grid;
|
|
gap: 0.75rem;
|
|
}
|
|
li a {
|
|
display: block;
|
|
padding: 1rem;
|
|
border: 1px solid #d0d0dc;
|
|
border-radius: 0.5rem;
|
|
color: inherit;
|
|
background: white;
|
|
text-decoration: none;
|
|
}
|
|
li a:hover, li a:focus-visible {
|
|
border-color: #5050a0;
|
|
}
|
|
li span {
|
|
display: block;
|
|
margin-top: 0.25rem;
|
|
color: #555566;
|
|
}
|
|
#back {
|
|
position: fixed;
|
|
right: 0.75rem;
|
|
bottom: 0.75rem;
|
|
z-index: 1;
|
|
padding: 0.5rem 0.75rem;
|
|
border-radius: 0.25rem;
|
|
color: #202030;
|
|
background: #ffffffee;
|
|
}
|
|
#status {
|
|
position: fixed;
|
|
top: 1rem;
|
|
left: 1rem;
|
|
right: 1rem;
|
|
z-index: 1;
|
|
padding: 1rem;
|
|
background: white;
|
|
white-space: pre-wrap;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
touch-action: none;
|
|
outline: none;
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main id="gallery">
|
|
<h1>GPUI Web Examples</h1>
|
|
<p>Each example runs in its own WebAssembly instance. Choose an example to open it.</p>
|
|
<ul>
|
|
<li><a href="/hello-world" data-module="hello_world"><strong>Hello world</strong><span>Basic GPUI layout and styling.</span></a></li>
|
|
<li><a href="/text" data-module="text"><strong>Styled text</strong><span>Typography, font sizes, and line heights.</span></a></li>
|
|
<li><a href="/text-layout" data-module="text_layout"><strong>Text layout</strong><span>Alignment, decorations, and font weights.</span></a></li>
|
|
<li><a href="/text-wrapper" data-module="text_wrapper"><strong>Text wrapping</strong><span>Wrapping and truncation, including CJK sample text.</span></a></li>
|
|
<li><a href="/input" data-module="input"><strong>Text input</strong><span>Typing, cursor movement, and selections.</span></a></li>
|
|
<li><a href="/dynamic-fonts" data-module="dynamic_fonts"><strong>Dynamic fonts</strong><span>Download a font when missing glyphs are reported.</span></a></li>
|
|
<li><a href="/prime-sieve" data-module="hello_web"><strong>Prime sieve</strong><span>Background tasks and progress updates.</span></a></li>
|
|
</ul>
|
|
</main>
|
|
<a id="back" href="/" hidden>All examples</a>
|
|
<p id="status" role="status" hidden></p>
|
|
</body>
|
|
</html>
|