{"record":{"id":"382d8208cbb87843","repo":"AlistGo/alist","slug":"sending-channel-blocking","errorCode":null,"errorMessage":"sending channel blocking","messagePattern":"sending channel blocking","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aria2/rpc/call.go","lineNumber":251,"sourceCode":"\t\tw.cancel()\n\t\tw.wg.Wait()\n\t})\n\treturn\n}\n\nfunc (w websocketCaller) Call(method string, params, reply interface{}) (err error) {\n\tctx, cancel := context.WithTimeout(context.Background(), w.timeout)\n\tdefer cancel()\n\tselect {\n\tcase w.sendChan <- &sendRequest{cancel: cancel, request: &clientRequest{\n\t\tVersion: \"2.0\",\n\t\tMethod:  method,\n\t\tParams:  params,\n\t\tId:      reqid(),\n\t}, reply: reply}:\n\n\tdefault:\n\t\treturn errors.New(\"sending channel blocking\")\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\t\tif err := ctx.Err(); err == context.DeadlineExceeded {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn\n}\n\ntype sendRequest struct {\n\tcancel  context.CancelFunc\n\trequest *clientRequest\n\treply   interface{}\n}\n\nvar reqid = func() func() uint64 {","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/pkg/aria2/rpc/call.go#L233-L269","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nfor _, gid := range gids {\n    go c.TellStatus(gid) // can exceed sendChan capacity\n}\n// after: bound concurrency below the channel capacity\nsem := make(chan struct{}, 8)\nfor _, gid := range gids {\n    sem <- struct{}{}\n    go func(g string) { defer func() { <-sem }(); c.TellStatus(g) }(gid)\n}","handlingStrategy":"retry","validationCode":"// Bound in-flight websocket calls below sendChan capacity\nsem := make(chan struct{}, 8) // < channel capacity\n_, _ = sem, 0\n// wrap each call: sem <- struct{}{}; defer func(){ <-sem }()","typeGuard":null,"tryCatchPattern":"err := c.Call(\"aria2.tellStatus\", params, &reply)\nif err != nil && strings.Contains(err.Error(), \"sending channel blocking\") {\n    time.Sleep(backoff) // let the writer drain\n    err = c.Call(\"aria2.tellStatus\", params, &reply)\n}","preventionTips":["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"],"tags":["aria2","websocket","backpressure","concurrency"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}