github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/exchange/bitswap/decision/peer_request_queue.go (about)

     1  package decision
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	key "github.com/ipfs/go-ipfs/blocks/key"
     8  	wantlist "github.com/ipfs/go-ipfs/exchange/bitswap/wantlist"
     9  	peer "github.com/ipfs/go-ipfs/p2p/peer"
    10  	pq "github.com/ipfs/go-ipfs/thirdparty/pq"
    11  )
    12  
    13  type peerRequestQueue interface {
    14  	// Pop returns the next peerRequestTask. Returns nil if the peerRequestQueue is empty.
    15  	Pop() *peerRequestTask
    16  	Push(entry wantlist.Entry, to peer.ID)
    17  	Remove(k key.Key, p peer.ID)
    18  	// NB: cannot expose simply expose taskQueue.Len because trashed elements
    19  	// may exist. These trashed elements should not contribute to the count.
    20  }
    21  
    22  func newPRQ() peerRequestQueue {
    23  	return &prq{
    24  		taskMap:  make(map[string]*peerRequestTask),
    25  		partners: make(map[peer.ID]*activePartner),
    26  		pQueue:   pq.New(partnerCompare),
    27  	}
    28  }
    29  
    30  // verify interface implementation
    31  var _ peerRequestQueue = &prq{}
    32  
    33  // TODO: at some point, the strategy needs to plug in here
    34  // to help decide how to sort tasks (on add) and how to select
    35  // tasks (on getnext). For now, we are assuming a dumb/nice strategy.
    36  type prq struct {
    37  	lock     sync.Mutex
    38  	pQueue   pq.PQ
    39  	taskMap  map[string]*peerRequestTask
    40  	partners map[peer.ID]*activePartner
    41  }
    42  
    43  // Push currently adds a new peerRequestTask to the end of the list
    44  func (tl *prq) Push(entry wantlist.Entry, to peer.ID) {
    45  	tl.lock.Lock()
    46  	defer tl.lock.Unlock()
    47  	partner, ok := tl.partners[to]
    48  	if !ok {
    49  		partner = newActivePartner()
    50  		tl.pQueue.Push(partner)
    51  		tl.partners[to] = partner
    52  	}
    53  
    54  	partner.activelk.Lock()
    55  	defer partner.activelk.Unlock()
    56  	_, ok = partner.activeBlocks[entry.Key]
    57  	if ok {
    58  		return
    59  	}
    60  
    61  	if task, ok := tl.taskMap[taskKey(to, entry.Key)]; ok {
    62  		task.Entry.Priority = entry.Priority
    63  		partner.taskQueue.Update(task.index)
    64  		return
    65  	}
    66  
    67  	task := &peerRequestTask{
    68  		Entry:   entry,
    69  		Target:  to,
    70  		created: time.Now(),
    71  		Done: func() {
    72  			tl.lock.Lock()
    73  			partner.TaskDone(entry.Key)
    74  			tl.pQueue.Update(partner.Index())
    75  			tl.lock.Unlock()
    76  		},
    77  	}
    78  
    79  	partner.taskQueue.Push(task)
    80  	tl.taskMap[task.Key()] = task
    81  	partner.requests++
    82  	tl.pQueue.Update(partner.Index())
    83  }
    84  
    85  // Pop 'pops' the next task to be performed. Returns nil if no task exists.
    86  func (tl *prq) Pop() *peerRequestTask {
    87  	tl.lock.Lock()
    88  	defer tl.lock.Unlock()
    89  	if tl.pQueue.Len() == 0 {
    90  		return nil
    91  	}
    92  	partner := tl.pQueue.Pop().(*activePartner)
    93  
    94  	var out *peerRequestTask
    95  	for partner.taskQueue.Len() > 0 {
    96  		out = partner.taskQueue.Pop().(*peerRequestTask)
    97  		delete(tl.taskMap, out.Key())
    98  		if out.trash {
    99  			out = nil
   100  			continue // discarding tasks that have been removed
   101  		}
   102  
   103  		partner.StartTask(out.Entry.Key)
   104  		partner.requests--
   105  		break // and return |out|
   106  	}
   107  
   108  	tl.pQueue.Push(partner)
   109  	return out
   110  }
   111  
   112  // Remove removes a task from the queue
   113  func (tl *prq) Remove(k key.Key, p peer.ID) {
   114  	tl.lock.Lock()
   115  	t, ok := tl.taskMap[taskKey(p, k)]
   116  	if ok {
   117  		// remove the task "lazily"
   118  		// simply mark it as trash, so it'll be dropped when popped off the
   119  		// queue.
   120  		t.trash = true
   121  
   122  		// having canceled a block, we now account for that in the given partner
   123  		tl.partners[p].requests--
   124  	}
   125  	tl.lock.Unlock()
   126  }
   127  
   128  type peerRequestTask struct {
   129  	Entry  wantlist.Entry
   130  	Target peer.ID
   131  
   132  	// A callback to signal that this task has been completed
   133  	Done func()
   134  
   135  	// trash in a book-keeping field
   136  	trash bool
   137  	// created marks the time that the task was added to the queue
   138  	created time.Time
   139  	index   int // book-keeping field used by the pq container
   140  }
   141  
   142  // Key uniquely identifies a task.
   143  func (t *peerRequestTask) Key() string {
   144  	return taskKey(t.Target, t.Entry.Key)
   145  }
   146  
   147  // Index implements pq.Elem
   148  func (t *peerRequestTask) Index() int {
   149  	return t.index
   150  }
   151  
   152  // SetIndex implements pq.Elem
   153  func (t *peerRequestTask) SetIndex(i int) {
   154  	t.index = i
   155  }
   156  
   157  // taskKey returns a key that uniquely identifies a task.
   158  func taskKey(p peer.ID, k key.Key) string {
   159  	return string(p) + string(k)
   160  }
   161  
   162  // FIFO is a basic task comparator that returns tasks in the order created.
   163  var FIFO = func(a, b *peerRequestTask) bool {
   164  	return a.created.Before(b.created)
   165  }
   166  
   167  // V1 respects the target peer's wantlist priority. For tasks involving
   168  // different peers, the oldest task is prioritized.
   169  var V1 = func(a, b *peerRequestTask) bool {
   170  	if a.Target == b.Target {
   171  		return a.Entry.Priority > b.Entry.Priority
   172  	}
   173  	return FIFO(a, b)
   174  }
   175  
   176  func wrapCmp(f func(a, b *peerRequestTask) bool) func(a, b pq.Elem) bool {
   177  	return func(a, b pq.Elem) bool {
   178  		return f(a.(*peerRequestTask), b.(*peerRequestTask))
   179  	}
   180  }
   181  
   182  type activePartner struct {
   183  
   184  	// Active is the number of blocks this peer is currently being sent
   185  	// active must be locked around as it will be updated externally
   186  	activelk sync.Mutex
   187  	active   int
   188  
   189  	activeBlocks map[key.Key]struct{}
   190  
   191  	// requests is the number of blocks this peer is currently requesting
   192  	// request need not be locked around as it will only be modified under
   193  	// the peerRequestQueue's locks
   194  	requests int
   195  
   196  	// for the PQ interface
   197  	index int
   198  
   199  	// priority queue of tasks belonging to this peer
   200  	taskQueue pq.PQ
   201  }
   202  
   203  func newActivePartner() *activePartner {
   204  	return &activePartner{
   205  		taskQueue:    pq.New(wrapCmp(V1)),
   206  		activeBlocks: make(map[key.Key]struct{}),
   207  	}
   208  }
   209  
   210  // partnerCompare implements pq.ElemComparator
   211  func partnerCompare(a, b pq.Elem) bool {
   212  	pa := a.(*activePartner)
   213  	pb := b.(*activePartner)
   214  
   215  	// having no blocks in their wantlist means lowest priority
   216  	// having both of these checks ensures stability of the sort
   217  	if pa.requests == 0 {
   218  		return false
   219  	}
   220  	if pb.requests == 0 {
   221  		return true
   222  	}
   223  	if pa.active == pb.active {
   224  		// sorting by taskQueue.Len() aids in cleaning out trash entries faster
   225  		// if we sorted instead by requests, one peer could potentially build up
   226  		// a huge number of cancelled entries in the queue resulting in a memory leak
   227  		return pa.taskQueue.Len() > pb.taskQueue.Len()
   228  	}
   229  	return pa.active < pb.active
   230  }
   231  
   232  // StartTask signals that a task was started for this partner
   233  func (p *activePartner) StartTask(k key.Key) {
   234  	p.activelk.Lock()
   235  	p.activeBlocks[k] = struct{}{}
   236  	p.active++
   237  	p.activelk.Unlock()
   238  }
   239  
   240  // TaskDone signals that a task was completed for this partner
   241  func (p *activePartner) TaskDone(k key.Key) {
   242  	p.activelk.Lock()
   243  	delete(p.activeBlocks, k)
   244  	p.active--
   245  	if p.active < 0 {
   246  		panic("more tasks finished than started!")
   247  	}
   248  	p.activelk.Unlock()
   249  }
   250  
   251  // Index implements pq.Elem
   252  func (p *activePartner) Index() int {
   253  	return p.index
   254  }
   255  
   256  // SetIndex implements pq.Elem
   257  func (p *activePartner) SetIndex(i int) {
   258  	p.index = i
   259  }