Skip to content

Commit

Permalink
Merge pull request #5377 from Jacalz/generic-chan-cleanup
Browse files Browse the repository at this point in the history
Make generic async.UnboundedChan work on Go 1.19
  • Loading branch information
Jacalz authored Jan 8, 2025
2 parents f2a9462 + d40c6c5 commit e6225b5
Show file tree
Hide file tree
Showing 11 changed files with 32 additions and 538 deletions.
2 changes: 1 addition & 1 deletion data/binding/queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestQueueItem(t *testing.T) {

func TestMakeInfiniteQueue(t *testing.T) {
var wg sync.WaitGroup
queue := async.NewUnboundedFuncChan()
queue := async.NewUnboundedChan[func()]()

wg.Add(1)
c := 0
Expand Down
4 changes: 2 additions & 2 deletions internal/app/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Lifecycle struct {

onStoppedHookExecuted func()

eventQueue *async.UnboundedFuncChan
eventQueue *async.UnboundedChan[func()]
}

// SetOnStoppedHookExecuted is an internal function that lets Fyne schedule a clean-up after
Expand Down Expand Up @@ -112,7 +112,7 @@ func (l *Lifecycle) DestroyEventQueue() {
// InitEventQueue initializes the event queue.
func (l *Lifecycle) InitEventQueue() {
// This channel should be closed when the window is closed.
l.eventQueue = async.NewUnboundedFuncChan()
l.eventQueue = async.NewUnboundedChan[func()]()
}

// QueueEvent uses this method to queue up a callback that handles an event. This ensures
Expand Down
41 changes: 19 additions & 22 deletions internal/async/chan_func.go → internal/async/chan.go
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
//go:build !go1.21

// Code generated by go run gen.go; DO NOT EDIT.
package async

// UnboundedFuncChan is a channel with an unbounded buffer for caching
// UnboundedChan is a channel with an unbounded buffer for caching
// Func objects. A channel must be closed via Close method.
type UnboundedFuncChan struct {
in, out chan func()
type UnboundedChan[T any] struct {
in, out chan T
close chan struct{}
q []func()
q []T
}

// NewUnboundedFuncChan returns a unbounded channel with unlimited capacity.
func NewUnboundedFuncChan() *UnboundedFuncChan {
ch := &UnboundedFuncChan{
// The size of Func is less than 16 bytes, we use 16 to fit
// NewUnboundedChan returns a unbounded channel with unlimited capacity.
func NewUnboundedChan[T any]() *UnboundedChan[T] {
ch := &UnboundedChan[T]{
// The size of Func, Interface, and CanvasObject are all less than 16 bytes, we use 16 to fit
// a CPU cache line (L2, 256 Bytes), which may reduce cache misses.
in: make(chan func(), 16),
out: make(chan func(), 16),
in: make(chan T, 16),
out: make(chan T, 16),
close: make(chan struct{}),
}
go ch.processing()
Expand All @@ -26,22 +23,22 @@ func NewUnboundedFuncChan() *UnboundedFuncChan {

// In returns the send channel of the given channel, which can be used to
// send values to the channel.
func (ch *UnboundedFuncChan) In() chan<- func() { return ch.in }
func (ch *UnboundedChan[T]) In() chan<- T { return ch.in }

// Out returns the receive channel of the given channel, which can be used
// to receive values from the channel.
func (ch *UnboundedFuncChan) Out() <-chan func() { return ch.out }
func (ch *UnboundedChan[T]) Out() <-chan T { return ch.out }

// Close closes the channel.
func (ch *UnboundedFuncChan) Close() { ch.close <- struct{}{} }
func (ch *UnboundedChan[T]) Close() { ch.close <- struct{}{} }

func (ch *UnboundedFuncChan) processing() {
func (ch *UnboundedChan[T]) processing() {
// This is a preallocation of the internal unbounded buffer.
// The size is randomly picked. But if one changes the size, the
// reallocation size at the subsequent for loop should also be
// changed too. Furthermore, there is no memory leak since the
// queue is garbage collected.
ch.q = make([]func(), 0, 1<<10)
ch.q = make([]T, 0, 1<<10)
for {
select {
case e, ok := <-ch.in:
Expand All @@ -59,7 +56,7 @@ func (ch *UnboundedFuncChan) processing() {
for len(ch.q) > 0 {
select {
case ch.out <- ch.q[0]:
ch.q[0] = nil // de-reference earlier to help GC
ch.q[0] = *new(T) // de-reference earlier to help GC (use clear() when Go 1.21 is base)
ch.q = ch.q[1:]
case e, ok := <-ch.in:
if !ok {
Expand All @@ -77,20 +74,20 @@ func (ch *UnboundedFuncChan) processing() {
// If the remaining capacity is too small, we prefer to
// reallocate the entire buffer.
if cap(ch.q) < 1<<5 {
ch.q = make([]func(), 0, 1<<10)
ch.q = make([]T, 0, 1<<10)
}
}
}

func (ch *UnboundedFuncChan) closed() {
func (ch *UnboundedChan[T]) closed() {
close(ch.in)
for e := range ch.in {
ch.q = append(ch.q, e)
}
for len(ch.q) > 0 {
select {
case ch.out <- ch.q[0]:
ch.q[0] = nil // de-reference earlier to help GC
ch.q[0] = *new(T) // de-reference earlier to help GC (use clear() when Go 1.21 is base)
ch.q = ch.q[1:]
default:
}
Expand Down
102 changes: 0 additions & 102 deletions internal/async/chan_canvasobject.go

This file was deleted.

128 changes: 0 additions & 128 deletions internal/async/chan_go1.21.go

This file was deleted.

Loading

0 comments on commit e6225b5

Please sign in to comment.