AlistGo/alist · error · ErrExceedMaxConcurrency
ErrExceedMaxConcurrency
ErrExceedMaxConcurrency
Error message
ExceedMaxConcurrency
What it means
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.
Source
Thrown at internal/net/request.go:122
nextChunk int //next chunk id
bufs []*Buf
written int64 //total bytes of file downloaded from remote
err error
concurrency int //剩余的并发数,递减。到0时停止并发
maxPart int //有多少个分片
pos int64
maxPos int64
m2 sync.Mutex
readingID int // 正在被读取的id
}
type ConcurrencyLimit struct {
_m sync.Mutex
Limit int // 需要大于0
}
var ErrExceedMaxConcurrency = fmt.Errorf("ExceedMaxConcurrency")
func (l *ConcurrencyLimit) sub() error {
l._m.Lock()
defer l._m.Unlock()
if l.Limit-1 < 0 {
return ErrExceedMaxConcurrency
}
l.Limit--
// log.Debugf("ConcurrencyLimit.sub: %d", l.Limit)
return nil
}
func (l *ConcurrencyLimit) add() {
l._m.Lock()
defer l._m.Unlock()
l.Limit++
// log.Debugf("ConcurrencyLimit.add: %d", l.Limit)
}
View on GitHub (pinned to 843d9dc814)
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.
Defensive patterns
Strategy: retry
Validate before calling
if limit.Limit < 1 {
limit.Limit = defaultConcurrency // never start with a zero/negative budget
} Type guard
errors.Is(err, ErrExceedMaxConcurrency)
Try / catch
if err := limiter.sub(); err != nil {
if errors.Is(err, ErrExceedMaxConcurrency) {
time.Sleep(backoff)
return limiter.sub() // or wait on a semaphore instead
}
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- chunk_%d: giving up after %d retries while server is overloa
- missing cookie or qrcode account
- http request failure,status: %d
- chunk download size incorrect, expected=%d, got=%d
- failed extracting file size
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/9b9fc0dbded92f3e.
Report an issue: GitHub.