Skip to content

Fix userEvents panic#5793

Merged
stefanhaller merged 2 commits into
v0.63.1from
fix-userEvents-panic
Jul 15, 2026
Merged

Fix userEvents panic#5793
stefanhaller merged 2 commits into
v0.63.1from
fix-userEvents-panic

Conversation

@stefanhaller

Copy link
Copy Markdown
Collaborator

In #5756 we changed the userEvents channel to a fixed 256-slot channel with a non-blocking send that panicked when the channel was full. It turns out that this panic can happen in real use:

  • Toggling a directory of several hundred files into a custom patch (reliably): the operation runs on a worker behind a waiting status, whose spinner enqueues a content-only render on every tick, and over the long operation these outrun the UI loop and overflow the buffer.
  • Editing the config in an editor that suspends lazygit: the editor subprocess runs on the UI thread, so the loop drains nothing for the whole editing session, and the full refresh fired on resume fans out across every scope at once — a burst of updates that overflows before the just-resumed loop catches up.
  • Any time the UI thread blocks for a long time, the periodic refreshes keep enqueuing and eventually overflow.

The 256-slot buffer was chosen deliberately, with the panic as a "should never happen" guard, to preserve two properties: FIFO ordering of same-goroutine Update calls (an earlier goroutine-per-Update design reordered them), and no self-deadlock (a blocking send from the UI thread would block against the loop that drains it). But a fixed channel can only offer those by crashing on overflow.

Replace it with an unbounded, order-preserving queue: a mutex-guarded slice plus a buffered(1) doorbell channel that wakes the main loop's select. Enqueuing appends and rings the doorbell; the loop drains the slice to empty on each wake. This keeps FIFO order and never blocks the caller, so there is no self-deadlock and no overflow to panic on — under a stall the queue just grows and then drains.

Fixes #5772.

stefanhaller and others added 2 commits July 15, 2026 10:14
Update and friends enqueued onto a fixed 256-slot channel with a
non-blocking send that panicked when the channel was full. That guard
was firing in real use:

 - Toggling a directory of several hundred files into a custom patch
   (reliably): the operation runs on a worker behind a waiting status,
   whose spinner enqueues a content-only render on every tick, and over
   the long operation these outrun the UI loop and overflow the buffer.
 - Editing the config in an editor that suspends lazygit: the editor
   subprocess runs on the UI thread, so the loop drains nothing for the
   whole editing session, and the full refresh fired on resume fans out
   across every scope at once — a burst of updates that overflows before
   the just-resumed loop catches up.
 - Any time the UI thread blocks for a long time, the periodic refreshes
   keep enqueuing and eventually overflow.

The 256-slot buffer was chosen deliberately, with the panic as a
"should never happen" guard, to preserve two properties: FIFO ordering
of same-goroutine Update calls (an earlier goroutine-per-Update design
reordered them), and no self-deadlock (a blocking send from the UI
thread would block against the loop that drains it). But a fixed
channel can only offer those by crashing on overflow.

Replace it with an unbounded, order-preserving queue: a mutex-guarded
slice plus a buffered(1) doorbell channel that wakes the main loop's
select. Enqueuing appends and rings the doorbell; the loop drains the
slice to empty on each wake. This keeps FIFO order and never blocks the
caller, so there is no self-deadlock and no overflow to panic on — under
a stall the queue just grows and then drains.

This also removes an inconsistency: updateContentOnly did a plain
blocking send while update panicked, so the two paths disagreed on what
happened when the queue was full. Both now share the same enqueue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that the queue is unbounded, its depth is a useful signal for
understanding how the event loop behaves under load — and we expect it
to look very different across builds (e.g. master, which carries the
bounce-state-updates-to-ui-thread work, versus the v0.63.0 release this
fix ships in). Track the deepest the queue has ever been and log an Info
line whenever that record is broken, so the numbers show up in the log
for later reasoning. The mark is session-wide and doesn't reset when the
queue drains.

gocui has no logger of its own, so it exposes the new depth through a
handler (matching the existing SetFocusHandler / SetOpenHyperlinkFunc
pattern) that the gui registers to log via its own logger.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stefanhaller
stefanhaller force-pushed the fix-userEvents-panic branch from daffec1 to f0b139f Compare July 15, 2026 08:14
@stefanhaller
stefanhaller merged commit c2489e1 into v0.63.1 Jul 15, 2026
12 checks passed
@stefanhaller
stefanhaller deleted the fix-userEvents-panic branch July 15, 2026 08:18
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Jul 17, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [jesseduffield/lazygit](https://github.com/jesseduffield/lazygit) | patch | `v0.63.0` → `v0.63.1` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>jesseduffield/lazygit (jesseduffield/lazygit)</summary>

### [`v0.63.1`](https://github.com/jesseduffield/lazygit/releases/tag/v0.63.1)

[Compare Source](jesseduffield/lazygit@v0.63.0...v0.63.1)

<!-- Release notes generated using configuration in .github/release.yml at v0.63.1 -->

Fixes for a few regressions introduced in the v0.63.0 release.

#### What's Changed

##### Fixes 🔧

- Improve index.lock retry mechanism by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;5788](jesseduffield/lazygit#5788)
- Fix userEvents panic by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;5793](jesseduffield/lazygit#5793)
- Fix a deadlock on Windows when switching between longer diffs by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;5815](jesseduffield/lazygit#5815)

##### Maintenance ⚙️

- Allow releasing from a branch other than master by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;5819](jesseduffield/lazygit#5819)

**Full Changelog**: <jesseduffield/lazygit@v0.63.0...v0.63.1>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjMuNiIsInVwZGF0ZWRJblZlciI6IjQzLjI2My42IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6cGF0Y2giXX0=-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant