lib/ui: introduce Invalidatable
Many Drawable implementations have their own Invalidate and OnInvalidate functions, with an unexported onInvalidate field. However OnInvalidate and Invalidate are usually not called in the same goroutine. This results in a race on this field, e.g.: Read at 0x00c000094748 by goroutine 7: git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList.func1() /home/simon/src/aerc2/widgets/dirlist.go:85 +0x56 git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start.func1() /home/simon/src/aerc2/widgets/spinner.go:93 +0x1bb Previous write at 0x00c000094748 by main goroutine: [failed to restore the stack] Goroutine 7 (running) created at: git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start() /home/simon/src/aerc2/widgets/spinner.go:46 +0x8f git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList() /home/simon/src/aerc2/widgets/dirlist.go:37 +0x286 git.sr.ht/~sircmpwn/aerc2/widgets.NewAccountView() /home/simon/src/aerc2/widgets/account.go:50 +0x5ca git.sr.ht/~sircmpwn/aerc2/widgets.NewAerc() /home/simon/src/aerc2/widgets/aerc.go:60 +0x800 main.main() /home/simon/src/aerc2/aerc.go:65 +0x33e To fix this, introduce a new type, Invalidatable, which protects the field. Unfortunately the Drawable must be passed to the callback function in Invalidate, so we still need to re-implement this in each Invalidatable user.
This commit is contained in:
parent
9ef2a57b51
commit
5685a17674
12 changed files with 82 additions and 127 deletions
|
@ -12,6 +12,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bordered struct {
|
type Bordered struct {
|
||||||
|
Invalidatable
|
||||||
borders uint
|
borders uint
|
||||||
content Drawable
|
content Drawable
|
||||||
onInvalidate func(d Drawable)
|
onInvalidate func(d Drawable)
|
||||||
|
@ -35,13 +36,7 @@ func (bordered *Bordered) Children() []Drawable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bordered *Bordered) Invalidate() {
|
func (bordered *Bordered) Invalidate() {
|
||||||
if bordered.onInvalidate != nil {
|
bordered.DoInvalidate(bordered)
|
||||||
bordered.onInvalidate(bordered)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bordered *Bordered) OnInvalidate(onInvalidate func(d Drawable)) {
|
|
||||||
bordered.onInvalidate = onInvalidate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bordered *Bordered) Draw(ctx *Context) {
|
func (bordered *Bordered) Draw(ctx *Context) {
|
||||||
|
|
|
@ -6,12 +6,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Grid struct {
|
type Grid struct {
|
||||||
|
Invalidatable
|
||||||
rows []GridSpec
|
rows []GridSpec
|
||||||
rowLayout []gridLayout
|
rowLayout []gridLayout
|
||||||
columns []GridSpec
|
columns []GridSpec
|
||||||
columnLayout []gridLayout
|
columnLayout []gridLayout
|
||||||
cells []*GridCell
|
cells []*GridCell
|
||||||
onInvalidate func(d Drawable)
|
|
||||||
invalid bool
|
invalid bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,9 +141,7 @@ func (grid *Grid) reflow(ctx *Context) {
|
||||||
|
|
||||||
func (grid *Grid) invalidateLayout() {
|
func (grid *Grid) invalidateLayout() {
|
||||||
grid.invalid = true
|
grid.invalid = true
|
||||||
if grid.onInvalidate != nil {
|
grid.DoInvalidate(grid)
|
||||||
grid.onInvalidate(grid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (grid *Grid) Invalidate() {
|
func (grid *Grid) Invalidate() {
|
||||||
|
@ -153,10 +151,6 @@ func (grid *Grid) Invalidate() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (grid *Grid) OnInvalidate(onInvalidate func(d Drawable)) {
|
|
||||||
grid.onInvalidate = onInvalidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (grid *Grid) AddChild(content Drawable) *GridCell {
|
func (grid *Grid) AddChild(content Drawable) *GridCell {
|
||||||
cell := &GridCell{
|
cell := &GridCell{
|
||||||
RowSpan: 1,
|
RowSpan: 1,
|
||||||
|
@ -193,7 +187,5 @@ func (grid *Grid) cellInvalidated(drawable Drawable) {
|
||||||
panic(fmt.Errorf("Attempted to invalidate unknown cell"))
|
panic(fmt.Errorf("Attempted to invalidate unknown cell"))
|
||||||
}
|
}
|
||||||
cell.invalid = true
|
cell.invalid = true
|
||||||
if grid.onInvalidate != nil {
|
grid.DoInvalidate(grid)
|
||||||
grid.onInvalidate(grid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
24
lib/ui/invalidatable.go
Normal file
24
lib/ui/invalidatable.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Invalidatable struct {
|
||||||
|
onInvalidate atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Invalidatable) OnInvalidate(f func(d Drawable)) {
|
||||||
|
i.onInvalidate.Store(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Invalidatable) DoInvalidate(d Drawable) {
|
||||||
|
v := i.onInvalidate.Load()
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := v.(func(d Drawable))
|
||||||
|
if f != nil {
|
||||||
|
f(d)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,13 +12,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Text struct {
|
type Text struct {
|
||||||
text string
|
Invalidatable
|
||||||
strategy uint
|
text string
|
||||||
fg tcell.Color
|
strategy uint
|
||||||
bg tcell.Color
|
fg tcell.Color
|
||||||
bold bool
|
bg tcell.Color
|
||||||
reverse bool
|
bold bool
|
||||||
onInvalidate func(d Drawable)
|
reverse bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewText(text string) *Text {
|
func NewText(text string) *Text {
|
||||||
|
@ -80,12 +80,6 @@ func (t *Text) Draw(ctx *Context) {
|
||||||
ctx.Printf(x, 0, style, t.text)
|
ctx.Printf(x, 0, style, t.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Text) OnInvalidate(onInvalidate func(d Drawable)) {
|
|
||||||
t.onInvalidate = onInvalidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Text) Invalidate() {
|
func (t *Text) Invalidate() {
|
||||||
if t.onInvalidate != nil {
|
t.DoInvalidate(t)
|
||||||
t.onInvalidate(t)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,16 +14,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountView struct {
|
type AccountView struct {
|
||||||
acct *config.AccountConfig
|
acct *config.AccountConfig
|
||||||
conf *config.AercConfig
|
conf *config.AercConfig
|
||||||
dirlist *DirectoryList
|
dirlist *DirectoryList
|
||||||
grid *ui.Grid
|
grid *ui.Grid
|
||||||
host TabHost
|
host TabHost
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
onInvalidate func(d ui.Drawable)
|
msglist *MessageList
|
||||||
msglist *MessageList
|
msgStores map[string]*lib.MessageStore
|
||||||
msgStores map[string]*lib.MessageStore
|
worker *types.Worker
|
||||||
worker *types.Worker
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
|
func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
|
||||||
|
|
|
@ -12,10 +12,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DirectoryList struct {
|
type DirectoryList struct {
|
||||||
|
ui.Invalidatable
|
||||||
conf *config.AccountConfig
|
conf *config.AccountConfig
|
||||||
dirs []string
|
dirs []string
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
onInvalidate func(d ui.Drawable)
|
|
||||||
selecting string
|
selecting string
|
||||||
selected string
|
selected string
|
||||||
spinner *Spinner
|
spinner *Spinner
|
||||||
|
@ -77,14 +77,8 @@ func (dirlist *DirectoryList) Selected() string {
|
||||||
return dirlist.selected
|
return dirlist.selected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dirlist *DirectoryList) OnInvalidate(onInvalidate func(d ui.Drawable)) {
|
|
||||||
dirlist.onInvalidate = onInvalidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dirlist *DirectoryList) Invalidate() {
|
func (dirlist *DirectoryList) Invalidate() {
|
||||||
if dirlist.onInvalidate != nil {
|
dirlist.DoInvalidate(dirlist)
|
||||||
dirlist.onInvalidate(dirlist)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
|
func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
// TODO: scrolling
|
// TODO: scrolling
|
||||||
|
|
||||||
type ExLine struct {
|
type ExLine struct {
|
||||||
|
ui.Invalidatable
|
||||||
command []rune
|
command []rune
|
||||||
commit func(cmd string)
|
commit func(cmd string)
|
||||||
ctx *ui.Context
|
ctx *ui.Context
|
||||||
|
@ -33,14 +34,8 @@ func NewExLine(commit func(cmd string), cancel func()) *ExLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ex *ExLine) OnInvalidate(onInvalidate func(d ui.Drawable)) {
|
|
||||||
ex.onInvalidate = onInvalidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ex *ExLine) Invalidate() {
|
func (ex *ExLine) Invalidate() {
|
||||||
if ex.onInvalidate != nil {
|
ex.DoInvalidate(ex)
|
||||||
ex.onInvalidate(ex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ex *ExLine) Draw(ctx *ui.Context) {
|
func (ex *ExLine) Draw(ctx *ui.Context) {
|
||||||
|
|
|
@ -12,14 +12,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type MessageList struct {
|
type MessageList struct {
|
||||||
conf *config.AercConfig
|
ui.Invalidatable
|
||||||
logger *log.Logger
|
conf *config.AercConfig
|
||||||
height int
|
logger *log.Logger
|
||||||
onInvalidate func(d ui.Drawable)
|
height int
|
||||||
scroll int
|
scroll int
|
||||||
selected int
|
selected int
|
||||||
spinner *Spinner
|
spinner *Spinner
|
||||||
store *lib.MessageStore
|
store *lib.MessageStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fish in config
|
// TODO: fish in config
|
||||||
|
@ -37,14 +37,8 @@ func NewMessageList(logger *log.Logger) *MessageList {
|
||||||
return ml
|
return ml
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ml *MessageList) OnInvalidate(onInvalidate func(d ui.Drawable)) {
|
|
||||||
ml.onInvalidate = onInvalidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ml *MessageList) Invalidate() {
|
func (ml *MessageList) Invalidate() {
|
||||||
if ml.onInvalidate != nil {
|
ml.DoInvalidate(ml)
|
||||||
ml.onInvalidate(ml)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ml *MessageList) Draw(ctx *ui.Context) {
|
func (ml *MessageList) Draw(ctx *ui.Context) {
|
||||||
|
|
|
@ -252,8 +252,7 @@ func (mv *MessageViewer) Focus(focus bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeaderView struct {
|
type HeaderView struct {
|
||||||
onInvalidate func(d ui.Drawable)
|
ui.Invalidatable
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
@ -281,17 +280,11 @@ func (hv *HeaderView) Draw(ctx *ui.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hv *HeaderView) Invalidate() {
|
func (hv *HeaderView) Invalidate() {
|
||||||
if hv.onInvalidate != nil {
|
hv.DoInvalidate(hv)
|
||||||
hv.onInvalidate(hv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hv *HeaderView) OnInvalidate(fn func(d ui.Drawable)) {
|
|
||||||
hv.onInvalidate = fn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MultipartView struct {
|
type MultipartView struct {
|
||||||
onInvalidate func(d ui.Drawable)
|
ui.Invalidatable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mpv *MultipartView) Draw(ctx *ui.Context) {
|
func (mpv *MultipartView) Draw(ctx *ui.Context) {
|
||||||
|
@ -303,11 +296,5 @@ func (mpv *MultipartView) Draw(ctx *ui.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mpv *MultipartView) Invalidate() {
|
func (mpv *MultipartView) Invalidate() {
|
||||||
if mpv.onInvalidate != nil {
|
mpv.DoInvalidate(mpv)
|
||||||
mpv.onInvalidate(mpv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mpv *MultipartView) OnInvalidate(fn func(d ui.Drawable)) {
|
|
||||||
mpv.onInvalidate = fn
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Spinner struct {
|
type Spinner struct {
|
||||||
|
ui.Invalidatable
|
||||||
frame int64 // access via atomic
|
frame int64 // access via atomic
|
||||||
onInvalidate func(d ui.Drawable)
|
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,12 +84,6 @@ func (s *Spinner) Draw(ctx *ui.Context) {
|
||||||
ctx.Printf(col, 0, tcell.StyleDefault, "%s", frames[cur])
|
ctx.Printf(col, 0, tcell.StyleDefault, "%s", frames[cur])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spinner) OnInvalidate(onInvalidate func(d ui.Drawable)) {
|
|
||||||
s.onInvalidate = onInvalidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Spinner) Invalidate() {
|
func (s *Spinner) Invalidate() {
|
||||||
if s.onInvalidate != nil {
|
s.DoInvalidate(s)
|
||||||
s.onInvalidate(s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatusLine struct {
|
type StatusLine struct {
|
||||||
|
ui.Invalidatable
|
||||||
stack []*StatusMessage
|
stack []*StatusMessage
|
||||||
fallback StatusMessage
|
fallback StatusMessage
|
||||||
|
|
||||||
onInvalidate func(d ui.Drawable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusMessage struct {
|
type StatusMessage struct {
|
||||||
|
@ -31,14 +30,8 @@ func NewStatusLine() *StatusLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (status *StatusLine) OnInvalidate(onInvalidate func(d ui.Drawable)) {
|
|
||||||
status.onInvalidate = onInvalidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *StatusLine) Invalidate() {
|
func (status *StatusLine) Invalidate() {
|
||||||
if status.onInvalidate != nil {
|
status.DoInvalidate(status)
|
||||||
status.onInvalidate(status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (status *StatusLine) Draw(ctx *ui.Context) {
|
func (status *StatusLine) Draw(ctx *ui.Context) {
|
||||||
|
|
|
@ -88,20 +88,20 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
closed bool
|
ui.Invalidatable
|
||||||
cmd *exec.Cmd
|
closed bool
|
||||||
colors map[tcell.Color]tcell.Color
|
cmd *exec.Cmd
|
||||||
ctx *ui.Context
|
colors map[tcell.Color]tcell.Color
|
||||||
cursorPos vterm.Pos
|
ctx *ui.Context
|
||||||
cursorShown bool
|
cursorPos vterm.Pos
|
||||||
damage []vterm.Rect
|
cursorShown bool
|
||||||
destroyed bool
|
damage []vterm.Rect
|
||||||
err error
|
destroyed bool
|
||||||
focus bool
|
err error
|
||||||
onInvalidate func(d ui.Drawable)
|
focus bool
|
||||||
pty *os.File
|
pty *os.File
|
||||||
start chan interface{}
|
start chan interface{}
|
||||||
vterm *vterm.VTerm
|
vterm *vterm.VTerm
|
||||||
|
|
||||||
OnClose func(err error)
|
OnClose func(err error)
|
||||||
OnStart func()
|
OnStart func()
|
||||||
|
@ -225,10 +225,6 @@ func (term *Terminal) Destroy() {
|
||||||
term.destroyed = true
|
term.destroyed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (term *Terminal) OnInvalidate(cb func(d ui.Drawable)) {
|
|
||||||
term.onInvalidate = cb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (term *Terminal) Invalidate() {
|
func (term *Terminal) Invalidate() {
|
||||||
if term.vterm != nil {
|
if term.vterm != nil {
|
||||||
width, height := term.vterm.Size()
|
width, height := term.vterm.Size()
|
||||||
|
@ -239,9 +235,7 @@ func (term *Terminal) Invalidate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (term *Terminal) invalidate() {
|
func (term *Terminal) invalidate() {
|
||||||
if term.onInvalidate != nil {
|
term.DoInvalidate(term)
|
||||||
term.onInvalidate(term)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (term *Terminal) Draw(ctx *ui.Context) {
|
func (term *Terminal) Draw(ctx *ui.Context) {
|
||||||
|
|
Loading…
Reference in a new issue