Skip to content

fix(app): guard the dirty-boundary set against concurrent marks - #203

Merged
kolkov merged 1 commit into
gogpu:mainfrom
samyfodil:fix/dirty-boundary-race-pr
Aug 3, 2026
Merged

fix(app): guard the dirty-boundary set against concurrent marks#203
kolkov merged 1 commit into
gogpu:mainfrom
samyfodil:fix/dirty-boundary-race-pr

Conversation

@samyfodil

Copy link
Copy Markdown
Contributor

Marking a boundary dirty is locked at every hop but the last one. SetNeedsRedraw, InvalidateScene and RegisterDirtyBoundary each take their mutex, snapshot, and release it before calling out — the shape of code meant to be reached from another goroutine. Window.AddDirtyBoundary then writes a bare map.

That is the call pattern of any app whose content arrives on its own goroutines: a terminal pane per shell, a video surface, a download. With one such goroutine the race stays invisible. With two, the runtime takes the process down with concurrent map writes, which no recover can catch.

Found in a terminal emulator on gogpu/ui where each pane repaints from its own PTY reader: one pane was fine for weeks, two panes printing at once was a hard crash.

The fix is a mutex on the four methods that touch dirtyBoundaries — an uncontended lock per mark, against a map write that was already there.

Rebased onto v0.1.49. go build ./... and go test ./app/... ./widget/... pass.

Marking a boundary dirty is locked at every hop but this one: SetNeedsRedraw,
InvalidateScene and RegisterDirtyBoundary each take their mutex, snapshot, and
release before calling out. AddDirtyBoundary then writes a bare map.

That is the call pattern of any app whose content arrives on its own
goroutines — a terminal pane per shell, a video surface, a download. With one
such goroutine the race stayed invisible; with two the runtime takes the
process down with "concurrent map writes", which no recover can catch.

An uncontended mutex per mark, against a map write that was already there.
@samyfodil
samyfodil requested a review from kolkov as a code owner August 2, 2026 22:26
@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kolkov kolkov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated: Flutter is single-threaded (no locks), Qt serializes through event queue. sync.Mutex is the correct Go equivalent — RWMutex overhead not justified for nanosecond critical sections. No other unprotected maps in Window. Closes a real production crash (concurrent map writes from multi-pane terminal).

@kolkov
kolkov merged commit e81f503 into gogpu:main Aug 3, 2026
9 checks passed
@kolkov kolkov mentioned this pull request Aug 3, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants