Skip to content

macOS: Repeated main-thread hang in CAMetalLayer::nextDrawable due to setAllowsNextDrawableTimeout: NO #53390

Closed as duplicate

Description

@laris

Reproduction steps

  1. Use Zed Preview (0.231.1) on macOS with an external monitor.
  2. Leave Zed running for extended periods (hours), optionally with agent/ACP sessions active.
  3. The Zed UI becomes completely frozen — the entire process is kernel-suspended.
  4. Force quit is required to recover.

The hang reproduces reliably after idle periods, after sleep/wake cycles, or when other processes create IOKit contention (Chrome GPU activity, etc.). I have captured 7 independent hang reports over 5 days, all showing the same root cause.

Observed behavior

The main thread blocks indefinitely in CAMetalLayer::nextDrawable because setAllowsNextDrawableTimeout: NO is set in metal_renderer.rs:162. When any external factor delays IOKit (another process holding a kernel rwlock, GPU pressure, sleep/wake), nextDrawable blocks forever, macOS detects the hung main thread and kernel-suspends the entire process, which prevents GPU work from completing — creating a deadlock.

Hang Report Analysis (7 incidents)

# Date/Time Duration PID Uptime Memory Free Disk Main Thread Blocked At External Factor
1 2026-03-05 19:39 44.93s 66444 140s 251 MB 61.6 GB truncated → _sigtramp → kernel suspended MxNotify deadlock
2 2026-03-05 20:39 1.37s 65539 773s 357 MB 60.5 GB truncated → _sigtramp → kernel suspended MxNotify deadlock
3 2026-04-05 15:37 1.45s 69996 1985s 1680 MB 62.6 GB truncated → _sigtramp → kernel suspended MxNotify + OneDrive deadlock
4 2026-04-07 08:04 1.14s 39401 217s 426 MB 53.1 GB MarkdownElement::pop_div (layout) → _sigtramp → kernel suspended MxNotify deadlock
5 2026-04-07 10:19 1.38s 74206 4785s 959 MB 51.7 GB Window::draw → _sigtramp → kernel suspended MxNotify deadlock
6 2026-04-07 10:20 22.31s 86304 77s 937 MB 51.6 GB Window::draw → CAMetalLayer::nextDrawable → IOConnectCallMethod → kernel MxNotify deadlock
7 2026-04-08 10:49 0.83s 42362 77749s 5428→2298 MB 32.7 GB truncated → _sigtramp → kernel suspended Chrome kernel rwlock

Key observations:

  • Every thread in the Zed process shows (suspended) — this is a process-wide kernel suspension, not a userspace deadlock.
  • The main thread always ends in _sigtramp → kernel suspended at kernel.release.t8122 + 753348.
  • Hang Collaborative code editor for internal use #6 captures the full stack showing the exact blocking path (see below).
  • The hang occurs with different external triggers (MxNotify deadlock, Chrome IO, etc.), confirming it is not caused by any specific external process but by Zed's vulnerability to any IOKit contention.
  • Hang Add a README with a high-level roadmap #7 occurred after removing MxNotify entirely, proving the bug is in Zed.

Full stack trace from Hang #6 (22.31s, most detailed)

Thread 0x1d1e846  DispatchQueue "com.apple.main-thread"(1)  Thread name "main"
  14  start + 7184 (dyld)
    14  main + 52 (zed)
      14  std::rt::lang_start_internal
        14  zed::main
          14  Application::run
            14  MacPlatform::Platform::run
              14  -[NSApplication run]
                14  _DPSNextEvent → _DPSBlockUntilNextEventMatchingListInMode
                  14  _CFRunLoopRunSpecificWithOptions
                    14  __CFRunLoopRun
                      14  __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
                        14  _dispatch_main_queue_drain
                          14  _dispatch_source_invoke
                            14  gpui_macos::window::step
                              14  Window::new (step closure)
                                14  Window::draw → MacWindow::PlatformWindow::draw
                                  14  -[CAMetalLayer nextDrawable]        ← BLOCKS HERE
                                    14  CAMetalLayerPrivateNextDrawableLocked
                                      14  -[CAMetalDrawable initWithDrawablePrivate:layer:waitStart:]
                                        14  -[IOSurfaceSharedEvent initWithOptions:]
                                          14  IOConnectCallMethod → io_connect_method
                                            14  _sigtramp + 0
                                             *14  kernel (suspended)

Full stack trace from Hang #4 (layout path)

Thread main:
  14  gpui_macos::window::step
    14  Window::new (step closure)
      14  Window::draw
        14  gpui::elements::list::StateInner::layout_items
          14  Div::request_layout (agent_ui)
            14  MarkdownElement::request_layout
              14  MarkdownElementBuilder::pop_div
                14  _sigtramp → kernel suspended

Root cause

In crates/gpui_macos/src/metal_renderer.rs:162:

let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];

This disables the drawable timeout on CAMetalLayer. Per Apple documentation, when this is NO, nextDrawable() blocks indefinitely if no drawable buffer is available. Combined with:

  1. set_maximum_drawable_count(3) — only 3 drawables in the pool
  2. Synchronous presents_with_transaction paths in display_layer and windowDidBecomeKey that call wait_until_scheduled() on the main thread
  3. VRR/InputRateTracker sustaining continuous presentation during active input

If the GPU pipeline stalls for any reason (IOKit contention from another process, sleep/wake, memory pressure), all 3 drawables remain in-flight, and the next nextDrawable() call blocks the main thread forever. macOS then kernel-suspends the entire process, which prevents GPU command buffers from completing — a circular deadlock.

Suggested fix

Change setAllowsNextDrawableTimeout to YES (the default), or handle the nil return from nextDrawable() when it times out:

// Option A: re-enable timeout (simplest)
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: YES];

// Option B: handle nil drawable gracefully
let drawable: *mut Object = msg_send![&*layer, nextDrawable];
if drawable.is_null() {
    // Skip this frame, try again next vsync
    return;
}

Related issues

Zed version and system specs

Zed Preview: v0.231.1 (20260402.194205)
OS: macOS 26.3.1 (Build 25D771280a)
Memory: 24 GB
Architecture: aarch64 (Apple M3)
Display: DELL U4025QW 5120x2160 (external) + built-in 4608x2880
Hardware: Mac15,12 (MacBook Air M3)

Activity

  1. added
    state:needs triagetriage is not complete: no `area:*` label, or no `severity:*`/`reach:*` for Bugs/Crashes
    on Apr 8, 2026
  2. yeskunall commented on Apr 13, 2026

    @yeskunall
    Contributor

    This is a dupe of #7940 but your reproduction is helpful @laris so I’ll be copying it over to that thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:needs triagetriage is not complete: no `area:*` label, or no `severity:*`/`reach:*` for Bugs/Crashes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions