⚠️ Note: This article has been archived

This post records an earlier troubleshooting session for input method lag and is preserved for historical reference only. For the comprehensive best practice guide that combines low-latency input with automated host memory reclamation, see: WSL2 Low Latency Tuning Guide.

Running into the Same Problem?

Typing Chinese in a WSL2 terminal feels “sluggish” — a sticky delay between keystroke and character appearing on screen.

Run these three lines first to confirm it’s the same issue, then jump straight to Quick Fix.

Inside WSL:

cat /proc/sys/vm/swappiness   # Default 60. >10 with plenty of free RAM → threshold too loose
free -h | grep Swap           # Plenty of free RAM but hundreds of MB in swap → likely legacy/thrashing
df -h /mnt/c/ | tail -1       # System drive >80% → IME file I/O affected

Root Cause in One Sentence

Three separate lines all bottlenecked on the keystroke-to-echo path:

Keystroke → [Windows Input Pipeline] ← C: drive full, IME sluggish
          → Terminal                ← same as above
          → [ConPTY → WSL]         ← autoMemoryReclaim interrupts VM frequently
          → shell → echo           ← swap thrashing, PTY echo delayed
  1. C: drive near full → Windows input method framework and terminal rendering both slow down
  2. swappiness=60 (default) → system pushes processes into swap despite 18 GB of free RAM
  3. swap thrashing → swapped-out pages are intermittently read back, I/O lands right in the PTY echo window

One layer alone wouldn’t be noticeable. Three layers stacked, landing in the tens of milliseconds between keystroke and echo.

Quick Fix

① Lower swappiness + persist (immediate effect)

Inside WSL:

sudo sysctl vm.swappiness=10                                          # Takes effect immediately
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf                # Loaded by systemd on boot

⚠️ Before editing /etc/sysctl.conf, check if a vm.swappiness entry already exists — modify it if present, append if not. Avoid duplicate lines.

⚠️ ② Clear swap (watch out for WSL2 specifics)

WSL2’s swap isn’t managed by /etc/fstabswapon -a does nothing for it.

Inside WSL:

# 1. Find what's occupying swap
for pid in /proc/*/status; do
  swp=$(grep VmSwap $pid | awk '{print $2}')
  [ "${swp:-0}" -gt 10000 ] && echo "$swp KB - $(grep Name $pid) (PID=$(basename $(dirname $pid)))"
done | sort -rn

This lists process names and PIDs, e.g. 1039088 KB - node-MainThread (PID=1200819).

# 2. Kill the biggest offenders (replace with actual PIDs from the output above)
kill -9 <PID1> <PID2>

# 3. Wipe and rebuild swap
sudo swapoff -a                   # Deactivate swap (clears the signature)
sudo mkswap /dev/sdc && sudo swapon /dev/sdc   # Rebuild signature and re-enable

③ Clean up the C: drive (Windows side)

In Windows PowerShell:

rd /s /q %TEMP%                  # Clear temp files
powercfg /hibernate off          # Disable hibernation, frees ~16 GB
cleanmgr /sagerun:1              # Disk cleanup

④ Set autoMemoryReclaim to disabled

Edit C:\Users\<username>\.wslconfig on Windows (or /mnt/c/Users/<username>/.wslconfig inside WSL):

disabled means WSL won’t return freed memory to Windows, but internal WSL reuse is unaffected — 20 GB is a cap, not a reservation. Actual memory usage depends entirely on your workload. The benefit of disabled is eliminating the HYP interrupts caused by gradual, making WSL more stable. The only cost: freed idle memory inside WSL won’t be immediately returned to Windows (internal reuse remains unaffected).

[experimental]
autoMemoryReclaim=disabled

⑤ Reboot (or manually clean up leftover processes)

After the first four fixes, the keyboard still felt slightly off. Turned out there were 26 stale zsh sessions and 8 gitstatusd daemons accumulated over 15 days without a reboot. A full system reboot cleared everything with WSL restarting.

Or, without rebooting, manually handle it inside WSL:

pkill gitstatusd          # Kill stale git monitoring processes
# Find orphan zsh sessions (PPID=1) and kill them:
ps -eo pid,ppid,cmd | awk '$2==1 && /zsh/ {print $1}' | xargs kill -9

Results

MetricBeforeAfter
swappiness6010 (persisted)
Swap used2.0 GB8.6 MB
C: drive81%64%
Swap I/Oconstant > 00
Input methodnoticeably laggyresponsive

Full Troubleshooting Log

What follows is the real-time record of the layer-by-layer investigation. Read on if you’re curious about the “why” or want to learn the debugging approach.

Symptoms

Typing Chinese in a WSL2 terminal felt laggy — a sticky, delayed sensation between pressing a key and seeing the character appear. Not a full freeze, just “not responsive.”

Pitfall: I didn’t know the full pipeline at first

IME lag requires tracing the entire keystroke-to-echo path. I reconstructed this pipeline after the fact using Microsoft documentation (Keyboard Input Overview + TSF + ConPTY architecture):

Keyboard hardware → [Windows Input Pipeline] → Terminal → [ConPTY → WSL] → shell → echo
                          ↑ IME lives here
  • Windows Input Pipeline: keyboard driver → scan codes → keyboard layout → TSF/IME processes Chinese → generates WM_CHAR character messages. IME isn’t a standalone step — it’s embedded in the input pipeline.
  • Terminal → ConPTY → WSL: Windows Terminal connects to WSL2 init via ConPTY (Windows Pseudo Console), which forwards to the Linux PTY, arriving at zsh.

But I didn’t know any of this during the investigation. I approached it by checking both ends — Windows side, then WSL side.

Step 1: Windows Task Manager → ruled out RAM

First instinct was memory pressure. Ctrl + Shift + Esc → Performance → Memory — usage was normal. Not the problem.

Step 2: WSL basic metrics → found swap anomaly

Inside WSL:

top -bn1 | head          # Load 0.19, CPU idle 97%
free -h                  # Free RAM 18.6 GB / Total 20 GB
cat /proc/meminfo        # Swap used: 2.6 GB ← red flag
cat /proc/sys/vm/swappiness  # = 60 (default)
MetricValueMeaning
swappiness60Swapping starts when free RAM drops below ~8 GB — threshold too loose
Swap used2.6 GB / 8 GBHistorical — swapped out during earlier long sessions when RAM was full; kernel doesn’t proactively swap back
14-day swap activity~50M page-ins, ~50M page-outsSwap wasn’t sitting idle — massive historical read/write volume

Swap usage (2.6 GB) is historical. Swap thrashing is the problem. The usage itself doesn’t cause lag. The intermittent page-in I/O from thrashing does.

Step 3: Who’s in swap

Inside WSL:

for pid in /proc/*/status; do
  swp=$(grep VmSwap $pid | awk '{print $2}')
  [ "$swp" -gt 0 ] && echo "$swp KB - $(grep Name $pid)"
