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 }