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  }