-
Notifications
You must be signed in to change notification settings - Fork 86
/
cancel.go
134 lines (112 loc) · 3.7 KB
/
cancel.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// This code is available on the terms of the project LICENSE.md file,
// also available online at https://blueoakcouncil.org/license/1.0.0.
package auth
import (
"bytes"
"fmt"
"sort"
"sync"
"decred.org/dcrdex/dex/order"
)
// freeCancelThreshold is the minimum number of epochs a user should wait before
// placing a cancel order, if they want to avoid penalization. It is set to 2,
// which means if a user places a cancel order in the same epoch as its limit
// order, or the next epoch, the user will be penalized. This value is chosen
// because it is the minimum value such that the order remains booked for at
// least one full epoch and one full match cycle.
const freeCancelThreshold = 2
// oidStamped is a time-stamped order ID, with a field for target order ID if
// the order is a cancel order.
type oidStamped struct {
order.OrderID
time int64
target *order.OrderID
epochGap int32
}
// ordsByTimeThenID is used to sort an ord slice in ascending order by time and
// then order ID. This puts the oldest order at the front and latest at the back
// of the slice.
type ordsByTimeThenID []*oidStamped
func (o ordsByTimeThenID) Len() int {
return len(o)
}
func (o ordsByTimeThenID) Swap(i, j int) {
o[j], o[i] = o[i], o[j]
}
func (o ordsByTimeThenID) Less(i, j int) bool {
return less(o[i], o[j])
}
func less(oi, oj *oidStamped) bool {
if oi.time == oj.time {
cmp := bytes.Compare(oi.OrderID[:], oj.OrderID[:])
// The same order should not be in the slice more than once, but nothing
// will explode so just log it.
if cmp == 0 {
log.Errorf(fmt.Sprintf("slice contains more than one instance of order %v",
oi.OrderID))
}
return cmp < 0 // ascending (smaller order ID first)
}
return oi.time < oj.time // ascending (newest last in slice)
}
// latestOrders manages a list of the latest orders for a user. Its purpose is
// to track cancellation frequency.
type latestOrders struct {
mtx sync.Mutex
cap int16
orders []*oidStamped
}
func newLatestOrders(cap int16) *latestOrders {
return &latestOrders{
cap: cap,
orders: make([]*oidStamped, 0, cap+1), // cap+1 since an old order is always popped after a new one is pushed
}
}
/*func (lo *latestOrders) addSimple(o *ord) {
lo.mtx.Lock()
defer lo.mtx.Unlock()
// push back, where the latest order goes
lo.orders = append(lo.orders, o)
// Should be few if any swaps. This is only to deal with the possibility of
// adding an order that is not the latest, and equal time stamps.
sort.Sort(ordsByTimeThenID(lo.orders))
// Pop one order if the slice was at capacity prior to pushing the new one.
for len(lo.orders) > int(lo.cap) {
// pop front, the oldest order
lo.orders[0] = nil // avoid memory leak
lo.orders = lo.orders[1:]
}
}*/
func (lo *latestOrders) add(o *oidStamped) {
lo.mtx.Lock()
defer lo.mtx.Unlock()
// Use sort.Search and insert it at the right spot.
n := len(lo.orders)
i := sort.Search(n, func(i int) bool {
return less(lo.orders[n-1-i], o)
})
if i == int(lo.cap) /* i == n && n == int(lo.cap) */ {
// The new one is the oldest/smallest, but already at capacity.
return
}
// Insert at proper location.
i = n - i // i-1 is first location that stays
lo.orders = append(lo.orders[:i], append([]*oidStamped{o}, lo.orders[i:]...)...)
// Pop one order if the slice was at capacity prior to pushing the new one.
if len(lo.orders) > int(lo.cap) {
// pop front, the oldest order
lo.orders[0] = nil // avoid memory leak
lo.orders = lo.orders[1:]
}
}
func (lo *latestOrders) counts() (total, cancels int) {
lo.mtx.Lock()
defer lo.mtx.Unlock()
total = len(lo.orders)
for _, o := range lo.orders {
if o.target != nil && o.epochGap >= 0 && o.epochGap < freeCancelThreshold {
cancels++
}
}
return
}