func (e *Engine) nextEnvelope(ctx context.Context) (*Envelope, error) {
	for {
		p, nextTasks, pendingBytes := e.peerRequestQueue.PopTasks(targetMessageSize)
		e.updateMetrics()
		for len(nextTasks) == 0 {
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			case <-e.workSignal:
				p, nextTasks, pendingBytes = e.peerRequestQueue.PopTasks(targetMessageSize)
				e.updateMetrics()
			case <-e.ticker.C:
				e.peerRequestQueue.ThawRound()
				p, nextTasks, pendingBytes = e.peerRequestQueue.PopTasks(targetMessageSize)
				e.updateMetrics()
			}
		}
		msg := bsmsg.New(false)
		log.Debugw("Bitswap process tasks", "local", e.self, "taskCount", len(nextTasks))
		msg.SetPendingBytes(int32(pendingBytes))
		blockCids := make([]cid.Cid, 0, len(nextTasks))
		blockTasks := make(map[cid.Cid]*taskData, len(nextTasks))
		for _, t := range nextTasks {
			c := t.Topic.(cid.Cid)
			td := t.Data.(*taskData)
			if td.HaveBlock {
				if td.IsWantBlock {
					blockCids = append(blockCids, c)
					blockTasks[c] = td
				} else {
					msg.AddHave(c)
				}
			} else {
				msg.AddDontHave(c)
			}
		}
		blks, err := e.bsm.getBlocks(ctx, blockCids)
		if err != nil {
			return nil, err
		}
		for c, t := range blockTasks {
			blk := blks[c]
			if blk == nil {
				if t.SendDontHave {
					msg.AddDontHave(c)
				}
			} else {
				msg.AddBlock(blk)
			}
		}
		if msg.Empty() {
			e.peerRequestQueue.TasksDone(p, nextTasks...)
			continue
		}
		log.Debugw("Bitswap engine -> msg", "local", e.self, "to", p, "blockCount", len(msg.Blocks()), "presenceCount", len(msg.BlockPresences()), "size", msg.Size())
		return &Envelope{
			Peer:    p,
			Message: msg,
			Sent: func() {
				e.peerRequestQueue.TasksDone(p, nextTasks...)
				e.signalNewWork()
			},
		}, nil
	}
}
nextEnvelope runs in the taskWorker goroutine. Returns an error if the
context is cancelled before the next Envelope can be created.
package decision
import (
	"context"
	"fmt"
	"sync"
	"time"
	"github.com/google/uuid"
	bsmsg "github.com/ipfs/go-bitswap/message"
	pb "github.com/ipfs/go-bitswap/message/pb"
	wl "github.com/ipfs/go-bitswap/wantlist"
	blocks "github.com/ipfs/go-block-format"
	"github.com/ipfs/go-cid"
	bstore "github.com/ipfs/go-ipfs-blockstore"
	logging "github.com/ipfs/go-log"
	"github.com/ipfs/go-metrics-interface"
	"github.com/ipfs/go-peertaskqueue"
	"github.com/ipfs/go-peertaskqueue/peertask"
	process "github.com/jbenet/goprocess"
	"github.com/libp2p/go-libp2p-core/peer"
)
var log = logging.Logger("engine")
const (
	outboxChanBuffer = 0
	targetMessageSize = 16 * 1024
	tagFormat = "bs-engine-%s-%s"
	queuedTagWeight = 10
	maxBlockSizeReplaceHasWithBlock = 1024
)
type Envelope struct {
	Peer peer.ID
	Message bsmsg.BitSwapMessage
	Sent func()
}
type PeerTagger interface {
	TagPeer(peer.ID, string, int)
	UntagPeer(p peer.ID, tag string)
}
type ScorePeerFunc func(peer.ID, int)
type ScoreLedger interface {
	GetReceipt(p peer.ID) *Receipt
	AddToSentBytes(p peer.ID, n int)
	AddToReceivedBytes(p peer.ID, n int)
	PeerConnected(p peer.ID)
	PeerDisconnected(p peer.ID)
	Start(scorePeer ScorePeerFunc)
	Stop()
}
type Engine struct {
	peerRequestQueue *peertaskqueue.PeerTaskQueue
	workSignal chan struct{}
	outbox chan (<-chan *Envelope)
	bsm *blockstoreManager
	peerTagger PeerTagger
	tagQueued, tagUseful string
	lock sync.RWMutex  
	ledgerMap map[peer.ID]*ledger
	peerLedger *peerLedger
	scoreLedger ScoreLedger
	ticker *time.Ticker
	taskWorkerLock  sync.Mutex
	taskWorkerCount int
	maxBlockSizeReplaceHasWithBlock int
	sendDontHaves bool
	self peer.ID
	pendingGauge metrics.Gauge
	activeGauge metrics.Gauge
	metricsLock         sync.Mutex
	metricUpdateCounter int
}
func NewEngine(
	ctx context.Context,
	bs bstore.Blockstore,
	bstoreWorkerCount,
	engineTaskWorkerCount, maxOutstandingBytesPerPeer int,
	peerTagger PeerTagger,
	self peer.ID,
	scoreLedger ScoreLedger,
	pendingEngineGauge metrics.Gauge,
	activeEngineGauge metrics.Gauge,
	pendingBlocksGauge metrics.Gauge,
	activeBlocksGauge metrics.Gauge,
) *Engine {
	return newEngine(
		ctx,
		bs,
		bstoreWorkerCount,
		engineTaskWorkerCount,
		maxOutstandingBytesPerPeer,
		peerTagger,
		self,
		maxBlockSizeReplaceHasWithBlock,
		scoreLedger,
		pendingEngineGauge,
		activeEngineGauge,
		pendingBlocksGauge,
		activeBlocksGauge,
	)
}
func newEngine(
	ctx context.Context,
	bs bstore.Blockstore,
	bstoreWorkerCount,
	engineTaskWorkerCount, maxOutstandingBytesPerPeer int,
	peerTagger PeerTagger,
	self peer.ID,
	maxReplaceSize int,
	scoreLedger ScoreLedger,
	pendingEngineGauge metrics.Gauge,
	activeEngineGauge metrics.Gauge,
	pendingBlocksGauge metrics.Gauge,
	activeBlocksGauge metrics.Gauge,
) *Engine {
	if scoreLedger == nil {
		scoreLedger = NewDefaultScoreLedger()
	}
	e := &Engine{
		ledgerMap:                       make(map[peer.ID]*ledger),
		scoreLedger:                     scoreLedger,
		bsm:                             newBlockstoreManager(ctx, bs, bstoreWorkerCount, pendingBlocksGauge, activeBlocksGauge),
		peerTagger:                      peerTagger,
		outbox:                          make(chan (<-chan *Envelope), outboxChanBuffer),
		workSignal:                      make(chan struct{}, 1),
		ticker:                          time.NewTicker(time.Millisecond * 100),
		maxBlockSizeReplaceHasWithBlock: maxReplaceSize,
		taskWorkerCount:                 engineTaskWorkerCount,
		sendDontHaves:                   true,
		self:                            self,
		peerLedger:                      newPeerLedger(),
		pendingGauge:                    pendingEngineGauge,
		activeGauge:                     activeEngineGauge,
	}
	e.tagQueued = fmt.Sprintf(tagFormat, "queued", uuid.New().String())
	e.tagUseful = fmt.Sprintf(tagFormat, "useful", uuid.New().String())
	e.peerRequestQueue = peertaskqueue.New(
		peertaskqueue.OnPeerAddedHook(e.onPeerAdded),
		peertaskqueue.OnPeerRemovedHook(e.onPeerRemoved),
		peertaskqueue.TaskMerger(newTaskMerger()),
		peertaskqueue.IgnoreFreezing(true),
		peertaskqueue.MaxOutstandingWorkPerPeer(maxOutstandingBytesPerPeer))
	return e
}
func (e *Engine) updateMetrics() {
	e.metricsLock.Lock()
	c := e.metricUpdateCounter
	e.metricUpdateCounter++
	e.metricsLock.Unlock()
	if c%100 == 0 {
		stats := e.peerRequestQueue.Stats()
		e.activeGauge.Set(float64(stats.NumActive))
		e.pendingGauge.Set(float64(stats.NumPending))
	}
}
func (e *Engine) SetSendDontHaves(send bool) {
	e.sendDontHaves = send
}
func (e *Engine) startScoreLedger(px process.Process) {
	e.scoreLedger.Start(func(p peer.ID, score int) {
		if score == 0 {
			e.peerTagger.UntagPeer(p, e.tagUseful)
		} else {
			e.peerTagger.TagPeer(p, e.tagUseful, score)
		}
	})
	px.Go(func(ppx process.Process) {
		<-ppx.Closing()
		e.scoreLedger.Stop()
	})
}
func (e *Engine) StartWorkers(ctx context.Context, px process.Process) {
	e.bsm.start(px)
	e.startScoreLedger(px)
	e.taskWorkerLock.Lock()
	defer e.taskWorkerLock.Unlock()
	for i := 0; i < e.taskWorkerCount; i++ {
		px.Go(func(px process.Process) {
			e.taskWorker(ctx)
		})
	}
}
func (e *Engine) onPeerAdded(p peer.ID) {
	e.peerTagger.TagPeer(p, e.tagQueued, queuedTagWeight)
}
func (e *Engine) onPeerRemoved(p peer.ID) {
	e.peerTagger.UntagPeer(p, e.tagQueued)
}
func (e *Engine) WantlistForPeer(p peer.ID) []wl.Entry {
	partner := e.findOrCreate(p)
	partner.lk.Lock()
	entries := partner.wantList.Entries()
	partner.lk.Unlock()
	return entries
}
func (e *Engine) LedgerForPeer(p peer.ID) *Receipt {
	return e.scoreLedger.GetReceipt(p)
}
func (e *Engine) taskWorker(ctx context.Context) {
	defer e.taskWorkerExit()
	for {
		oneTimeUse := make(chan *Envelope, 1)  
		select {
		case <-ctx.Done():
			return
		case e.outbox <- oneTimeUse:
		}
		envelope, err := e.nextEnvelope(ctx)
		if err != nil {
			close(oneTimeUse)
			return  
		}
		oneTimeUse <- envelope  
		close(oneTimeUse)
	}
}
func (e *Engine) taskWorkerExit() {
	e.taskWorkerLock.Lock()
	defer e.taskWorkerLock.Unlock()
	e.taskWorkerCount--
	if e.taskWorkerCount == 0 {
		close(e.outbox)
	}
}