From 5733ebce81beaf4d636a654f50c48a9c066babc9 Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Fri, 11 Jul 2025 00:10:06 +1000 Subject: [PATCH] Fix panic writing webseed peer status --- peer-impl.go | 5 ++++- peer.go | 31 +++++++++++++++++++------------ webseed-peer.go | 17 ++++++++++++++--- webseed-requesting.go | 2 +- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/peer-impl.go b/peer-impl.go index 5586e43b..4bae3f8c 100644 --- a/peer-impl.go +++ b/peer-impl.go @@ -1,10 +1,12 @@ package torrent import ( + "io" + "github.com/RoaringBitmap/roaring" - pp "github.com/anacrolix/torrent/peer_protocol" "github.com/anacrolix/torrent/metainfo" + pp "github.com/anacrolix/torrent/peer_protocol" ) // Contains implementation details that differ between peer types, like WebSeeds and regular @@ -32,6 +34,7 @@ type legacyPeerImpl interface { String() string // Per peer-impl lines for WriteStatus. peerImplStatusLines() []string + peerImplWriteStatus(w io.Writer) // All if the peer should have everything, known if we know that for a fact. For example, we can // guess at how many pieces are in a torrent, and assume they have all pieces based on them diff --git a/peer.go b/peer.go index 9b031fb8..a9e13d1e 100644 --- a/peer.go +++ b/peer.go @@ -268,7 +268,7 @@ func (p *Peer) DownloadRate() float64 { return p.Stats().DownloadRate } -func (cn *Peer) iterContiguousPieceRequests(f func(piece pieceIndex, count int)) { +func (cn *PeerConn) iterContiguousPieceRequests(f func(piece pieceIndex, count int)) { var last Option[pieceIndex] var count int next := func(item Option[pieceIndex]) { @@ -289,12 +289,7 @@ func (cn *Peer) iterContiguousPieceRequests(f func(piece pieceIndex, count int)) next(None[pieceIndex]()) } -func (cn *Peer) writeStatus(w io.Writer) { - // \t isn't preserved in
 blocks?
-	if cn.closed.IsSet() {
-		fmt.Fprint(w, "CLOSED: ")
-	}
-	fmt.Fprintln(w, strings.Join(cn.peerImplStatusLines(), "\n"))
+func (cn *PeerConn) peerImplWriteStatus(w io.Writer) {
 	prio, err := cn.peerPriority()
 	prioStr := fmt.Sprintf("%08x", prio)
 	if err != nil {
@@ -309,18 +304,30 @@ func (cn *Peer) writeStatus(w io.Writer) {
 		cn.totalExpectingTime(),
 	)
 	fmt.Fprintf(w,
-		"%s completed, %d pieces touched, good chunks: %v/%v:%v dr: %.1f KiB/s\n",
+		"%s completed, chunks uploaded: %v\n",
 		cn.completedString(),
-		len(cn.peerTouchedPieces),
-		&cn._stats.ChunksReadUseful,
-		&cn._stats.ChunksRead,
 		&cn._stats.ChunksWritten,
-		cn.downloadRate()/(1<<10),
 	)
 	fmt.Fprintf(w, "requested pieces:")
 	cn.iterContiguousPieceRequests(func(piece pieceIndex, count int) {
 		fmt.Fprintf(w, " %v(%v)", piece, count)
 	})
+}
+
+func (cn *Peer) writeStatus(w io.Writer) {
+	// \t isn't preserved in 
 blocks?
+	if cn.closed.IsSet() {
+		fmt.Fprint(w, "CLOSED: ")
+	}
+	fmt.Fprintln(w, strings.Join(cn.peerImplStatusLines(), "\n"))
+	cn.peerImplWriteStatus(w)
+	fmt.Fprintf(w,
+		"%d pieces touched, good chunks: %v/%v, dr: %.1f KiB/s\n",
+		len(cn.peerTouchedPieces),
+		&cn._stats.ChunksReadUseful,
+		&cn._stats.ChunksRead,
+		cn.downloadRate()/(1<<10),
+	)
 	fmt.Fprintf(w, "\n")
 }
 
diff --git a/webseed-peer.go b/webseed-peer.go
index 80479e3b..13b7794b 100644
--- a/webseed-peer.go
+++ b/webseed-peer.go
@@ -7,6 +7,7 @@ import (
 	"iter"
 	"log/slog"
 	"math/rand"
+	"strings"
 	"sync"
 	"time"
 
@@ -29,6 +30,8 @@ type webseedPeer struct {
 	hostKey          webseedHostKeyHandle
 }
 
+func (me *webseedPeer) peerImplWriteStatus(w io.Writer) {}
+
 func (me *webseedPeer) isLowOnRequests() bool {
 	// Updates globally instead.
 	return false
@@ -58,10 +61,18 @@ func (me *webseedPeer) lastWriteUploadRate() float64 {
 var _ legacyPeerImpl = (*webseedPeer)(nil)
 
 func (me *webseedPeer) peerImplStatusLines() []string {
-	return []string{
+	lines := []string{
 		me.client.Url,
 		fmt.Sprintf("last unhandled error: %v", eventAgeString(me.lastUnhandledErr)),
 	}
+	if len(me.activeRequests) > 0 {
+		elems := make([]string, 0, len(me.activeRequests))
+		for wr := range me.activeRequests {
+			elems = append(elems, fmt.Sprintf("%v of [%v-%v)", wr.next, wr.begin, wr.end))
+		}
+		lines = append(lines, "active requests: "+strings.Join(elems, ", "))
+	}
+	return lines
 }
 
 func (ws *webseedPeer) String() string {
@@ -129,7 +140,7 @@ func (ws *webseedPeer) runRequest(webseedRequest *webseedRequest) {
 	locker := ws.locker
 	err := ws.readChunks(webseedRequest)
 	if webseed.PrintDebug && webseedRequest.next < webseedRequest.end {
-		fmt.Printf("webseed peer stopped reading chunks early\n")
+		fmt.Printf("webseed peer stopped reading chunks early: %v\n", err)
 	}
 	// Ensure the body reader and response are closed.
 	webseedRequest.Close()
@@ -142,7 +153,7 @@ func (ws *webseedPeer) runRequest(webseedRequest *webseedRequest) {
 		torrent.Add("webseed request error count", 1)
 		// This used to occur only on webseed.ErrTooFast but I think it makes sense to slow down any
 		// kind of error. Pausing here will starve the available requester slots which slows things
-		// down.
+		// down. TODO: Use the Retry-After implementation from Erigon.
 		select {
 		case <-ws.peer.closed.Done():
 		case <-time.After(time.Duration(rand.Int63n(int64(10 * time.Second)))):
diff --git a/webseed-requesting.go b/webseed-requesting.go
index 0fb3d579..7ea252ba 100644
--- a/webseed-requesting.go
+++ b/webseed-requesting.go
@@ -12,10 +12,10 @@ import (
 	g "github.com/anacrolix/generics"
 	"github.com/anacrolix/generics/heap"
 	"github.com/anacrolix/missinggo/v2/panicif"
-	"github.com/anacrolix/torrent/webseed"
 
 	"github.com/anacrolix/torrent/internal/request-strategy"
 	"github.com/anacrolix/torrent/metainfo"
+	"github.com/anacrolix/torrent/webseed"
 )
 
 const defaultRequestsPerWebseedHost = 5
-- 
2.51.0