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

  1. Increase the concurrency limit in the downloader config to match the chunk count strategy.
  2. Ensure limit is initialized > 0 before any download starts.
  3. Make retries re-use the already-acquired slot instead of calling sub() again.
  4. 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

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


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/9b9fc0dbded92f3e. Report an issue: GitHub.