httpdl: fix progress-reporter race on name/out; harden mirror & resume validation
Findings from a Rob-Pike-lens review (bugs/races/network), each verified against the code before fixing: - httpdl: name/out are now atomic.Pointer[string] -- the engine publishes a download to the reporter before Run resolves the name, so Stat raced the write (bt already did this) - httpdl: a malformed --proxy fails loudly instead of silently bypassing it - httpdl: a 206 must carry a matching Content-Range; a 200 in segmented mode is fatal so it fails over instead of burning the retry budget - httpdl: single-stream mirror failover validates the range before appending; ErrTooSlow only when the error is a real ctx cancellation - httpdl: idle guard tracks progress by timestamp (no Reset/Stop race, no sticky fired flag) - httpdl/control: reject a resume file whose segments don't tile [0,total) - bt: clamp the listen-port range; verify on-disk data before choosing pieces under --check-integrity - cli: reject size overflow; show --seed-time=MIN; clamp --select-file range - progress/engine/main: clamp ETA against int64 overflow; show queued downloads as waiting; join the reporter on exit instead of a 20ms sleep
This commit is contained in:
@@ -153,8 +153,7 @@ func (r *Reporter) lineOne(s download.Stat) string {
|
||||
fmt.Fprintf(&b, " UL:%s", speed(ul, r.human))
|
||||
}
|
||||
if !seeding && s.Total > 0 && dl > 0 {
|
||||
eta := time.Duration(float64(s.Total-s.Completed)/float64(dl)) * time.Second
|
||||
fmt.Fprintf(&b, " ETA:%s", secfmt(eta))
|
||||
fmt.Fprintf(&b, " ETA:%s", secfmt(etaDuration(s.Total-s.Completed, dl)))
|
||||
}
|
||||
b.WriteByte(']')
|
||||
return b.String()
|
||||
@@ -215,16 +214,23 @@ func (r *Reporter) summary(stats []download.Stat) string {
|
||||
return ""
|
||||
}
|
||||
var totDL, totUL int64
|
||||
stalled := 0
|
||||
stalled, waiting := 0, 0
|
||||
for _, s := range stats {
|
||||
dl, ul := r.rates(s)
|
||||
totDL += dl
|
||||
totUL += ul
|
||||
if s.Status == download.Waiting {
|
||||
waiting++
|
||||
continue
|
||||
}
|
||||
if r.isStalled(s, dl) {
|
||||
stalled++
|
||||
}
|
||||
}
|
||||
line := fmt.Sprintf("%d active DL:%s UL:%s", len(stats), speed(totDL, r.human), speed(totUL, r.human))
|
||||
line := fmt.Sprintf("%d active DL:%s UL:%s", len(stats)-waiting, speed(totDL, r.human), speed(totUL, r.human))
|
||||
if waiting > 0 {
|
||||
line += fmt.Sprintf(" (%d waiting)", waiting)
|
||||
}
|
||||
if stalled > 0 {
|
||||
line += fmt.Sprintf(" (%d stalled)", stalled)
|
||||
}
|
||||
@@ -245,6 +251,8 @@ func (r *Reporter) tableRow(s download.Stat) string {
|
||||
// upload rate, or a "stalled" flag).
|
||||
func (r *Reporter) rowMetric(s download.Stat, dl, ul int64) (metric, tail string) {
|
||||
switch s.Status {
|
||||
case download.Waiting:
|
||||
return "queued", ""
|
||||
case download.Seeding:
|
||||
return "seeding", "UL:" + speed(ul, r.human)
|
||||
case download.Complete:
|
||||
@@ -255,8 +263,7 @@ func (r *Reporter) rowMetric(s download.Stat, dl, ul int64) (metric, tail string
|
||||
metric = "DL:" + speed(dl, r.human)
|
||||
switch {
|
||||
case dl > 0 && s.Total > 0:
|
||||
eta := time.Duration(float64(s.Total-s.Completed)/float64(dl)) * time.Second
|
||||
tail = secfmt(eta)
|
||||
tail = secfmt(etaDuration(s.Total-s.Completed, dl))
|
||||
case r.isStalled(s, dl):
|
||||
tail = "stalled"
|
||||
}
|
||||
@@ -264,6 +271,20 @@ func (r *Reporter) rowMetric(s download.Stat, dl, ul int64) (metric, tail string
|
||||
}
|
||||
}
|
||||
|
||||
// maxETASeconds caps an ETA before it becomes a Duration: a near-stalled download
|
||||
// yields an astronomically large seconds value, and time.Duration(secs) *
|
||||
// time.Second would overflow int64 and wrap to a bogus tiny ETA. secfmt renders
|
||||
// anything past 99h as "--", so clamping to 100h loses nothing.
|
||||
const maxETASeconds = 100 * 3600
|
||||
|
||||
func etaDuration(remaining, dl int64) time.Duration {
|
||||
secs := float64(remaining) / float64(dl)
|
||||
if secs > maxETASeconds {
|
||||
secs = maxETASeconds
|
||||
}
|
||||
return time.Duration(secs) * time.Second
|
||||
}
|
||||
|
||||
// isStalled reports an active download that has gone a while with no new bytes,
|
||||
// so the table can flag it rather than leave the eye to read DL:0B. A download
|
||||
// too young to have a rate yet is not stalled.
|
||||
|
||||
Reference in New Issue
Block a user