done | sort -rn

Two orphan qwen (Tongyi Qianwen CLI) processes held 1.6 GB — each had allocated 44 GB of virtual address space but only 1–2 MB RSS. Their original terminals were long gone; the processes were adopted by systemd (PID 1).

Step 4: Confirmed swap thrashing

Inside WSL:

vmstat 1                    # si column consistently > 0
cat /proc/pressure/memory   # some avg10=3.48
cat /proc/pressure/io       # some avg10=3.97

Step 5: C: drive nearly full

Inside WSL:

df -h /mnt/c/   # 196 GB capacity, 159 GB used, 38 GB free (81%)

At 81% full, Windows IME’s dictionary reads and writes become sluggish. PowerShell scanning identified AppData\Local\Temp (8 GB) and hiberfil.sys (~16 GB) as the main culprits.

Root Cause Mapping

Keystroke → [Windows Input Pipeline] ← C: drive 81% full, IME file I/O slowed
          → Terminal                ← same as above
          → [ConPTY → WSL]         ← autoMemoryReclaim=gradual, VM frequently paused
          → shell → echo           ← swap thrashing, PTY echo delayed by I/O

A single layer’s delay might be imperceptible. All three stacked, landing in the few dozen milliseconds between keystroke and echo.

⚠️ Pitfall: WSL2 swap recovery

After swapoff -a, swapon -a does nothing — because WSL2’s swap isn’t managed by /etc/fstab (fstab is empty). Instead, WSL2 init directly mounts /dev/sdc. Recovery requires mkswap /dev/sdc && swapon /dev/sdc.

Lessons Learned

  • Debug WSL2 lag from both ends: rule out the obvious (Task Manager) first, then dig into what you normally overlook (swap, PSI, orphan processes).
  • Large free RAM ≠ no lag: swappiness=60 is too aggressive for desktop/WSL use. Lower it to 10 — don’t touch swap until RAM is truly needed.
  • Swap is about thrashing, not usage: 2 GB of swap usage doesn’t cause lag. Small, frequent page-in/page-out bursts do.
  • Orphan process swap is a liability too: inactive processes with 44 GB VSZ each in swap generate continuous page faults.

Glossary

Skim these if unfamiliar — not required to follow the fix above.

swappiness: Linux kernel parameter (0–100) controlling how aggressively memory is swapped out. Default is 60. At 10, the kernel only swaps when RAM is truly scarce.

swap (swap space) : a disk partition used as overflow when physical RAM runs low. WSL2 swap size is configured via swap=8G in .wslconfig.

PSI (Pressure Stall Information) : Linux kernel pressure metrics. /proc/pressure/memory and /proc/pressure/io show how much time the system spends stalled on memory or disk I/O.

HYP interrupt: Hyper-V callback interrupt. WSL2 runs as a Hyper-V VM, and the host (Windows) communicates with the VM through these interrupts. A count in the hundreds of millions indicates extremely frequent interaction.

ConPTY: Windows Pseudo Console — Windows Terminal connects to the Linux PTY inside WSL2 through this.

autoMemoryReclaim: WSL2 experimental feature controlling whether freed WSL memory is returned to Windows. gradual returns it slowly; disabled never returns it.

References