{"record":{"id":"9b9fc0dbded92f3e","repo":"AlistGo/alist","slug":"errexceedmaxconcurrency","errorCode":"ErrExceedMaxConcurrency","errorMessage":"ExceedMaxConcurrency","messagePattern":"ExceedMaxConcurrency","errorType":"http","errorClass":"ErrExceedMaxConcurrency","httpStatus":429,"severity":"error","filePath":"internal/net/request.go","lineNumber":122,"sourceCode":"\tnextChunk int //next chunk id\n\tbufs      []*Buf\n\twritten   int64 //total bytes of file downloaded from remote\n\terr       error\n\n\tconcurrency int //剩余的并发数，递减。到0时停止并发\n\tmaxPart     int //有多少个分片\n\tpos         int64\n\tmaxPos      int64\n\tm2          sync.Mutex\n\treadingID   int // 正在被读取的id\n}\n\ntype ConcurrencyLimit struct {\n\t_m    sync.Mutex\n\tLimit int // 需要大于0\n}\n\nvar ErrExceedMaxConcurrency = fmt.Errorf(\"ExceedMaxConcurrency\")\n\nfunc (l *ConcurrencyLimit) sub() error {\n\tl._m.Lock()\n\tdefer l._m.Unlock()\n\tif l.Limit-1 < 0 {\n\t\treturn ErrExceedMaxConcurrency\n\t}\n\tl.Limit--\n\t// log.Debugf(\"ConcurrencyLimit.sub: %d\", l.Limit)\n\treturn nil\n}\nfunc (l *ConcurrencyLimit) add() {\n\tl._m.Lock()\n\tdefer l._m.Unlock()\n\tl.Limit++\n\t// log.Debugf(\"ConcurrencyLimit.add: %d\", l.Limit)\n}\n","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/net/request.go#L104-L140","documentation":"ErrExceedMaxConcurrency is returned by ConcurrencyLimit.sub in internal/net/request.go when the download's chunk-concurrency budget is exhausted. The limiter is a simple counter guarded by a mutex; sub() decrements and fails if the limit is already 0, capping how many chunk readers download in parallel.","triggerScenarios":"Starting more concurrent chunk downloads than the configured ConcurrencyLimit allows; sub() called from concurrent goroutines after the counter hits zero (e.g. retries allocating new slots while the budget is spent).","commonSituations":"Large multi-part download with many parts and a small concurrency limit; retry storms where failed chunks re-acquire slots while others hold them; misconfigured limit of 0 or negative making every sub() fail.","solutions":["Increase the concurrency limit in the downloader config to match the chunk count strategy.","Ensure limit is initialized > 0 before any download starts.","Make retries re-use the already-acquired slot instead of calling sub() again.","Reduce parallelism (fewer simultaneous download tasks) if the limit is intentionally low."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if limit.Limit < 1 {\n    limit.Limit = defaultConcurrency // never start with a zero/negative budget\n}","typeGuard":"errors.Is(err, ErrExceedMaxConcurrency)","tryCatchPattern":"if err := limiter.sub(); err != nil {\n    if errors.Is(err, ErrExceedMaxConcurrency) {\n        time.Sleep(backoff)\n        return limiter.sub() // or wait on a semaphore instead\n    }\n}","preventionTips":["Initialize ConcurrencyLimit > 0 before launching goroutines.","Prefer a buffered-channel semaphore over counter sub/add for slot management.","Match chunk concurrency config to the limit so sub() is never called beyond budget.","Ensure add() runs on all paths (defer) so slots are not leaked."],"tags":["concurrency","download","rate-limit","network","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}