decred.org/dcrdex@v1.0.5/server/book/orderpq.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package book 5 6 import ( 7 "bytes" 8 "container/heap" 9 "fmt" 10 "sort" 11 "sync" 12 13 "decred.org/dcrdex/dex/order" 14 "decred.org/dcrdex/server/account" 15 ) 16 17 type orderEntry struct { 18 order *order.LimitOrder 19 heapIdx int 20 } 21 22 type orderHeap []*orderEntry 23 24 // OrderPQ is a priority queue for orders, provided as orders, based on 25 // price rate. A max-oriented queue with highest rates on top is constructed via 26 // NewMaxOrderPQ, while a min-oriented queue is constructed via NewMinOrderPQ. 27 type OrderPQ struct { 28 mtx sync.RWMutex 29 oh orderHeap 30 capacity uint32 31 lessFn func(bi, bj *order.LimitOrder) bool 32 orders map[order.OrderID]*orderEntry 33 userOrders map[account.AccountID]map[order.OrderID]*order.LimitOrder 34 } 35 36 // Copy makes a deep copy of the OrderPQ. The orders are the same; each 37 // orderEntry is new. The capacity and lessFn are the same. 38 func (pq *OrderPQ) Copy() *OrderPQ { 39 pq.mtx.Lock() 40 defer pq.mtx.Unlock() 41 return pq.copy(pq.capacity) 42 } 43 44 // realloc changes the capacity of the OrderPQ by making a deep copy with the 45 // specified capacity. The specified capacity must not be less the current queue 46 // length. Truncation is not supported; the consumer should first extract 47 // entries before attempting to reallocate to a smaller capacity. 48 func (pq *OrderPQ) realloc(newCap uint32) { 49 if len(pq.oh) > int(newCap) { 50 panic(fmt.Sprintf("(*OrderPQ).Realloc: new cap %d < current utilization %d", 51 newCap, len(pq.oh))) 52 } 53 newPQ := pq.copy(newCap) 54 pq.capacity = newCap 55 pq.orders = newPQ.orders 56 pq.oh = newPQ.oh 57 pq.userOrders = newPQ.userOrders 58 } 59 60 // Cap returns the current capacity of the OrderPQ. 61 func (pq *OrderPQ) Cap() uint32 { 62 pq.mtx.RLock() 63 defer pq.mtx.RUnlock() 64 return pq.capacity 65 } 66 67 func (pq *OrderPQ) push(oe *orderEntry) { 68 // Append the entry to the heap slice. 69 pq.oh = append(pq.oh, oe) 70 // Store it in the orders and userOrders maps. 71 lo := oe.order 72 oid := lo.ID() 73 pq.orders[oid] = oe 74 if uos, found := pq.userOrders[lo.AccountID]; found { 75 uos[oid] = lo // alloc_space hot spot 76 } else { 77 pq.userOrders[lo.AccountID] = map[order.OrderID]*order.LimitOrder{oid: lo} 78 } 79 } 80 81 // copy makes a deep copy of the OrderPQ. The orders are the same; each 82 // orderEntry is new. The lessFn is the same. This function is not thread-safe. 83 // The CALLER (e.g. realloc) must be sure the new capacity is sufficient. 84 func (pq *OrderPQ) copy(newCap uint32) *OrderPQ { 85 if len(pq.oh) > int(newCap) { 86 panic(fmt.Sprintf("len %d > newCap %d", len(pq.oh), int(newCap))) 87 } 88 // Initialize the new OrderPQ. 89 newPQ := newOrderPQ(newCap, pq.lessFn) 90 newPQ.userOrders = make(map[account.AccountID]map[order.OrderID]*order.LimitOrder, len(pq.userOrders)) 91 for aid, uos := range pq.userOrders { 92 newPQ.userOrders[aid] = make(map[order.OrderID]*order.LimitOrder, len(uos)) // actual *LimitOrders copied in push 93 } 94 95 // Deep copy the order heap, and recreate the maps. 96 for _, oe := range pq.oh { 97 entry := &orderEntry{ // alloc_objects hot spot 98 oe.order, 99 oe.heapIdx, 100 } 101 newPQ.push(entry) 102 } 103 104 // Since the heap is copied in the same order, and with the same heap 105 // indexes, it should not be necessary to reheap. But do it to be safe. 106 heap.Init(newPQ) 107 108 return newPQ 109 } 110 111 // UnfilledForUser retrieves all completely unfilled orders for a given user. 112 func (pq *OrderPQ) UnfilledForUser(user account.AccountID) []*order.LimitOrder { 113 pq.mtx.RLock() 114 var orders []*order.LimitOrder 115 for _, oe := range pq.oh { 116 if oe.order.AccountID == user && oe.order.Filled() == 0 { 117 orders = append(orders, oe.order) 118 } 119 } 120 pq.mtx.RUnlock() 121 return orders 122 } 123 124 // Orders copies all orders, sorted with the lessFn. The OrderPQ is unmodified. 125 func (pq *OrderPQ) Orders() []*order.LimitOrder { 126 // Deep copy the orders. 127 pq.mtx.RLock() 128 orders := make([]*order.LimitOrder, len(pq.oh)) 129 for i, oe := range pq.oh { 130 orders[i] = oe.order 131 } 132 pq.mtx.RUnlock() 133 134 // Sort the orders with pq.lessFn. 135 sort.Slice(orders, func(i, j int) bool { 136 return pq.lessFn(orders[i], orders[j]) 137 }) 138 139 return orders 140 } 141 142 // OrdersN copies the N best orders, sorted with the lessFn. To avoid modifying 143 // the OrderPQ or any of the data fields, a deep copy of the OrderPQ is made and 144 // ExtractBest is called until the requested number of entries are extracted. 145 func (pq *OrderPQ) OrdersN(count int) []*order.LimitOrder { 146 // Make a deep copy since extracting all the orders in sorted order (i.e. 147 // heap sort) modifies the heap, and the heapIdx of each orderEntry. 148 tmp := pq.Copy() 149 return tmp.ExtractN(count) 150 } 151 152 // ExtractN extracts the N best orders, sorted with the lessFn. ExtractBest is 153 // called until the requested number of entries are extracted. Thus, the OrderPQ 154 // is reduced in length by count, or the length of the heap, whichever is 155 // shorter. To avoid modifying the queue, use Orders or OrdersN. 156 func (pq *OrderPQ) ExtractN(count int) []*order.LimitOrder { 157 pq.mtx.Lock() 158 defer pq.mtx.Unlock() 159 sz := len(pq.oh) 160 if sz < count { 161 count = sz 162 } 163 if count < 1 { 164 return nil 165 } 166 orders := make([]*order.LimitOrder, 0, count) 167 for len(orders) < count { 168 orders = append(orders, pq.ExtractBest()) 169 } 170 return orders 171 } 172 173 // NewMinOrderPQ is the constructor for OrderPQ that initializes an empty heap 174 // with the given capacity, and sets the default LessFn for a min heap. Use 175 // OrderPQ.SetLessFn to redefine the comparator. 176 func NewMinOrderPQ(capacity uint32) *OrderPQ { 177 return newOrderPQ(capacity, LessByPriceThenTime) 178 } 179 180 // NewMaxOrderPQ is the constructor for OrderPQ that initializes an empty heap 181 // with the given capacity, and sets the default LessFn for a max heap. Use 182 // OrderPQ.SetLessFn to redefine the comparator. 183 func NewMaxOrderPQ(capacity uint32) *OrderPQ { 184 return newOrderPQ(capacity, GreaterByPriceThenTime) 185 } 186 187 func newOrderPQ(cap uint32, lessFn func(bi, bj *order.LimitOrder) bool) *OrderPQ { 188 return &OrderPQ{ 189 oh: make(orderHeap, 0, cap), 190 capacity: cap, 191 lessFn: lessFn, 192 orders: make(map[order.OrderID]*orderEntry, cap), 193 userOrders: make(map[account.AccountID]map[order.OrderID]*order.LimitOrder), 194 } 195 } 196 197 const ( 198 minCapIncrement = 4096 199 deallocThresh = 10 * minCapIncrement 200 ) 201 202 // capForUtilization suggests a capacity for a certain utilization. It is 203 // computed as sz plus the larger of sz/8 and minCapIncrement. 204 func capForUtilization(sz int) uint32 { 205 inc := sz / 8 206 if inc < minCapIncrement { 207 inc = minCapIncrement 208 } 209 return uint32(sz + inc) 210 } 211 212 // Count returns the number of orders in the queue. 213 func (pq *OrderPQ) Count() int { 214 pq.mtx.Lock() 215 defer pq.mtx.Unlock() 216 return len(pq.orders) 217 } 218 219 // Satisfy heap.Interface (Len, Less, Swap, Push, Pop). These functions are only 220 // to be used by the container/heap functions via other thread-safe OrderPQ 221 // methods. These are not safe for concurrent use. 222 223 // Len is required for heap.Interface. It is not thread-safe. 224 func (pq *OrderPQ) Len() int { 225 return len(pq.oh) 226 } 227 228 // Less performs the comparison priority(i) vs. priority(j). Use 229 // OrderPQ.SetLessFn to define the desired behavior for the orderEntry heap[i] 230 // and heap[j]. Less is required for heap.Interface. It is not thread-safe. 231 func (pq *OrderPQ) Less(i, j int) bool { 232 return pq.lessFn(pq.oh[i].order, pq.oh[j].order) 233 } 234 235 // Swap swaps the orderEntry at i and j. This is used by container/heap. Swap is 236 // required for heap.Interface. It is not thread-safe. 237 func (pq *OrderPQ) Swap(i, j int) { 238 pq.oh[i], pq.oh[j] = pq.oh[j], pq.oh[i] 239 pq.oh[i].heapIdx = i 240 pq.oh[j].heapIdx = j 241 } 242 243 // Push an order, which must be a *LimitOrder. Use heap.Push, not this directly. 244 // Push is required for heap.Interface. It is not thread-safe. 245 func (pq *OrderPQ) Push(ord any) { 246 lo, ok := ord.(*order.LimitOrder) 247 if !ok || lo == nil { 248 fmt.Printf("Failed to push an order: %v", ord) 249 return 250 } 251 252 if pq.orders[lo.ID()] != nil { 253 fmt.Printf("Attempted to push existing order: %v", ord) 254 return 255 } 256 257 entry := &orderEntry{ 258 order: lo, 259 heapIdx: len(pq.oh), 260 } 261 pq.push(entry) 262 } 263 264 // Pop will return an any that may be cast to *LimitOrder. Use heap.Pop, 265 // Extract*, or Remove*, not this method. Pop is required for heap.Interface. It 266 // is not thread-safe. 267 func (pq *OrderPQ) Pop() any { 268 // heap.Pop put the best value at the end and re-heaped without it. Now 269 // actually pop it off the heap's slice. 270 n := pq.Len() 271 oe := pq.oh[n-1] 272 oe.heapIdx = -1 273 pq.oh[n-1] = nil 274 pq.oh = pq.oh[:n-1] 275 276 // Remove the order from the orders and userOrders maps. 277 lo := oe.order 278 oid := lo.ID() 279 delete(pq.orders, oid) 280 user := lo.AccountID 281 if uos, found := pq.userOrders[user]; found { 282 if len(uos) == 1 { 283 delete(pq.userOrders, user) 284 } else { 285 delete(uos, oid) 286 } 287 } else { 288 fmt.Printf("(*OrderPQ).Pop: no userOrders for %v found when popping order %v!", user, oid) 289 } 290 291 // If the heap has shrunk well below capacity, realloc smaller. 292 if pq.capacity > deallocThresh { 293 capTarget := capForUtilization(len(pq.oh)) // new cap if we realloc for this utilization 294 if pq.capacity > capTarget { // don't increase 295 savings := pq.capacity - capTarget 296 if savings > deallocThresh && savings > pq.capacity/3 { // only reduce cap for significant savings 297 pq.realloc(capTarget) 298 } 299 } 300 } 301 302 return lo 303 } 304 305 // End heap.Interface. 306 307 // SetLessFn sets the function called by Less. The input lessFn must accept two 308 // *orderEntry and return a bool, unlike Less, which accepts heap indexes i, j. 309 // This allows to define a comparator without requiring a heap. 310 func (pq *OrderPQ) SetLessFn(lessFn func(bi, bj *order.LimitOrder) bool) { 311 pq.mtx.Lock() 312 defer pq.mtx.Unlock() 313 pq.lessFn = lessFn 314 } 315 316 // LessByPrice defines a higher priority as having a lower price rate. 317 func LessByPrice(bi, bj *order.LimitOrder) bool { 318 return bi.Rate < bj.Rate 319 } 320 321 // GreaterByPrice defines a higher priority as having a higher price rate. 322 func GreaterByPrice(bi, bj *order.LimitOrder) bool { 323 return bi.Rate > bj.Rate 324 } 325 326 // LessByPriceThenTime defines a higher priority as having a lower price rate, 327 // with older orders breaking any tie, then OrderID as a last tie breaker. 328 func LessByPriceThenTime(bi, bj *order.LimitOrder) bool { 329 if bi.Rate == bj.Rate { 330 ti, tj := bi.Time(), bj.Time() 331 if ti == tj { 332 // Lexicographical comparison of the OrderIDs requires a slice. This 333 // comparison should be exceedingly rare, so the required memory 334 // allocations are acceptable. 335 idi, idj := bi.ID(), bj.ID() 336 return bytes.Compare(idi[:], idj[:]) < 0 // idi < idj 337 } 338 return ti < tj 339 } 340 return LessByPrice(bi, bj) 341 } 342 343 // GreaterByPriceThenTime defines a higher priority as having a higher price 344 // rate, with older orders breaking any tie, then OrderID as a last tie breaker. 345 func GreaterByPriceThenTime(bi, bj *order.LimitOrder) bool { 346 if bi.Rate == bj.Rate { 347 ti, tj := bi.Time(), bj.Time() 348 if ti == tj { 349 // Lexicographical comparison of the OrderIDs requires a slice. This 350 // comparison should be exceedingly rare, so the required memory 351 // allocations are acceptable. 352 idi, idj := bi.ID(), bj.ID() 353 return bytes.Compare(idi[:], idj[:]) < 0 // idi < idj 354 } 355 return ti < tj 356 } 357 return GreaterByPrice(bi, bj) 358 } 359 360 // ExtractBest a.k.a. pop removes the highest priority order from the queue, and 361 // returns it. 362 func (pq *OrderPQ) ExtractBest() *order.LimitOrder { 363 return pq.extractBest() 364 } 365 366 // extractBest is the not thread-safe version of ExtractBest 367 func (pq *OrderPQ) extractBest() *order.LimitOrder { 368 if pq.Len() == 0 { 369 return nil 370 } 371 return heap.Pop(pq).(*order.LimitOrder) 372 } 373 374 // PeekBest returns the highest priority order without removing it from the 375 // queue. 376 func (pq *OrderPQ) PeekBest() *order.LimitOrder { 377 pq.mtx.Lock() 378 defer pq.mtx.Unlock() 379 if pq.Len() == 0 { 380 return nil 381 } 382 return pq.oh[0].order 383 } 384 385 // Reset creates a fresh queue given the input LimitOrder slice. For every 386 // element in the queue, this resets the heap index. The heap is then heapified. 387 // The input slice is not modified. 388 func (pq *OrderPQ) Reset(orders []*order.LimitOrder) { 389 pq.mtx.Lock() 390 defer pq.mtx.Unlock() 391 392 pq.oh = make([]*orderEntry, 0, len(orders)) 393 pq.orders = make(map[order.OrderID]*orderEntry, len(pq.oh)) 394 pq.userOrders = make(map[account.AccountID]map[order.OrderID]*order.LimitOrder) 395 for i, lo := range orders { 396 entry := &orderEntry{ 397 order: lo, 398 heapIdx: i, 399 } 400 pq.push(entry) 401 } 402 403 heap.Init(pq) 404 } 405 406 // Reheap is a thread-safe shortcut for heap.Init(pq). 407 func (pq *OrderPQ) Reheap() { 408 pq.mtx.Lock() 409 defer pq.mtx.Unlock() 410 heap.Init(pq) 411 } 412 413 // Insert will add an element, while respecting the queue's capacity. 414 // 415 // if (have order already), fail 416 // else (not at capacity), push the order onto the heap 417 // 418 // If the queue is at capacity, it will automatically reallocate with an 419 // increased capacity. See the Cap method. 420 func (pq *OrderPQ) Insert(ord *order.LimitOrder) bool { 421 pq.mtx.Lock() 422 defer pq.mtx.Unlock() 423 424 if ord == nil { 425 fmt.Println("(*OrderPQ).Insert: attempting to insert nil *LimitOrder!") 426 return false 427 } 428 429 // Have order 430 if _, found := pq.orders[ord.ID()]; found { 431 return false 432 } 433 434 // At capacity, reallocate larger. 435 if int(pq.capacity) <= len(pq.oh) { 436 pq.realloc(capForUtilization(len(pq.oh))) 437 } 438 439 // With room to grow, append at bottom and bubble up. Note that 440 // (*OrderPQ).Push will update the OrderPQ.orders map. 441 heap.Push(pq, ord) 442 return true 443 } 444 445 // RemoveOrder attempts to remove the provided order from the priority queue 446 // based on it's ID. 447 func (pq *OrderPQ) RemoveOrder(lo *order.LimitOrder) (*order.LimitOrder, bool) { 448 pq.mtx.Lock() 449 defer pq.mtx.Unlock() 450 return pq.removeOrder(pq.orders[lo.ID()]) 451 } 452 453 // RemoveOrderID attempts to remove the order with the given ID from the 454 // priority queue. 455 func (pq *OrderPQ) RemoveOrderID(oid order.OrderID) (*order.LimitOrder, bool) { 456 pq.mtx.Lock() 457 defer pq.mtx.Unlock() 458 return pq.removeOrder(pq.orders[oid]) 459 } 460 461 // RemoveUserOrders removes all orders from the queue that belong to a user. 462 func (pq *OrderPQ) RemoveUserOrders(user account.AccountID) (removed []*order.LimitOrder) { 463 pq.mtx.Lock() 464 defer pq.mtx.Unlock() 465 uos, found := pq.userOrders[user] 466 if !found { 467 return 468 } 469 470 removed = make([]*order.LimitOrder, 0, len(uos)) 471 for oid, lo := range uos { 472 pq.removeOrder(pq.orders[oid]) 473 removed = append(removed, lo) 474 } 475 return 476 } 477 478 // HaveOrder indicates if an order is in the queue. 479 func (pq *OrderPQ) HaveOrder(oid order.OrderID) bool { 480 return pq.Order(oid) != nil 481 } 482 483 // Order retrieves any existing order in the queue with the given ID. 484 func (pq *OrderPQ) Order(oid order.OrderID) *order.LimitOrder { 485 pq.mtx.Lock() 486 defer pq.mtx.Unlock() 487 if oe := pq.orders[oid]; oe != nil { 488 return oe.order 489 } 490 return nil 491 } 492 493 // UserOrderTotals returns the total value and number of booked orders. 494 func (pq *OrderPQ) UserOrderTotals(user account.AccountID) (amt, count uint64) { 495 pq.mtx.Lock() 496 defer pq.mtx.Unlock() 497 uos, found := pq.userOrders[user] 498 if !found { 499 return 500 } 501 for _, lo := range uos { 502 amt += lo.Remaining() 503 count++ 504 } 505 return 506 } 507 508 // removeOrder removes the specified orderEntry from the queue. This function is 509 // NOT thread-safe. 510 func (pq *OrderPQ) removeOrder(o *orderEntry) (*order.LimitOrder, bool) { 511 if o != nil && o.heapIdx >= 0 && o.heapIdx < pq.Len() { 512 // Only remove the order if it is really in the queue. 513 oid := o.order.ID() 514 removed := pq.oh[o.heapIdx].order 515 if removed.ID() == oid { 516 heap.Remove(pq, o.heapIdx) // heap.Pop => (*OrderPQ).Pop removes map entries 517 return removed, true 518 } 519 fmt.Printf("Tried to remove an order that was NOT in the PQ. ID: %s", oid) 520 } 521 return nil, false 522 } 523 524 func (pq *OrderPQ) leafNodes() []*orderEntry { 525 n := len(pq.oh) 526 if n == 0 { 527 return nil 528 } 529 numLeaves := n/2 + n%2 530 return pq.oh[n-numLeaves:] 531 } 532 533 // Worst returns the worst order (depending on the queue's lessFn) in the queue. 534 // This is done by scanning the binary heap's leaf nodes since only the best 535 // order's position (first element) is known, while the only guarantee regarding 536 // the worst element is that it will not be another node's parent (i.e. it is a 537 // leaf node). 538 func (pq *OrderPQ) Worst() *order.LimitOrder { 539 pq.mtx.RLock() 540 defer pq.mtx.RUnlock() 541 // Check the leaf nodes for the worst order according to lessFn. 542 leaves := pq.leafNodes() 543 switch len(leaves) { 544 case 0: 545 return nil 546 case 1: 547 return leaves[0].order 548 } 549 worst := leaves[0].order 550 for i := 0; i < len(leaves)-1; i++ { 551 if pq.lessFn(worst, leaves[i+1].order) { 552 worst = leaves[i+1].order 553 } 554 } 555 return worst 556 }