Files
got/httpdl/segment.go
Hojun-Cho 270812de3e httpdl: drop work-stealing segment pool for one worker per segment
The pool let an idle worker steal the back half of a slower in-flight
segment, behind a mutex that also serialized every advance. At the default
-x 1 it never fired, and it carried the trickiest invariant in the tree
(minSplit >= readBuf keeps an owner's in-flight write out of the stolen tail).

makeSegments already caps the segment count at the connection count, so the
segments map one-to-one onto workers: give each its own goroutine, advance
written with a plain atomic add, and snapshot the fixed slice with no lock.
pool/newPool/acquire/steal/advance and the seg.owned field all go away.

Net -156 lines. Behaviour at the user's flags is unchanged, except a slow
mirror's tail segment is no longer rebalanced onto idle connections.
2026-06-20 23:43:15 +09:00

82 lines
2.7 KiB
Go

package httpdl
import "sync/atomic"
// seg is one contiguous byte range of the output file, downloaded by a single
// ranged GET. A segment IS a byte range — a plain HTTP downloader does not need
// the Piece/Segment/block layering that exists only to share code with
// BitTorrent. start and end are fixed when the file is divided; written is
// advanced (atomically) by the segment's one worker, so Stat() and the resume
// snapshot can read its frontier while it downloads.
type seg struct {
index int
start int64 // first byte offset, inclusive
end int64 // last byte offset, inclusive
written int64 // bytes already written into this segment (the resume point)
}
func (s *seg) length() int64 { return s.end - s.start + 1 }
func (s *seg) endOff() int64 { return s.end }
func (s *seg) done() bool { return atomic.LoadInt64(&s.written) >= s.length() }
func (s *seg) addWritten(n int64) { atomic.AddInt64(&s.written, n) }
func (s *seg) progress() int64 { return atomic.LoadInt64(&s.written) }
func (s *seg) offset() int64 { return s.start + atomic.LoadInt64(&s.written) }
func (s *seg) remaining() int64 { return s.length() - atomic.LoadInt64(&s.written) }
// makeSegments divides a file of total bytes into contiguous segments, using at
// most conns of them and never splitting below minSplit. The remainder lands in
// the last segment. With conns==1 (the default) this yields one segment. Each
// segment gets its own worker, so the division here fixes the parallelism: there
// is no later rebalancing.
func makeSegments(total, minSplit int64, conns int) []seg {
if conns < 1 {
conns = 1
}
if minSplit < 1 {
minSplit = 1
}
n := int64(conns)
if max := total / minSplit; max < n {
n = max
}
if n < 1 {
n = 1
}
chunk := total / n
segs := make([]seg, n)
var start int64
for i := int64(0); i < n; i++ {
end := start + chunk - 1
if i == n-1 {
end = total - 1
}
segs[i] = seg{index: int(i), start: start, end: end}
start = end + 1
}
return segs
}
// maxAutoConns is the ceiling --auto-split scales to. aria2 caps
// max-connection-per-server (-x) at 16, so a got-chosen count honours the same
// per-server limit rather than inventing a looser one.
const maxAutoConns = 16
// autoConns picks a connection count from the file size, used only when
// --auto-split is set: one connection per min-split-size of content, at least 1
// and at most maxAutoConns. It is the got-only stand-in for hand-tuning -x/-s,
// and because the count never exceeds total/minSplit, makeSegments yields exactly
// that many balanced segments.
func autoConns(total, minSplit int64) int {
if minSplit < 1 {
minSplit = 1
}
n := total / minSplit
if n < 1 {
n = 1
}
if n > maxAutoConns {
n = maxAutoConns
}
return int(n)
}