fyne-io/fyne · error

async: misuse of unbounded channel, In() was closed

Error message

async: misuse of unbounded channel, In() was closed

What it means

UnboundedStructChan is the struct{} signal-channel variant of the unbounded queue (event counting). It has the same contract: terminate with Close(), never close(In()). The outer select of its processing loop treats ok==false from ch.in as misuse and panics, mirroring UnboundedChan.

Source

Thrown at internal/async/chan_struct.go:47

// to the channel.
func (ch *UnboundedStructChan) In() chan<- struct{} { return ch.in }

// Out returns a receive-only channel that can be used to receive
// values from the channel.
func (ch *UnboundedStructChan) Out() <-chan struct{} { return ch.out }

// Close closes the channel.
func (ch *UnboundedStructChan) Close() { ch.close <- struct{}{} }

func (ch *UnboundedStructChan) processing() {
	for {
		select {
		case _, ok := <-ch.in:
			if !ok {
				// We don't want the input channel be accidentally closed
				// via close() instead of Close(). If that happens, it is
				// a misuse, do a panic as warning.
				panic("async: misuse of unbounded channel, In() was closed")
			}
			ch.n++
		case <-ch.close:
			ch.closed()
			return
		}
		for ch.n > 0 {
			select {
			case ch.out <- struct{}{}:
				ch.n--
			case _, ok := <-ch.in:
				if !ok {
					// We don't want the input channel be accidentally closed
					// via close() instead of Close(). If that happens, it is
					// a misuse, do a panic as warning.
					panic("async: misuse of unbounded channel, In() was closed")
				}
				ch.n++

View on GitHub (pinned to 8860ee95c3)

Solutions

  1. Replace close(ch.In()) with ch.Close()
  2. If producers finish at different times, count them and Close() once at the end
  3. Search for close( on any channel from In() in the signal path

Example fix

// before
ch := async.NewUnboundedStructChan()
for i := 0; i < n; i++ { ch.In() <- struct{}{} }
close(ch.In())

// after
ch := async.NewUnboundedStructChan()
for i := 0; i < n; i++ { ch.In() <- struct{}{} }
ch.Close()
Defensive patterns

Strategy: validation

Validate before calling

// Containment: wrap the struct-signal channel behind methods with no close.
type signal struct{ ch *async.UnboundedStructChan }

func (s signal) Emit()      { s.ch.In() <- struct{}{} }
func (s signal) Subscribe() <-chan struct{} { return s.ch.Out() }
func (s signal) Close()     { s.ch.Close() } // sole owner closes

Prevention

When it happens

Trigger: Calling close() on the channel returned from a UnboundedStructChan's In() - the outer select observes the closure and panics on the processing goroutine, killing the process.

Common situations: Code ported from UnboundedChan patterns that closed the input; signal fan-out helpers that close their outbound channel when the source ends; tests that close producer channels in cleanup.

Related errors


AI-assisted analysis of fyne-io/fyne@8860ee95c3 (2026-08-15). Data as JSON: /api/errors/20a9a3dff15ad19e. Report an issue: GitHub.