Ztorrent/internal/torrent/piecescheduler.go
itexpert228 bba337ee60 govno
2026-03-06 14:47:54 +03:00

228 lines
4.6 KiB
Go

package torrent
import (
"context"
"sync"
)
type pieceTask struct {
Index int
}
type assignPieceRequest struct {
have []bool
hasInfo bool
responseCh chan assignPieceResponse
}
type assignPieceResponse struct {
task pieceTask
ok bool
}
type reportPieceRequest struct {
pieceIndex int
success bool
responseCh chan bool
}
type pieceScheduler struct {
assignCh chan assignPieceRequest
reportCh chan reportPieceRequest
progressCh chan int
doneCh chan struct{}
stopCh chan struct{}
stopOnce sync.Once
}
type pieceState uint8
const (
piecePending pieceState = iota
pieceInProgress
pieceDone
)
func newPieceScheduler(pieceCount int) *pieceScheduler {
ps := &pieceScheduler{
assignCh: make(chan assignPieceRequest, 128),
reportCh: make(chan reportPieceRequest, 128),
progressCh: make(chan int, 128),
doneCh: make(chan struct{}),
stopCh: make(chan struct{}),
}
go ps.run(pieceCount)
return ps
}
func (ps *pieceScheduler) Acquire(ctx context.Context, have []bool, hasInfo bool) (pieceTask, bool, error) {
responseCh := make(chan assignPieceResponse, 1)
req := assignPieceRequest{
have: have,
hasInfo: hasInfo,
responseCh: responseCh,
}
select {
case <-ctx.Done():
return pieceTask{}, false, ctx.Err()
case <-ps.doneCh:
return pieceTask{}, false, nil
case ps.assignCh <- req:
}
select {
case <-ctx.Done():
return pieceTask{}, false, ctx.Err()
case <-ps.doneCh:
return pieceTask{}, false, nil
case response := <-responseCh:
return response.task, response.ok, nil
}
}
func (ps *pieceScheduler) Report(ctx context.Context, pieceIndex int, success bool) (bool, error) {
responseCh := make(chan bool, 1)
req := reportPieceRequest{
pieceIndex: pieceIndex,
success: success,
responseCh: responseCh,
}
select {
case <-ctx.Done():
return false, ctx.Err()
case <-ps.doneCh:
return false, nil
case ps.reportCh <- req:
}
select {
case <-ctx.Done():
return false, ctx.Err()
case <-ps.doneCh:
return false, nil
case accepted := <-responseCh:
return accepted, nil
}
}
func (ps *pieceScheduler) Progress() <-chan int {
return ps.progressCh
}
func (ps *pieceScheduler) Done() <-chan struct{} {
return ps.doneCh
}
func (ps *pieceScheduler) Stop() {
ps.stopOnce.Do(func() {
close(ps.stopCh)
})
}
func (ps *pieceScheduler) run(pieceCount int) {
states := make([]pieceState, pieceCount)
completed := 0
finish := func() {
close(ps.doneCh)
close(ps.progressCh)
}
if pieceCount == 0 {
finish()
return
}
for {
if completed >= pieceCount {
finish()
return
}
select {
case <-ps.stopCh:
finish()
return
case req := <-ps.assignCh:
pieceIndex := selectPendingPiece(states, req.have, req.hasInfo)
if pieceIndex >= 0 {
states[pieceIndex] = pieceInProgress
debugf("scheduler assigned piece %d", pieceIndex)
req.responseCh <- assignPieceResponse{
task: pieceTask{Index: pieceIndex},
ok: true,
}
continue
}
req.responseCh <- assignPieceResponse{ok: false}
case req := <-ps.reportCh:
if req.pieceIndex < 0 || req.pieceIndex >= len(states) {
req.responseCh <- false
continue
}
if req.success {
if states[req.pieceIndex] == pieceDone {
debugf("scheduler report piece %d ignored (already done)", req.pieceIndex)
req.responseCh <- false
continue
}
states[req.pieceIndex] = pieceDone
completed++
debugf("scheduler report piece %d success (%d/%d)", req.pieceIndex, completed, pieceCount)
select {
case ps.progressCh <- req.pieceIndex:
default:
}
req.responseCh <- true
continue
}
if states[req.pieceIndex] == pieceInProgress {
states[req.pieceIndex] = piecePending
debugf("scheduler report piece %d failed, re-queued", req.pieceIndex)
}
req.responseCh <- false
}
}
}
func selectPendingPiece(states []pieceState, have []bool, hasInfo bool) int {
firstPending := firstPendingPiece(states)
if firstPending < 0 {
return -1
}
// If peer has not sent bitfield/have yet, treat it as potentially having any piece.
if !hasInfo {
return firstPending
}
// Prefer pieces explicitly advertised by peer.
for pieceIndex, state := range states {
if state != piecePending {
continue
}
if pieceIndex < len(have) && have[pieceIndex] {
return pieceIndex
}
}
// Some peers send missing/truncated availability info; allow optimistic probing.
if len(have) == 0 || len(have) < len(states) {
return firstPending
}
return -1
}
func firstPendingPiece(states []pieceState) int {
for pieceIndex, state := range states {
if state == piecePending {
return pieceIndex
}
}
return -1
}