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
UnboundedChan is fyne's internal unbounded queue (it backs the glfw event loop and lifecycle queues). Its contract is: shut down with Close(), never close(In()). The processing goroutine reads from ch.in in its outer select; a closed input yields ok==false, which the design treats as API misuse and converts into a deliberate panic so the bug is loud instead of silently dequeuing zero values forever.
Source
Thrown at internal/async/chan.go:52
// Close closes the channel.
func (ch *UnboundedChan[T]) Close() { ch.close <- struct{}{} }
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([]T, 0, 1<<10)
for {
select {
case e, 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.q = append(ch.q, e)
case <-ch.close:
ch.closed()
return
}
for len(ch.q) > 0 {
select {
case ch.out <- ch.q[0]:
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 {
// 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")
}View on GitHub (pinned to 8860ee95c3)
Solutions
- Replace close(ch.In()) with ch.Close()
- With multiple producers sharing In(), track them yourself and call Close() exactly once after the last finishes - Close() is the single ownership point
- Grep the codebase for close( applied to channels obtained from In()
Example fix
// before ch := async.NewUnboundedChan[int]() ... produce ... close(ch.In()) // misuse: panics in the processing goroutine // after ch := async.NewUnboundedChan[int]() ... produce ... ch.Close()
Defensive patterns
Strategy: validation
Validate before calling
// Containment: hand producers a send-only facade so they cannot close the channel.
type sender[T any] struct{ in chan<- T }
func (s sender[T]) Send(v T) { s.in <- v } // no Close exposed; owner calls ch.Close() Prevention
- Treat In() as send-only forever; the single owner calls Close() once
- Never store In() as a plain chan T where helper code might close it
- Grep for 'close(' near any channel obtained from In() during review
When it happens
Trigger: Calling close() on the channel returned by In() - or on any alias stored as a chan T - anywhere in the program. The panic fires on the processing goroutine and crashes the whole process.
Common situations: Producer code that idiomatically closes its output channel when done (correct for plain channels); refactors that handed the send side to a helper which closes it; copying bounded-pipeline patterns onto an unbounded channel.
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/1046c946ddce1b4f.
Report an issue: GitHub.