# gpui: Fix deadlock in performance profiler and reenable it \(\#61584\) · gitcafe/zed

[View on GitCafe](https://git.cafe/gitcafe/zed/commit/c16d19c94dbc1c62752b736186ab8fd6f1cd25c6)

Repository: [gitcafe/zed](https://git.cafe/gitcafe/zed)

Visibility: public

Requested revision: c16d19c94dbc1c62752b736186ab8fd6f1cd25c6

Requested commit: c16d19c94dbc1c62752b736186ab8fd6f1cd25c6

Commit: c16d19c94dbc1c62752b736186ab8fd6f1cd25c6

Tree: 84abd7c352586fda5b94214c33e11f564a825f2d

Author: Anthony Eid

Committer: GitHub

## Message

````
gpui: Fix deadlock in performance profiler and reenable it (#61584)

### Summary

PR #58942 disabled the performance profiler within Zed because there was
a race condition that caused Zed to hang forever due to a deadlock
involving the foreground thread. This PR fixes the deadlock and
re-enables the performance profiler.

The deadlock happened because `ThreadTimings::drop()` locks
`GLOBAL_THREAD_TIMINGS`, and that drop could run in places where
`GLOBAL_THREAD_TIMINGS` was already locked: the collection paths
(`get_all_timings`, `take_all_stats`, `set_trace_enabled(false)`) held
the global lock while upgrading and then dropping per-thread
`Arc<GuardedTaskTimings>` handles. If a worker thread exited in that
window (e.g. GCD reclaiming an idle thread), the collector inherited the
last strong reference, and dropping it ran `ThreadTimings::drop` ->
`GLOBAL_THREAD_TIMINGS.lock()` reentrantly on the same thread. The
spinlock is not reentrant, so the thread spun forever while holding the
lock, hanging every other thread that touched the profiler.

The fix: hold `GLOBAL_THREAD_TIMINGS` only long enough to upgrade the
`Weak` handles (`upgraded_thread_timings()`), and release it before any
per-thread buffer is locked, copied, or dropped. A last-reference drop
now always runs with the global lock free. As a side benefit, the
up-to-16MiB per-thread buffer copies no longer happen under the global
lock.

### Diagram

```mermaid
sequenceDiagram
    participant C as Collector thread (get_all_timings)
    participant G as GLOBAL_THREAD_TIMINGS (spin::Mutex)
    participant T as Worker thread (exiting)

    Note over T: holds the only strong Arc<br/>in its THREAD_TIMINGS TLS
    C->>G: lock() — guard held for entire collection
    C->>C: Weak::upgrade() (strong: 1 → 2)
    T->>T: thread exits, TLS destructor drops its Arc (strong: 2 → 1)
    C->>C: temp Arc dropped at end of iteration (strong: 1 → 0)
    C->>C: ThreadTimings::drop() runs on collector thread
    C->>G: lock() again — already held by this thread
    Note over C,G: spin lock is not reentrant → spins forever,<br/>every other profiler user spins behind it

    Note over C,T: Fix: upgrade all Weaks under the lock, release it,<br/>then lock/copy/drop per-thread handles — the reentrant<br/>drop can now only ever run with the global lock free
```

Release Notes:

- Fixed a deadlock in the performance profiler and re-enabled it (`zed:
open performance profiler`)
````

## Parents

- [f7d481c4156924089c4f98f08aa88a605d5e8ea9](https://git.cafe/gitcafe/zed/commit/f7d481c4156924089c4f98f08aa88a605d5e8ea9?format=markdown)

[Source at this commit](https://git.cafe/gitcafe/zed/tree/c16d19c94dbc1c62752b736186ab8fd6f1cd25c6?format=markdown)
