arm64 WSL2: whole VM stalls for minutes when many threads page-fault a cold mmap'd file
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 38/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- c
- Lĩnh vực
- operating-systems
Hướng nghiên cứu
The report identifies arm64 Linux spinlocks and Hyper-V early initialization but names no WSL source files or tests. Start by locating the arm64 spinlock fallback and Hyper-V initialization paths in the WSL kernel tree, then compare them with x86's virt_spin_lock() and Hyper-V setup. Done means the reported multithreaded mmap repro no longer stalls the VM, while the proposed static key remains off outside Hyper-V.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Windows Version
Windows 11 [Version 10.0.28120.2738]
WSL Version
3.0.3.0
Are you using WSL 1, WSL 2, or WSLC?
- WSL 2
- WSL 1
- WSLC
Kernel Version
6.18.54.1-microsoft-standard-WSL2
Distro Version
Ubuntu-24.04
Other Software
No response
Repro Steps
What happens
On an arm64 WSL2 VM with many vCPUs, the whole VM can stall for minutes when many threads page-fault the same file-backed mmap at the same time and the file isn't in the page cache yet.
PyTorch apps can hit this issue. When they load model weights, they copy tensors out of an mmap'd file, and PyTorch splits each copy across all cores by default. ComfyUI is one example: on a 72-vCPU VM it hangs while loading its text encoder.
This isn't just a slow app. Once it starts, the rest of the VM stops making progress too:
- The guest kernel logs repeated RCU stalls: 17 stall reports in about an hour. Each one lists CPUs that have been stuck for at least 60 s in the page-fault path (
do_page_fault → filemap_fault → filemap_add_folio), up to 29 CPUs at a time. - systemd's watchdog kills
systemd-resolvedandsystemd-udevdafter they stop responding for 3 minutes. - Shell commands in the VM hang.
Guest journal excerpt from one of these stalls (stall reports, a representative backtrace and the watchdog kills): wsl-arm64-rcu-stall-excerpt.log
With fewer threads, the same work finishes in well under a second.
Environment
- 72-core arm64 host
- Windows 11 build 28120.2738 (arm64)
- WSL 3.0.1. WSL 2.7.x has the same problem.
- Stock kernel
6.18.54.1-microsoft-standard-WSL2(attached log) - Default Windows and WSL settings (the VM gets all 72 vCPUs)
Repro
The script below needs only PyTorch, and no root. It writes a 512 MiB file, evicts it from the page cache, and copies it out of a read-only mmap in 32 MiB tensors, the same way PyTorch apps load model weights. It runs once with 4 threads and once with one thread per vCPU.
pip install torch --index-url https://download.pytorch.org/whl/cpu
python3 mmap_fault_repro.py # 4 threads, then one per vCPU
python3 mmap_fault_repro.py 4 16 72 # or pick thread counts
mmap_fault_repro.py
#!/usr/bin/env python3
"""Many threads page-faulting the same cold file mmap stall an arm64 WSL2 VM.
Copies a 512 MiB file out of a read-only mmap with PyTorch, the way PyTorch apps
load model weights: each 32 MiB tensor is copied with tensor.copy_(), which splits
the copy across all threads. The file is evicted from the page cache first, so the
threads page-fault it in, all within the same tensor at a time.
pip install torch --index-url https://download.pytorch.org/whl/cpu
python3 mmap_fault_repro.py [THREADS ...] # default: 4, then one per CPU
72-vCPU arm64 WSL2 VM: 4 threads take ~0.1 s, 72 threads take minutes (almost all system time).
"""
import mmap
import os
import platform
import resource
import sys
import time
import warnings
import torch
PATH = os.path.expanduser("~/mmap_fault_repro.bin") # on the Linux filesystem, not /mnt/c
TENSORS, TENSOR_BYTES = 16, 32 << 20
warnings.filterwarnings("ignore", "The given buffer is not writable")
def make_file():
if os.path.exists(PATH) and os.path.getsize(PATH) == TENSORS * TENSOR_BYTES:
return
with open(PATH, "wb") as f:
for _ in range(TENSORS):
f.write(bytes(TENSOR_BYTES))
os.fsync(f.fileno()) # dirty pages can't be evicted
def copy_from_cold_mmap(threads):
torch.set_num_threads(threads)
with open(PATH, "rb") as f:
os.posix_fadvise(f.fileno(), 0, 0, os.POSIX_FADV_DONTNEED) # evict from the page cache
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
src = [torch.frombuffer(m, dtype=torch.float16, count=TENSOR_BYTES // 2, offset=i * TENSOR_BYTES)
for i in range(TENSORS)]
dst = [torch.empty_like(s) for s in src]
r0, t0 = resource.getrusage(resource.RUSAGE_SELF), time.monotonic()
for s, d in zip(src, dst):
d.copy_(s)
wall, r1 = time.monotonic() - t0, resource.getrusage(resource.RUSAGE_SELF)
print(f"threads={threads:<3} wall={wall:7.2f} s sys={r1.ru_stime - r0.ru_stime:8.1f} s", flush=True)
def main():
print(f"kernel {platform.release()} {platform.machine()}, {os.cpu_count()} CPUs, torch {torch.__version__}")
make_file()
for threads in [int(a) for a in sys.argv[1:]] or [4, os.cpu_count()]:
copy_from_cold_mmap(threads)
if __name__ == "__main__":
main()
Output on the stock kernel:
kernel 6.18.40.1-microsoft-standard-WSL2 aarch64, 72 CPUs, torch 2.14.1+cu130
threads=4 wall= 0.27 s sys= 0.4 s
threads=72 wall= 308.26 s sys= 13775.2 s
Across runs, 4 threads take 0.1–0.3 s. 72 threads take 110–310 s, and the system time adds up to 5,000–13,800 CPU-seconds. Nearly all of that time is vCPUs spinning in the kernel.
Why it happens
All of these page faults go through the same kernel spinlock, the one that protects the file's page cache. Here is the chain of events:
- arm64 Linux spinlocks are strictly FIFO. When the lock is released, it goes to the next waiter in line, and only that waiter can take it.
- In a VM, that waiter might not be running. The host schedules vCPUs like ordinary threads, so any vCPU can be descheduled at any moment.
- If the next waiter isn't running, nobody gets the lock. Every other vCPU keeps spinning until the host runs that one vCPU again. A handoff that normally takes nanoseconds now takes milliseconds.
- With dozens of vCPUs queued up, this compounds. The VM spends almost all its time spinning, and anything else that needs the same lock, or just needs a CPU, stalls too. That's where the RCU stalls and watchdog kills come from.
x86 guests don't have this problem. Under Hyper-V, x86 Linux uses Hyper-V's paravirtualized spinlocks when the host supports them: a waiting vCPU halts and is woken by the hypervisor instead of spinning. Otherwise it falls back to virt_spin_lock(), a simple unfair test-and-set lock that x86 Linux uses under any hypervisor. Any running vCPU can grab that lock. Either way, a descheduled waiter doesn't block everyone else.
arm64 has neither. Hyper-V has no paravirtualized spinlocks for arm64 guests, and arm64 Linux has no virt_spin_lock() fallback. So arm64 WSL2 is stuck with strict FIFO handoff and no protection against preempted waiters.
Proposed fix
Give arm64 the same fallback x86 has, and turn it on under Hyper-V:
- Add an arm64
virt_spin_lock()that mirrors x86's test-and-set fallback, behind a static key that is off by default. Bare-metal arm64 and other hypervisors see no change. - Turn the key on in the arm64 Hyper-V early init, before secondary CPUs come up.
Longer term, it may be worth looking at Hyper-V paravirtualized spinlocks for arm64 guests, like hv_init_spinlocks() on x86. Waiting vCPUs would then halt instead of spinning, and arm64 guests would behave the same as x86 guests under Hyper-V.
Workarounds until then
These make the stall less likely, but they don't prevent it:
- Limit the VM's vCPUs with
processors=in.wslconfig. - Limit thread counts in the app (
OMP_NUM_THREADS,torch.set_num_threads()).
Expected Behavior
The VM should not hang when many threads page-fault a cold mmap'd file at the same time.
Actual Behavior
The whole VM hangs for minutes, with RCU stall warnings and systemd watchdog kills (see the description above).
Diagnostic Logs
Journal log during the time that the repro script ran:
- Ngôn ngữ chính
- C++
- Star
- 34.3k
- Fork
- 1.9k
- Merge trung bình
- 1 ngày 13 giờ
- Pull request đã merge (30 ngày)
- 166
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của microsoft/WSL
-
feature
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
microsoft/WSL#42114 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
feature kernel wsl2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
microsoft/WSL#41107 · 3 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
feature kernel
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
microsoft/WSL#41054 · 9 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
feature GPU
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 70/100
microsoft/WSL#13592 · 4 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
feature
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
microsoft/WSL#11458 · 6 bình luận · 8 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của microsoft/WSL
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
DarkCastleMUD/DarkCastle#283 ·
-
Toggle state is unclearĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
-
-priority- blocked good first issue help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
scribe-org/Scribe-Android#761 · 2 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Bug Testing
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
SCIInstitute/SCIRun#2931 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 5 ngày