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 }