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  }