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

  1. Reduce concurrency of RPC calls (add a semaphore or batch via Multicall)
  2. Use the http:// RPC endpoint instead of ws:// for high-fanout workloads
  3. 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

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.