github.com/zhiqiangxu/util@v0.0.0-20230112053021-0a7aee056cd5/xorlayer/xtm.go (about) 1 package xorlayer 2 3 import ( 4 "context" 5 "math/bits" 6 "sync" 7 "time" 8 "unsafe" 9 10 "github.com/zhiqiangxu/util/sort" 11 ) 12 13 // NodeID can be replace by https://github.com/zhiqiangxu/gg 14 type NodeID uint64 15 16 const ( 17 bitSize = int(unsafe.Sizeof(NodeID(0)) * 8) 18 ) 19 20 // Callback is used by XTM 21 type Callback interface { 22 Ping(ctx context.Context, nodeID NodeID) error 23 } 24 25 // XTM manages xor topology 26 type XTM struct { 27 sync.RWMutex 28 k int 29 h int 30 theta int // for delimiting close region 31 id NodeID 32 buckets []*bucket 33 cb Callback 34 } 35 36 // NewXTM is ctor for XTM 37 // caller is responsible for dealing with duplicate NodeID 38 func NewXTM(k, h int, id NodeID, cb Callback) *XTM { 39 buckets := make([]*bucket, bitSize) 40 for i := range buckets { 41 buckets[i] = newBucket() 42 } 43 x := &XTM{k: k, h: h, theta: -1, id: id, buckets: buckets, cb: cb} 44 return x 45 } 46 47 // AddNeighbours is batch for AddNeighbour 48 func (x *XTM) AddNeighbours(ns []NodeID, cookies []uint64) { 49 x.Lock() 50 51 var unlocked bool 52 53 for i, n := range ns { 54 unlocked = x.addNeighbourLocked(n, cookies[i]) 55 if unlocked { 56 unlocked = false 57 x.Lock() 58 } 59 } 60 61 if !unlocked { 62 x.Unlock() 63 } 64 } 65 66 // AddNeighbour tries to add NodeID with cookie to kbucket 67 func (x *XTM) AddNeighbour(n NodeID, cookie uint64) { 68 x.Lock() 69 70 if !x.addNeighbourLocked(n, cookie) { 71 x.Unlock() 72 } 73 } 74 75 const ( 76 pingTimeout = time.Second * 2 77 ) 78 79 func (x *XTM) addNeighbourLocked(n NodeID, cookie uint64) (unlocked bool) { 80 i := x.getBucketIdx(n) 81 82 if i >= bitSize { 83 return 84 } 85 86 bucket := x.buckets[i] 87 exists := bucket.refresh(n) 88 if exists { 89 return 90 } 91 92 if i <= x.theta { 93 // at most |x.k + x.h| 94 if bucket.size() < (x.k + x.h) { 95 bucket.insert(n, cookie) 96 } else { 97 if x.cb != nil { 98 oldest := bucket.oldest() 99 x.Unlock() 100 ctx, cancelFunc := context.WithTimeout(context.Background(), pingTimeout) 101 defer cancelFunc() 102 103 err := x.cb.Ping(ctx, oldest.N) 104 if err != nil { 105 x.Lock() 106 if bucket.remove(oldest.N, oldest.Cookie) { 107 bucket.insert(n, cookie) 108 } 109 } else { 110 unlocked = true 111 } 112 } 113 } 114 } else { 115 // no limit 116 bucket.insert(n, cookie) 117 118 // increment theta if necessary 119 120 for { 121 if x.buckets[x.theta+1].size() >= (x.k + x.h) { 122 total := 0 123 for j := x.theta + 2; j < bitSize; j++ { 124 total += x.buckets[j].size() 125 } 126 if total >= (x.k + x.h) { 127 x.theta++ 128 x.buckets[x.theta].reduceTo(x.k + x.h) 129 } else { 130 break 131 } 132 } else { 133 break 134 } 135 } 136 137 } 138 139 return 140 } 141 142 func (x *XTM) delNeighbourLocked(n NodeID, cookie uint64) { 143 i := x.getBucketIdx(n) 144 if i >= bitSize { 145 return 146 } 147 148 x.buckets[i].remove(n, cookie) 149 150 if i <= x.theta { 151 // decrement theta if necessary 152 if x.buckets[i].size() < x.k { 153 x.theta = i - 1 154 } 155 } 156 157 } 158 159 // zero based index 160 // 0 for all NodeID that's different from id from the first bit, all with the same prefix 1 bit (2^^63) 161 // 1 for all NodeID that's different from id from the second bit, all with the same prefix 2 bit (2^^62) 162 // ... 163 // 63 for all NodeID that's different from id from the 64th bit, all with the same prefix 64 bit(2^^0) 164 // 64 means n == id 165 func (x *XTM) getBucketIdx(n NodeID) int { 166 xor := uint64(n ^ x.id) 167 lz := bits.LeadingZeros64(xor) 168 169 return lz 170 } 171 172 // DelNeighbours is batch for DelNeighbour 173 func (x *XTM) DelNeighbours(ns []NodeID, cookies []uint64) { 174 x.Lock() 175 defer x.Unlock() 176 177 for i, n := range ns { 178 x.delNeighbourLocked(n, cookies[i]) 179 } 180 } 181 182 // DelNeighbour by NodeID and cookie 183 func (x *XTM) DelNeighbour(n NodeID, cookie uint64) { 184 x.Lock() 185 defer x.Unlock() 186 187 x.delNeighbourLocked(n, cookie) 188 } 189 190 // NeighbourCount returns total neighbour count 191 func (x *XTM) NeighbourCount() (total int) { 192 x.RLock() 193 defer x.RUnlock() 194 195 for _, bucket := range x.buckets { 196 total += bucket.size() 197 } 198 return total 199 } 200 201 // KClosest returns k-closest nodes to target 202 func (x *XTM) KClosest(target NodeID) (ns []NodeID) { 203 x.RLock() 204 defer x.RUnlock() 205 206 ns = make([]NodeID, 0, x.k) 207 208 i := x.getBucketIdx(target) 209 if i >= bitSize { 210 ns = append(ns, x.id) 211 remain := x.k - 1 212 for j := bitSize - 1; j >= 0; j-- { 213 bucket := x.buckets[j] 214 ns = bucket.appendXClosest(ns, remain, target) 215 remain = x.k - len(ns) 216 if remain == 0 { 217 return 218 } 219 } 220 return 221 } 222 223 ns = x.buckets[i].appendXClosest(ns, x.k, target) 224 remain := x.k - len(ns) 225 if remain == 0 { 226 return 227 } 228 229 // search i+1, i+2, ... etc 230 var right []NodeID 231 for j := i + 1; j < bitSize; j++ { 232 right = x.buckets[i].appendAll(right) 233 } 234 right = append(right, x.id) 235 236 kclosest := sort.KSmallest(right, remain, func(j, k int) int { 237 dj := right[j] ^ target 238 dk := right[k] ^ target 239 switch { 240 case dj < dk: 241 return -1 242 case dj > dk: 243 return 1 244 } 245 246 return 0 247 }).([]NodeID) 248 for _, n := range kclosest { 249 ns = append(ns, n) 250 } 251 252 remain = x.k - len(ns) 253 if remain == 0 { 254 return 255 } 256 257 // search i-1, i-2, ... etc 258 for j := i - 1; j >= 0; j-- { 259 bucket := x.buckets[j] 260 ns = bucket.appendXClosest(ns, remain, target) 261 remain = x.k - len(ns) 262 if remain == 0 { 263 return 264 } 265 } 266 267 return 268 }