Repository navigation
fix(gles): keep surface-created EGL context alive past Surface.Destroy - #362
Conversation
On Linux the AdapterContext that backs an EGL context may be created by the
Surface instead of the Instance (Wayland, or when the detected window kind
differs from the real window's kind, e.g. an X11 window under a Wayland session).
In that case Surface.SetFail owned the context and Surface.Destroy freed it.
The Adapter, Device and Queue created from that Surface hold the same
*AdapterContext and outlive the Surface. Closing the window freed the EGL
context first; the later Device.Destroy locked a nil EGL context ("gles:
AdapterContext.Lock: nil egl context") and dereferenced the nil *gl.Context in
DeleteVertexArrays, crashing during app shutdown:
panic: runtime error: invalid memory address or nil pointer dereference
... gles.(*Device).Destroy -> gl.(*Context).DeleteVertexArrays(0x0)
Adopt ownership on the Instance when CreateSurface has to create the context.
The Instance is released after the Device and Surface, so the shared context now
lives as long as everything that uses it. Region Path B now always returns
ownsContext=false.
Adds an integration regression test that destroys a Surface sharing an adopted
context, then destroys the Device, then the Instance.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
# Conflicts: # CHANGELOG.md
kolkov
left a comment
There was a problem hiding this comment.
Thanks @admin-else. The diagnosis matches the code: the Path B context was owned by the Surface while the Device kept using it, and moving ownership to the Instance fixes the shutdown order.
I checked it merged with current main:
- On Linux (Mesa, EGL)
TestSurfaceDoesNotDestroyAdoptedContextpasses, and so does the whole-tags integration ./hal/gles/...suite. go test ./...,go mod tidy, golangci-lint (incl.-tags integrationfor linux) and the cross-builds are clean.mainmoved since you opened this, soCHANGELOG.mdconflicted: #363 and #359 both added[Unreleased]entries. I mergedmaininto your branch keeping both sides (02f72aa) and added the PR number and your handle to your entry (b3cd482). Nothing needed from you.
Optional, a question about adoptContext: when the Instance already has its own context that doesn't match the surface's window kind (X11 or headless detection, then a surface of another kind), prev.Destroy() runs. If someone opened a Device from that instance context before creating the surface (EnumerateAdapters(nil), compute first, window later), that Device loses its context, which is the same class of crash this PR fixes. On Wayland the instance context is skipped, so the common case is fine. Keeping prev alive until Instance.Destroy would cover it. Fine as a follow-up if you'd rather keep this PR small.
Approving. Because of the merge from main, this goes in as one squashed commit with you as the author.
Summary
On Linux GLES, a
Surfacethat has to create its own EGL context (Wayland, or when the detected window kind does not match the real window) owned that context and freed it inSurface.Destroy. TheAdapter,Device, andQueuecreated from that Surface share the same*AdapterContextand outlive the Surface, so closing the window freed the EGL context first and the laterDevice.Destroylocked a nil EGL context.Reproduced signature:
This crashes any app that closes a window under GLES when the context is Surface-owned.
Root cause
Instance.CreateSurfacehas two paths:AdapterContext—ownsContext=false, so the Instance (released last) frees it.AdapterContextwithownsContext=true.AdapterContext.DestroysetseglCtx=nilandgl=nil. In Path B the Surface then destroyed the context thatDevice/Queue/Adapterstill referenced. On shutdown gogpu destroys the Surface (window close), then the Device, then the Instance, so the Device hit a dead context:Lock()loggednil egl context, returned the nilgltable, andDeleteVertexArraysdereferenced nil.Fix
CreateSurfacenow adopts the Path B context on theInstance. The Instance is released after the Device and Surface, so the shared context lives as long as everything that uses it. Path B now returnsownsContext=false; theInstance.ctxfield is guarded by a mutex.This mirrors the Windows/X11 model where the context is owned by the Instance, not the Surface.
Testing
go test ./core/ ./hal/...— passgo test -tags integration ./hal/gles/— pass, including a new regression testTestSurfaceDoesNotDestroyAdoptedContextthat destroys a Surface sharing an adopted context, then the Device, then the Instance (the exact shutdown order that used to panic).go build ./...— passRelated
Follow-up to the GLES Linux AdapterContext work (FEAT-GLES-003, #332).