Brushes panel redraw recursion freezes Photoshop
## Summary
With the **Brushes panel open**, rapidly increasing/decreasing brush size sends the
main thread into a runaway AppKit redraw loop. Photoshop becomes unresponsive
(spinning beachball) at 120-220% CPU for tens of seconds at a time, several times
per hour.
**Closing the Brushes panel eliminates the freeze completely.** Everything else can
stay enabled, including GPU and GPU compositing.
This is a hang, not a crash: **no crash reports are generated** (zero `.crash` /
`.ips` files), which is likely why it has gone unreported.
## Environment
| | |
|---|---|
| Photoshop | 27.10.0 20260824.r.26 9d9635d (x64) |
| macOS | 26.6.2 (build 25G83) |
| Machine | Mac Pro (MacPro7,1) |
| CPU | Intel Xeon W-3223 @ 3.50 GHz |
| RAM | 96 GB |
| GPU | AMD Radeon Pro 580X, 8 GB VRAM, Metal 2 |
| Displays | 2 (2560x1440, scale 1) |
| Tablet | reproduced with both Wacom driver and OpenTabletDriver, and with a plain mouse |
## Steps to reproduce
1. Open a working document
2. Open the **Brushes panel** (Window > Brushes)
3. Select any brush tool
4. Rapidly increase and decrease brush size (bracket keys or ctrl+alt+drag),
repeatedly and quickly
5. Within seconds to a couple of minutes, Photoshop stops responding and shows the
spinning beachball; CPU rises to 120-220%
**Expected**: brush preview redraws keep up, or coalesce.
**Actual**: the main thread enters a deep recursive redraw and stops servicing events.
## Evidence: sampled stacks captured DURING the freeze
Six samples taken with `/usr/bin/sample` while the application was hung (samples
taken outside the hang show the main thread 99% idle in `mach_msg2_trap`, so timing
is essential).
Representative main thread stack:
```
CA::Transaction::commit() 1525 / 2507 samples (61%)
CA::Context::commit_transaction()
CA::Layer::display_if_needed()
-[NSViewBackingLayer display] 1229 samples
-[NSView(NSLayerKitGlue) _drawViewBackingLayer:inContext:drawingHandler:]
-[NSView _recursive:displayRectIgnoringOpacity:inContext:stopAtLayerBackedViews:]
-[NSView _recursive:displayRectIgnoringOpacity:...] (5 nested levels)
_NSViewDrawRect
<Photoshop> +0x2aa35a9 <-- calls ITSELF 50-62 times
<Photoshop> +0x2aa33e2
```
The recursing frame `Adobe Photoshop 2026 + 0x2aa35a9` appears **50 to 62 times in a
single stack**, spanning roughly 80 nesting levels. Hottest leaves underneath it:
`+0x6497ab5` (718 samples) and `+0x64ae8a3` (711 samples).
`mouseMoved` accounts for only **4-8 samples out of 2507**, ruling out input event
flooding as the driver of the loop.
## Evidence: controlled comparison
`CA::Transaction::commit` sample counts, same machine, same user, same document:
| Configuration | `CA::Transaction::commit` | Freezes? |
|---|---|---|
| Brushes panel **open**, GPU compositing on | **1525** | yes |
| Brushes panel **open**, GPU compositing off | 101-216 | **yes** |
| Brushes panel **closed**, GPU compositing on | 36-155 | **no** |
Disabling GPU compositing reduces the loop roughly tenfold but does **not** prevent
the freeze. Closing the panel does.
Healthy high-CPU work looks completely different in the samples: 48-64
`MultiProcessor` worker threads plus `_handleMouseDownEvent`, at up to 791% CPU,
with no freeze.
## Ruled out (each verified, not assumed)
- **Brush library size**: reduced `Brushes.psp` from 103 MB to 6 MB, no change.
A second user on the **same machine** has **108 MB / 399 brushes / 13 groups**
and never freezes.
- **Corrupt preferences**: full preferences reset (cmd+opt+shift on launch), no change.
- **GPU hardware**: `sniffer-out.txt` clean for both users, same adapter, same VRAM,
no errors. Disabling the GPU entirely removes the Metal wait but makes redraw
unusably slow and is not an acceptable workaround.
- **Third-party plugins**: none installed. The only non-Adobe-looking module,
`dynamic-torqnative`, is Adobe code and was idle in `condition_variable::wait`.
- **Tablet driver**: identical behaviour with Wacom 6.4.14-2, with OpenTabletDriver,
and with a plain mouse.
- **Content Credentials / CAI**: disabled, no change.
- **RAM / disk / scratch**: 96 GB RAM with zero swap, 511 GB free on the system volume.
- **PSErrorLog**: the *unaffected* user has **1211** `REQUIRE failed` entries versus
**50** for the affected user, so log errors do not correlate with the freeze.
- **Second machine**: the same user, same Adobe ID, same files, same gesture on a
different Mac never freezes.
## Workaround
Keep the Brushes panel closed and change brushes from the brush preset picker in the
options bar. With the panel closed, GPU and GPU compositing can both remain enabled
and performance is unaffected.
## What would help
The recursing frame is at a fixed offset (`+0x2aa35a9` in the 27.10.0 x64 build) and
should be identifiable from Adobe's symbol files. Full `sample` output available on
request.
---
