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
- Replace close(ch.In()) with ch.Close()
- If producers finish at different times, count them and Close() once at the end
- 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
- Never close a signal channel returned from In(); emit-and-stop is the correct pattern
- Keep exactly one owner responsible for Close() (usually the driver/event-loop owner)
- Review test cleanup code that closes producer channels by habit
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
- async: misuse of unbounded channel, In() was closed
- Run() or ShowAndRun() must be called from main goroutine
- unhandled token type: %T %+v
- encountered empty values slice
- TODO only know how to handle DataIntDec type here
AI-assisted analysis of fyne-io/fyne@8860ee95c3 (2026-08-15).
Data as JSON: /api/errors/20a9a3dff15ad19e.
Report an issue: GitHub.