github.com/maypok86/otter@v1.2.1/internal/expiry/variable.go (about)

     1  // Copyright (c) 2024 Alexey Mayshev. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package expiry
    16  
    17  import (
    18  	"math"
    19  	"math/bits"
    20  	"time"
    21  
    22  	"github.com/maypok86/otter/internal/generated/node"
    23  	"github.com/maypok86/otter/internal/unixtime"
    24  	"github.com/maypok86/otter/internal/xmath"
    25  )
    26  
    27  var (
    28  	buckets = []uint32{64, 64, 32, 4, 1}
    29  	spans   = []uint32{
    30  		xmath.RoundUpPowerOf2(uint32((1 * time.Second).Seconds())),             // 1s
    31  		xmath.RoundUpPowerOf2(uint32((1 * time.Minute).Seconds())),             // 1.07m
    32  		xmath.RoundUpPowerOf2(uint32((1 * time.Hour).Seconds())),               // 1.13h
    33  		xmath.RoundUpPowerOf2(uint32((24 * time.Hour).Seconds())),              // 1.52d
    34  		buckets[3] * xmath.RoundUpPowerOf2(uint32((24 * time.Hour).Seconds())), // 6.07d
    35  		buckets[3] * xmath.RoundUpPowerOf2(uint32((24 * time.Hour).Seconds())), // 6.07d
    36  	}
    37  	shift = []uint32{
    38  		uint32(bits.TrailingZeros32(spans[0])),
    39  		uint32(bits.TrailingZeros32(spans[1])),
    40  		uint32(bits.TrailingZeros32(spans[2])),
    41  		uint32(bits.TrailingZeros32(spans[3])),
    42  		uint32(bits.TrailingZeros32(spans[4])),
    43  	}
    44  )
    45  
    46  type Variable[K comparable, V any] struct {
    47  	wheel [][]node.Node[K, V]
    48  	time  uint32
    49  }
    50  
    51  func NewVariable[K comparable, V any](nodeManager *node.Manager[K, V]) *Variable[K, V] {
    52  	wheel := make([][]node.Node[K, V], len(buckets))
    53  	for i := 0; i < len(wheel); i++ {
    54  		wheel[i] = make([]node.Node[K, V], buckets[i])
    55  		for j := 0; j < len(wheel[i]); j++ {
    56  			var k K
    57  			var v V
    58  			fn := nodeManager.Create(k, v, math.MaxUint32, 1)
    59  			fn.SetPrevExp(fn)
    60  			fn.SetNextExp(fn)
    61  			wheel[i][j] = fn
    62  		}
    63  	}
    64  	return &Variable[K, V]{
    65  		wheel: wheel,
    66  	}
    67  }
    68  
    69  // findBucket determines the bucket that the timer event should be added to.
    70  func (v *Variable[K, V]) findBucket(expiration uint32) node.Node[K, V] {
    71  	duration := expiration - v.time
    72  	length := len(v.wheel) - 1
    73  	for i := 0; i < length; i++ {
    74  		if duration < spans[i+1] {
    75  			ticks := expiration >> shift[i]
    76  			index := ticks & (buckets[i] - 1)
    77  			return v.wheel[i][index]
    78  		}
    79  	}
    80  	return v.wheel[length][0]
    81  }
    82  
    83  // Add schedules a timer event for the node.
    84  func (v *Variable[K, V]) Add(n node.Node[K, V]) {
    85  	root := v.findBucket(n.Expiration())
    86  	link(root, n)
    87  }
    88  
    89  // Delete removes a timer event for this entry if present.
    90  func (v *Variable[K, V]) Delete(n node.Node[K, V]) {
    91  	unlink(n)
    92  	n.SetNextExp(nil)
    93  	n.SetPrevExp(nil)
    94  }
    95  
    96  func (v *Variable[K, V]) RemoveExpired(expired []node.Node[K, V]) []node.Node[K, V] {
    97  	currentTime := unixtime.Now()
    98  	prevTime := v.time
    99  	v.time = currentTime
   100  
   101  	for i := 0; i < len(shift); i++ {
   102  		previousTicks := prevTime >> shift[i]
   103  		currentTicks := currentTime >> shift[i]
   104  		delta := currentTicks - previousTicks
   105  		if delta == 0 {
   106  			break
   107  		}
   108  
   109  		expired = v.removeExpiredFromBucket(expired, i, previousTicks, delta)
   110  	}
   111  
   112  	return expired
   113  }
   114  
   115  func (v *Variable[K, V]) removeExpiredFromBucket(expired []node.Node[K, V], index int, prevTicks, delta uint32) []node.Node[K, V] {
   116  	mask := buckets[index] - 1
   117  	steps := buckets[index]
   118  	if delta < steps {
   119  		steps = delta
   120  	}
   121  	start := prevTicks & mask
   122  	end := start + steps
   123  	timerWheel := v.wheel[index]
   124  	for i := start; i < end; i++ {
   125  		root := timerWheel[i&mask]
   126  		n := root.NextExp()
   127  		root.SetPrevExp(root)
   128  		root.SetNextExp(root)
   129  
   130  		for !node.Equals(n, root) {
   131  			next := n.NextExp()
   132  			n.SetPrevExp(nil)
   133  			n.SetNextExp(nil)
   134  
   135  			if n.Expiration() <= v.time {
   136  				expired = append(expired, n)
   137  			} else {
   138  				v.Add(n)
   139  			}
   140  
   141  			n = next
   142  		}
   143  	}
   144  
   145  	return expired
   146  }
   147  
   148  func (v *Variable[K, V]) Clear() {
   149  	for i := 0; i < len(v.wheel); i++ {
   150  		for j := 0; j < len(v.wheel[i]); j++ {
   151  			root := v.wheel[i][j]
   152  			n := root.NextExp()
   153  			// NOTE(maypok86): Maybe we should use the same approach as in RemoveExpired?
   154  
   155  			for !node.Equals(n, root) {
   156  				next := n.NextExp()
   157  				v.Delete(n)
   158  
   159  				n = next
   160  			}
   161  		}
   162  	}
   163  	v.time = unixtime.Now()
   164  }
   165  
   166  // link adds the entry at the tail of the bucket's list.
   167  func link[K comparable, V any](root, n node.Node[K, V]) {
   168  	n.SetPrevExp(root.PrevExp())
   169  	n.SetNextExp(root)
   170  
   171  	root.PrevExp().SetNextExp(n)
   172  	root.SetPrevExp(n)
   173  }
   174  
   175  // unlink removes the entry from its bucket, if scheduled.
   176  func unlink[K comparable, V any](n node.Node[K, V]) {
   177  	next := n.NextExp()
   178  	if !node.Equals(next, nil) {
   179  		prev := n.PrevExp()
   180  		next.SetPrevExp(prev)
   181  		prev.SetNextExp(next)
   182  	}
   183  }