app: avoid race condition on the underlying driver

Instead of a single racy window.driver field, maintain a driver
reference for each goroutine that needs it: the window.run event loop
and the callbacks structure.

Fixes gio#230

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-06-04 10:04:23 +02:00
parent 965792c1a9
commit d95e6f2a15
+41 -34
View File
@@ -26,13 +26,12 @@ type Option func(opts *wm.Options)
// Window represents an operating system window. // Window represents an operating system window.
type Window struct { type Window struct {
driver wm.Driver ctx wm.Context
ctx wm.Context loop *renderLoop
loop *renderLoop
// driverFuncs is a channel of functions to run when // driverFuncs is a channel of functions to run when
// the Window has a valid driver. // the Window has a valid driver.
driverFuncs chan func() driverFuncs chan func(d wm.Driver)
// wakeups wakes up the native event loop to send a // wakeups wakes up the native event loop to send a
// wm.WakeupEvent that flushes driverFuncs. // wm.WakeupEvent that flushes driverFuncs.
wakeups chan struct{} wakeups chan struct{}
@@ -63,6 +62,7 @@ type Window struct {
type callbacks struct { type callbacks struct {
w *Window w *Window
d wm.Driver
} }
// queue is an event.Queue implementation that distributes system events // queue is an event.Queue implementation that distributes system events
@@ -71,8 +71,7 @@ type queue struct {
q router.Router q router.Router
} }
// driverEvent is sent when a new native driver // driverEvent is sent when the underlying driver changes.
// is available for the wm.
type driverEvent struct { type driverEvent struct {
driver wm.Driver driver wm.Driver
} }
@@ -107,7 +106,7 @@ func NewWindow(options ...Option) *Window {
invalidates: make(chan struct{}, 1), invalidates: make(chan struct{}, 1),
frames: make(chan *op.Ops), frames: make(chan *op.Ops),
frameAck: make(chan struct{}), frameAck: make(chan struct{}),
driverFuncs: make(chan func(), 1), driverFuncs: make(chan func(d wm.Driver), 1),
wakeups: make(chan struct{}, 1), wakeups: make(chan struct{}, 1),
dead: make(chan struct{}), dead: make(chan struct{}),
notifyAnimate: make(chan struct{}, 1), notifyAnimate: make(chan struct{}, 1),
@@ -132,7 +131,7 @@ func (w *Window) update(frame *op.Ops) {
<-w.frameAck <-w.frameAck
} }
func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync bool, frame *op.Ops) error { func (w *Window) validateAndProcess(driver wm.Driver, frameStart time.Time, size image.Point, sync bool, frame *op.Ops) error {
for { for {
if w.loop != nil { if w.loop != nil {
if err := w.loop.Flush(); err != nil { if err := w.loop.Flush(); err != nil {
@@ -145,7 +144,7 @@ func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync
} }
if w.loop == nil && !w.nocontext { if w.loop == nil && !w.nocontext {
var err error var err error
w.ctx, err = w.driver.NewContext() w.ctx, err = driver.NewContext()
if err != nil { if err != nil {
return err return err
} }
@@ -181,9 +180,9 @@ func (w *Window) processFrame(frameStart time.Time, size image.Point, frame *op.
w.queue.q.Frame(frame) w.queue.q.Frame(frame)
switch w.queue.q.TextInputState() { switch w.queue.q.TextInputState() {
case router.TextInputOpen: case router.TextInputOpen:
go w.Run(func() { w.driver.ShowTextInput(true) }) go w.driverRun(func(d wm.Driver) { d.ShowTextInput(true) })
case router.TextInputClose: case router.TextInputClose:
go w.Run(func() { w.driver.ShowTextInput(false) }) go w.driverRun(func(d wm.Driver) { d.ShowTextInput(false) })
} }
if txt, ok := w.queue.q.WriteClipboard(); ok { if txt, ok := w.queue.q.WriteClipboard(); ok {
go w.WriteClipboard(txt) go w.WriteClipboard(txt)
@@ -230,12 +229,12 @@ func (w *Window) Invalidate() {
// Option applies the options to the window. // Option applies the options to the window.
func (w *Window) Option(opts ...Option) { func (w *Window) Option(opts ...Option) {
go w.Run(func() { go w.driverRun(func(d wm.Driver) {
o := new(wm.Options) o := new(wm.Options)
for _, opt := range opts { for _, opt := range opts {
opt(o) opt(o)
} }
w.driver.Option(o) d.Option(o)
}) })
} }
@@ -243,22 +242,22 @@ func (w *Window) Option(opts ...Option) {
// of a clipboard.Event. Multiple reads may be coalesced // of a clipboard.Event. Multiple reads may be coalesced
// to a single event. // to a single event.
func (w *Window) ReadClipboard() { func (w *Window) ReadClipboard() {
go w.Run(func() { go w.driverRun(func(d wm.Driver) {
w.driver.ReadClipboard() d.ReadClipboard()
}) })
} }
// WriteClipboard writes a string to the clipboard. // WriteClipboard writes a string to the clipboard.
func (w *Window) WriteClipboard(s string) { func (w *Window) WriteClipboard(s string) {
go w.Run(func() { go w.driverRun(func(d wm.Driver) {
w.driver.WriteClipboard(s) d.WriteClipboard(s)
}) })
} }
// SetCursorName changes the current window cursor to name. // SetCursorName changes the current window cursor to name.
func (w *Window) SetCursorName(name pointer.CursorName) { func (w *Window) SetCursorName(name pointer.CursorName) {
go w.Run(func() { go w.driverRun(func(d wm.Driver) {
w.driver.SetCursor(name) d.SetCursor(name)
}) })
} }
@@ -268,8 +267,8 @@ func (w *Window) SetCursorName(name pointer.CursorName) {
// Currently, only macOS, Windows and X11 drivers implement this functionality, // Currently, only macOS, Windows and X11 drivers implement this functionality,
// all others are stubbed. // all others are stubbed.
func (w *Window) Close() { func (w *Window) Close() {
go w.Run(func() { go w.driverRun(func(d wm.Driver) {
w.driver.Close() d.Close()
}) })
} }
@@ -282,10 +281,16 @@ func (w *Window) Close() {
// Note that most programs should not call Run; configuring a Window with // Note that most programs should not call Run; configuring a Window with
// CustomRenderer is a notable exception. // CustomRenderer is a notable exception.
func (w *Window) Run(f func()) { func (w *Window) Run(f func()) {
done := make(chan struct{}) w.driverRun(func(_ wm.Driver) {
wrapper := func() {
f() f()
close(done) })
}
func (w *Window) driverRun(f func(d wm.Driver)) {
done := make(chan struct{})
wrapper := func(d wm.Driver) {
defer close(done)
f(d)
} }
select { select {
case w.driverFuncs <- wrapper: case w.driverFuncs <- wrapper:
@@ -336,26 +341,27 @@ func (w *Window) setNextFrame(at time.Time) {
} }
func (c *callbacks) SetDriver(d wm.Driver) { func (c *callbacks) SetDriver(d wm.Driver) {
c.d = d
c.Event(driverEvent{d}) c.Event(driverEvent{d})
} }
func (c *callbacks) Event(e event.Event) { func (c *callbacks) Event(e event.Event) {
select { select {
case c.w.in <- e: case c.w.in <- e:
c.w.runFuncs() c.w.runFuncs(c.d)
case <-c.w.dead: case <-c.w.dead:
} }
} }
func (w *Window) runFuncs() { func (w *Window) runFuncs(d wm.Driver) {
// Flush pending runnnables. // Flush pending runnnables.
loop: loop:
for { for {
select { select {
case <-w.notifyAnimate: case <-w.notifyAnimate:
w.driver.SetAnimating(w.animating) d.SetAnimating(w.animating)
case f := <-w.driverFuncs: case f := <-w.driverFuncs:
f() f(d)
default: default:
break loop break loop
} }
@@ -364,9 +370,9 @@ loop:
for { for {
select { select {
case <-w.notifyAnimate: case <-w.notifyAnimate:
w.driver.SetAnimating(w.animating) d.SetAnimating(w.animating)
case f := <-w.driverFuncs: case f := <-w.driverFuncs:
f() f(d)
case <-w.ack: case <-w.ack:
return return
} }
@@ -433,9 +439,10 @@ func (w *Window) run(opts *wm.Options) {
w.out <- system.DestroyEvent{Err: err} w.out <- system.DestroyEvent{Err: err}
return return
} }
var driver wm.Driver
for { for {
var wakeups chan struct{} var wakeups chan struct{}
if w.driver != nil { if driver != nil {
wakeups = w.wakeups wakeups = w.wakeups
} }
var timer <-chan time.Time var timer <-chan time.Time
@@ -450,7 +457,7 @@ func (w *Window) run(opts *wm.Options) {
w.setNextFrame(time.Time{}) w.setNextFrame(time.Time{})
w.updateAnimation() w.updateAnimation()
case <-wakeups: case <-wakeups:
w.driver.Wakeup() driver.Wakeup()
case e := <-w.in: case e := <-w.in:
switch e2 := e.(type) { switch e2 := e.(type) {
case system.StageEvent: case system.StageEvent:
@@ -484,7 +491,7 @@ func (w *Window) run(opts *wm.Options) {
} }
} }
frame, gotFrame := w.waitFrame() frame, gotFrame := w.waitFrame()
err := w.validateAndProcess(frameStart, e2.Size, e2.Sync, frame) err := w.validateAndProcess(driver, frameStart, e2.Size, e2.Sync, frame)
if gotFrame { if gotFrame {
// We're done with frame, let the client continue. // We're done with frame, let the client continue.
w.frameAck <- struct{}{} w.frameAck <- struct{}{}
@@ -499,7 +506,7 @@ func (w *Window) run(opts *wm.Options) {
w.out <- e w.out <- e
w.waitAck() w.waitAck()
case driverEvent: case driverEvent:
w.driver = e2.driver driver = e2.driver
case system.DestroyEvent: case system.DestroyEvent:
w.destroyGPU() w.destroyGPU()
w.out <- e2 w.out <- e2