github.com/phuslu/lru@v1.0.16-0.20240421170520-46288a2fd47c/ttl_shard.go (about)

     1  // Copyright 2023-2024 Phus Lu. All rights reserved.
     2  
     3  package lru
     4  
     5  import (
     6  	"sync"
     7  	"sync/atomic"
     8  	"time"
     9  	"unsafe"
    10  )
    11  
    12  // ttlnode is a list of ttl node, storing key-value pairs and related information
    13  type ttlnode[K comparable, V any] struct {
    14  	key     K
    15  	expires uint32
    16  	next    uint32
    17  	prev    uint32
    18  	ttl     uint32
    19  	value   V
    20  }
    21  
    22  type ttlbucket struct {
    23  	hdib  uint32 // bitfield { hash:24 dib:8 }
    24  	index uint32 // node index
    25  }
    26  
    27  // ttlshard is a LRU partition contains a list and a hash table.
    28  type ttlshard[K comparable, V any] struct {
    29  	mu sync.Mutex
    30  
    31  	// the hash table, with 20% extra space than the list for fewer conflicts.
    32  	table_buckets []uint64 // []ttlbucket
    33  	table_mask    uint32
    34  	table_length  uint32
    35  	table_hasher  func(key unsafe.Pointer, seed uintptr) uintptr
    36  	table_seed    uintptr
    37  
    38  	// the list of nodes
    39  	list []ttlnode[K, V]
    40  
    41  	sliding bool
    42  
    43  	// stats
    44  	stats_getcalls uint64
    45  	stats_setcalls uint64
    46  	stats_misses   uint64
    47  
    48  	// padding
    49  	_ [16]byte
    50  }
    51  
    52  func (s *ttlshard[K, V]) Init(size uint32, hasher func(key unsafe.Pointer, seed uintptr) uintptr, seed uintptr) {
    53  	s.list_Init(size)
    54  	s.table_Init(size, hasher, seed)
    55  }
    56  
    57  func (s *ttlshard[K, V]) Get(hash uint32, key K) (value V, ok bool) {
    58  	s.mu.Lock()
    59  
    60  	s.stats_getcalls++
    61  
    62  	if index, exists := s.table_Get(hash, key); exists {
    63  		if expires := s.list[index].expires; expires == 0 {
    64  			s.list_MoveToFront(index)
    65  			// value = s.list[index].value
    66  			value = (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))).value
    67  			ok = true
    68  		} else if now := atomic.LoadUint32(&clock); now < expires {
    69  			if s.sliding {
    70  				s.list[index].expires = now + s.list[index].ttl
    71  			}
    72  			s.list_MoveToFront(index)
    73  			// value = s.list[index].value
    74  			value = (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))).value
    75  			ok = true
    76  		} else {
    77  			s.list_MoveToBack(index)
    78  			// s.list[index].value = value
    79  			(*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))).value = value
    80  			s.table_Delete(hash, key)
    81  			s.stats_misses++
    82  		}
    83  	} else {
    84  		s.stats_misses++
    85  	}
    86  
    87  	s.mu.Unlock()
    88  
    89  	return
    90  }
    91  
    92  func (s *ttlshard[K, V]) Peek(hash uint32, key K) (value V, expires int64, ok bool) {
    93  	s.mu.Lock()
    94  
    95  	if index, exists := s.table_Get(hash, key); exists {
    96  		value = s.list[index].value
    97  		if e := s.list[index].expires; e > 0 {
    98  			expires = (int64(e) + clockBase) * int64(time.Second)
    99  		}
   100  		ok = true
   101  	}
   102  
   103  	s.mu.Unlock()
   104  
   105  	return
   106  }
   107  
   108  func (s *ttlshard[K, V]) SetIfAbsent(hash uint32, key K, value V, ttl time.Duration) (prev V, replaced bool) {
   109  	s.mu.Lock()
   110  
   111  	if index, exists := s.table_Get(hash, key); exists {
   112  		// node := &s.list[index]
   113  		node := (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0])))
   114  		prev = node.value
   115  		if node.expires == 0 || atomic.LoadUint32(&clock) < node.expires {
   116  			s.mu.Unlock()
   117  			return
   118  		}
   119  
   120  		s.stats_setcalls++
   121  
   122  		node.value = value
   123  		if ttl > 0 {
   124  			node.ttl = uint32(ttl / time.Second)
   125  			node.expires = atomic.LoadUint32(&clock) + node.ttl
   126  		} else {
   127  			node.ttl = 0
   128  			node.expires = 0
   129  		}
   130  		replaced = true
   131  
   132  		s.mu.Unlock()
   133  		return
   134  	}
   135  
   136  	s.stats_setcalls++
   137  
   138  	// index := s.list_Back()
   139  	// node := &s.list[index]
   140  	index := s.list[0].prev
   141  	node := (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0])))
   142  	evictedValue := node.value
   143  	s.table_Delete(uint32(s.table_hasher(noescape(unsafe.Pointer(&node.key)), s.table_seed)), node.key)
   144  
   145  	node.key = key
   146  	node.value = value
   147  	if ttl > 0 {
   148  		node.ttl = uint32(ttl / time.Second)
   149  		node.expires = atomic.LoadUint32(&clock) + node.ttl
   150  	}
   151  	s.table_Set(hash, key, index)
   152  	s.list_MoveToFront(index)
   153  	prev = evictedValue
   154  
   155  	s.mu.Unlock()
   156  	return
   157  }
   158  
   159  func (s *ttlshard[K, V]) Set(hash uint32, key K, value V, ttl time.Duration) (prev V, replaced bool) {
   160  	s.mu.Lock()
   161  
   162  	s.stats_setcalls++
   163  
   164  	if index, exists := s.table_Get(hash, key); exists {
   165  		// node := &s.list[index]
   166  		node := (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0])))
   167  		previousValue := node.value
   168  		s.list_MoveToFront(index)
   169  		node.value = value
   170  		if ttl > 0 {
   171  			node.ttl = uint32(ttl / time.Second)
   172  			node.expires = atomic.LoadUint32(&clock) + node.ttl
   173  		}
   174  		prev = previousValue
   175  		replaced = true
   176  
   177  		s.mu.Unlock()
   178  		return
   179  	}
   180  
   181  	// index := s.list_Back()
   182  	// node := &s.list[index]
   183  	index := s.list[0].prev
   184  	node := (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0])))
   185  	evictedValue := node.value
   186  	if key != node.key {
   187  		s.table_Delete(uint32(s.table_hasher(noescape(unsafe.Pointer(&node.key)), s.table_seed)), node.key)
   188  	}
   189  
   190  	node.key = key
   191  	node.value = value
   192  	if ttl > 0 {
   193  		node.ttl = uint32(ttl / time.Second)
   194  		node.expires = atomic.LoadUint32(&clock) + node.ttl
   195  	}
   196  	s.table_Set(hash, key, index)
   197  	s.list_MoveToFront(index)
   198  	prev = evictedValue
   199  
   200  	s.mu.Unlock()
   201  	return
   202  }
   203  
   204  func (s *ttlshard[K, V]) Delete(hash uint32, key K) (v V) {
   205  	s.mu.Lock()
   206  
   207  	if index, exists := s.table_Get(hash, key); exists {
   208  		node := &s.list[index]
   209  		value := node.value
   210  		s.list_MoveToBack(index)
   211  		node.value = v
   212  		s.table_Delete(hash, key)
   213  		v = value
   214  	}
   215  
   216  	s.mu.Unlock()
   217  
   218  	return
   219  }
   220  
   221  func (s *ttlshard[K, V]) Len() (n uint32) {
   222  	s.mu.Lock()
   223  	// inlining s.table_Len()
   224  	n = s.table_length
   225  	s.mu.Unlock()
   226  
   227  	return
   228  }
   229  
   230  func (s *ttlshard[K, V]) AppendKeys(dst []K, now uint32) []K {
   231  	s.mu.Lock()
   232  	for _, bucket := range s.table_buckets {
   233  		b := (*ttlbucket)(unsafe.Pointer(&bucket))
   234  		if b.index == 0 {
   235  			continue
   236  		}
   237  		node := &s.list[b.index]
   238  		if expires := node.expires; expires == 0 || now <= expires {
   239  			dst = append(dst, node.key)
   240  		}
   241  	}
   242  	s.mu.Unlock()
   243  
   244  	return dst
   245  }