diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go index 69ec44781..80b2b9ded 100644 --- a/pkg/gui/gui_common.go +++ b/pkg/gui/gui_common.go @@ -58,7 +58,18 @@ func (self *guiCommon) PauseBackgroundRefreshes(pause bool) { self.gui.BackgroundRoutineMgr.PauseBackgroundRefreshes(pause) } +// assertOnUIThread panics (in debug builds) if called from a worker goroutine. +// Use it to guard accessors for state that only the UI thread may touch, so +// that a stray worker access fails deterministically -- and points at itself -- +// rather than surfacing later as a probabilistic data race. +func (self *guiCommon) assertOnUIThread(accessor string) { + if self.GetConfig().GetDebug() && !self.GocuiGui().IsUIThread() { + panic(accessor + " accessed from a worker") + } +} + func (self *guiCommon) Context() types.IContextMgr { + self.assertOnUIThread("Context()") return self.gui.State.ContextMgr } @@ -113,6 +124,7 @@ func (self *guiCommon) Modes() *types.Modes { } func (self *guiCommon) Model() *types.Model { + self.assertOnUIThread("Model()") return self.gui.State.Model } diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go index 31094b253..7bd31d93d 100644 --- a/pkg/gui/gui_driver.go +++ b/pkg/gui/gui_driver.go @@ -92,7 +92,10 @@ func (self *GuiDriver) Keys() config.KeybindingConfig { } func (self *GuiDriver) CurrentContext() types.Context { - return self.gui.c.Context().Current() + // Read the context manager directly rather than through c.Context(): the + // driver runs on the test goroutine, not the UI thread, so it must bypass + // the UI-thread assertion that accessor carries. + return self.gui.State.ContextMgr.Current() } func (self *GuiDriver) ContextForView(viewName string) types.Context {