AlistGo/alist · error
sending channel blocking
Error message
sending channel blocking
What it means
The websocket caller multiplexes RPC requests over one connection through a bounded sendChan; the Call does a non-blocking send with a default branch, so if the channel is full it immediately returns 'sending channel blocking' instead of waiting. The channel is drained by the single websocket write loop; backpressure there (slow aria2 daemon, blocked TCP connection) fills the queue.
Source
Thrown at pkg/aria2/rpc/call.go:251
w.cancel()
w.wg.Wait()
})
return
}
func (w websocketCaller) Call(method string, params, reply interface{}) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), w.timeout)
defer cancel()
select {
case w.sendChan <- &sendRequest{cancel: cancel, request: &clientRequest{
Version: "2.0",
Method: method,
Params: params,
Id: reqid(),
}, reply: reply}:
default:
return errors.New("sending channel blocking")
}
select {
case <-ctx.Done():
if err := ctx.Err(); err == context.DeadlineExceeded {
return err
}
}
return
}
type sendRequest struct {
cancel context.CancelFunc
request *clientRequest
reply interface{}
}
var reqid = func() func() uint64 {View on GitHub (pinned to 843d9dc814)
Solutions
- Reduce concurrency of RPC calls (add a semaphore or batch via Multicall)
- Use the http:// RPC endpoint instead of ws:// for high-fanout workloads
- Restart the aria2 connection (recreate the client) if the websocket is wedged; check network stability to the aria2 host
Example fix
// before
for _, gid := range gids {
go c.TellStatus(gid) // can exceed sendChan capacity
}
// after: bound concurrency below the channel capacity
sem := make(chan struct{}, 8)
for _, gid := range gids {
sem <- struct{}{}
go func(g string) { defer func() { <-sem }(); c.TellStatus(g) }(gid)
} Defensive patterns
Strategy: retry
Validate before calling
// Bound in-flight websocket calls below sendChan capacity
sem := make(chan struct{}, 8) // < channel capacity
_, _ = sem, 0
// wrap each call: sem <- struct{}{}; defer func(){ <-sem }() Try / catch
err := c.Call("aria2.tellStatus", params, &reply)
if err != nil && strings.Contains(err.Error(), "sending channel blocking") {
time.Sleep(backoff) // let the writer drain
err = c.Call("aria2.tellStatus", params, &reply)
} Prevention
- Prefer http:// RPC for high-concurrency pollers; reserve ws for notifications
- Batch status queries with system.multicall instead of N parallel calls
- Add a semaphore limiting concurrency below the client's channel capacity
- Recreate the client on repeated channel-blocking errors — the socket may be wedged
When it happens
Trigger: Issuing more concurrent websocket RPC calls than the sendChan capacity while the writer goroutine is stalled or slower than the callers — e.g. parallel task polling bursts, aria2 busy, or the ws connection is half-dead after a network change.
Common situations: Bursty status polling over ws:// with many tasks; NAT/firewall silently dropping the websocket so writes block; aria2 under heavy load responding slowly. The caller's context timeout never fires because the error is returned before the request is queued.
Related errors
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/382d8208cbb87843.
Report an issue: GitHub.