Skip to content

Commit

Permalink
Rework async queue to not use unsafe
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacalz committed Dec 26, 2023
1 parent f9d5427 commit 554a7b9
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 283 deletions.
181 changes: 0 additions & 181 deletions internal/async/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,40 +45,6 @@ func main() {
Imports: "",
},
},
queueImpl: {
"queue_canvasobject.go": {
Type: "fyne.CanvasObject",
Name: "CanvasObject",
Imports: `import (
"sync"
"fyne.io/fyne/v2"
)`,
},
},
queueUnsafeStructImpl: {
"queue_unsafe_canvasobject.go": {
Type: "fyne.CanvasObject",
Name: "CanvasObject",
Imports: `import (
"sync/atomic"
"unsafe"
"fyne.io/fyne/v2"
)`,
},
},
queuePureStructImpl: {
"queue_pure_canvasobject.go": {
Type: "fyne.CanvasObject",
Name: "CanvasObject",
Imports: `import (
"sync/atomic"
"fyne.io/fyne/v2"
)`,
},
},
}

for tmpl, types := range codes {
Expand Down Expand Up @@ -201,150 +167,3 @@ func (ch *Unbounded{{.Name}}Chan) closed() {
close(ch.close)
}
`))

var queuePureStructImpl = template.Must(template.New("queue").Parse(`// Code generated by go run gen.go; DO NOT EDIT.
//go:build js
// +build js
package async
{{.Imports}}
// {{.Name}}Queue implements lock-free FIFO freelist based queue.
//
// Reference: https://dl.acm.org/citation.cfm?doid=248052.248106
type {{.Name}}Queue struct {
head *item{{.Name}}
tail *item{{.Name}}
len atomic.Uint64
}
// New{{.Name}}Queue returns a queue for caching values.
func New{{.Name}}Queue() *{{.Name}}Queue {
head := &item{{.Name}}{next: nil, v: nil}
return &{{.Name}}Queue{
tail: head,
head: head,
}
}
type item{{.Name}} struct {
next *item{{.Name}}
v {{.Type}}
}
func load{{.Name}}Item(p **item{{.Name}}) *item{{.Name}} {
return *p
}
func cas{{.Name}}Item(p **item{{.Name}}, _, new *item{{.Name}}) bool {
*p = new
return true
}
`))

var queueUnsafeStructImpl = template.Must(template.New("queue").Parse(`// Code generated by go run gen.go; DO NOT EDIT.
//go:build !js
// +build !js
package async
{{.Imports}}
// {{.Name}}Queue implements lock-free FIFO freelist based queue.
//
// Reference: https://dl.acm.org/citation.cfm?doid=248052.248106
type {{.Name}}Queue struct {
head unsafe.Pointer
tail unsafe.Pointer
len atomic.Uint64
}
// New{{.Name}}Queue returns a queue for caching values.
func New{{.Name}}Queue() *{{.Name}}Queue {
head := &item{{.Name}}{next: nil, v: nil}
return &{{.Name}}Queue{
tail: unsafe.Pointer(head),
head: unsafe.Pointer(head),
}
}
type item{{.Name}} struct {
next unsafe.Pointer
v {{.Type}}
}
func load{{.Name}}Item(p *unsafe.Pointer) *item{{.Name}} {
return (*item{{.Name}})(atomic.LoadPointer(p))
}
func cas{{.Name}}Item(p *unsafe.Pointer, old, new *item{{.Name}}) bool {
return atomic.CompareAndSwapPointer(p, unsafe.Pointer(old), unsafe.Pointer(new))
}
`))

var queueImpl = template.Must(template.New("queue").Parse(`// Code generated by go run gen.go; DO NOT EDIT.
package async
{{.Imports}}
var item{{.Name}}Pool = sync.Pool{
New: func() interface{} { return &item{{.Name}}{next: nil, v: nil} },
}
// In puts the given value at the tail of the queue.
func (q *{{.Name}}Queue) In(v {{.Type}}) {
i := item{{.Name}}Pool.Get().(*item{{.Name}})
i.next = nil
i.v = v
var last, lastnext *item{{.Name}}
for {
last = load{{.Name}}Item(&q.tail)
lastnext = load{{.Name}}Item(&last.next)
if load{{.Name}}Item(&q.tail) == last {
if lastnext == nil {
if cas{{.Name}}Item(&last.next, lastnext, i) {
cas{{.Name}}Item(&q.tail, last, i)
q.len.Add(1)
return
}
} else {
cas{{.Name}}Item(&q.tail, last, lastnext)
}
}
}
}
// Out removes and returns the value at the head of the queue.
// It returns nil if the queue is empty.
func (q *{{.Name}}Queue) Out() {{.Type}} {
var first, last, firstnext *item{{.Name}}
for {
first = load{{.Name}}Item(&q.head)
last = load{{.Name}}Item(&q.tail)
firstnext = load{{.Name}}Item(&first.next)
if first == load{{.Name}}Item(&q.head) {
if first == last {
if firstnext == nil {
return nil
}
cas{{.Name}}Item(&q.tail, last, firstnext)
} else {
v := firstnext.v
if cas{{.Name}}Item(&q.head, first, firstnext) {
q.len.Add(^uint64(0))
item{{.Name}}Pool.Put(first)
return v
}
}
}
}
}
// Len returns the length of the queue.
func (q *{{.Name}}Queue) Len() uint64 {
return q.len.Load()
}
`))
55 changes: 39 additions & 16 deletions internal/async/queue_canvasobject.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,58 @@
// Code generated by go run gen.go; DO NOT EDIT.

package async

import (
"sync"
"sync/atomic"

"fyne.io/fyne/v2"
)

// CanvasObjectQueue implements lock-free FIFO freelist based queue.
//
// Reference: https://dl.acm.org/citation.cfm?doid=248052.248106
type CanvasObjectQueue struct {
head atomic.Pointer[itemCanvasObject]
tail atomic.Pointer[itemCanvasObject]
len atomic.Uint64
}

// NewCanvasObjectQueue returns a queue for caching values.
func NewCanvasObjectQueue() *CanvasObjectQueue {
head := &itemCanvasObject{}
queue := &CanvasObjectQueue{}
queue.head.Store(head)
queue.tail.Store(head)
return queue
}

type itemCanvasObject struct {
next atomic.Pointer[itemCanvasObject]
v fyne.CanvasObject
}

var itemCanvasObjectPool = sync.Pool{
New: func() interface{} { return &itemCanvasObject{next: nil, v: nil} },
New: func() interface{} { return &itemCanvasObject{} },
}

// In puts the given value at the tail of the queue.
func (q *CanvasObjectQueue) In(v fyne.CanvasObject) {
i := itemCanvasObjectPool.Get().(*itemCanvasObject)
i.next = nil
i.next.Store(nil)
i.v = v

var last, lastnext *itemCanvasObject
for {
last = loadCanvasObjectItem(&q.tail)
lastnext = loadCanvasObjectItem(&last.next)
if loadCanvasObjectItem(&q.tail) == last {
last = q.tail.Load()
lastnext = last.next.Load()
if q.tail.Load() == last {
if lastnext == nil {
if casCanvasObjectItem(&last.next, lastnext, i) {
casCanvasObjectItem(&q.tail, last, i)
if last.next.CompareAndSwap(lastnext, i) {
q.tail.CompareAndSwap(last, i)
q.len.Add(1)
return
}
} else {
casCanvasObjectItem(&q.tail, last, lastnext)
q.tail.CompareAndSwap(last, lastnext)
}
}
}
Expand All @@ -41,18 +63,19 @@ func (q *CanvasObjectQueue) In(v fyne.CanvasObject) {
func (q *CanvasObjectQueue) Out() fyne.CanvasObject {
var first, last, firstnext *itemCanvasObject
for {
first = loadCanvasObjectItem(&q.head)
last = loadCanvasObjectItem(&q.tail)
firstnext = loadCanvasObjectItem(&first.next)
if first == loadCanvasObjectItem(&q.head) {
first = q.head.Load()
last = q.tail.Load()
firstnext = first.next.Load()
if first == q.head.Load() {
if first == last {
if firstnext == nil {
return nil
}
casCanvasObjectItem(&q.tail, last, firstnext)

q.tail.CompareAndSwap(last, firstnext)
} else {
v := firstnext.v
if casCanvasObjectItem(&q.head, first, firstnext) {
if q.head.CompareAndSwap(first, firstnext) {
q.len.Add(^uint64(0))
itemCanvasObjectPool.Put(first)
return v
Expand Down
43 changes: 0 additions & 43 deletions internal/async/queue_pure_canvasobject.go

This file was deleted.

43 changes: 0 additions & 43 deletions internal/async/queue_unsafe_canvasobject.go

This file was deleted.

0 comments on commit 554a7b9

Please sign in to comment.